diff --git a/.ci/Arch/Dockerfile b/.ci/Arch/Dockerfile
index 36cf5c4ae..b08e568f3 100644
--- a/.ci/Arch/Dockerfile
+++ b/.ci/Arch/Dockerfile
@@ -8,10 +8,13 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \
gtest \
mariadb-libs \
ninja \
+ openssl \
protobuf \
qt6-base \
+ qt6-declarative \
qt6-imageformats \
qt6-multimedia \
+ qt6-shadertools \
qt6-svg \
qt6-tools \
qt6-translations \
diff --git a/.ci/Debian12/Dockerfile b/.ci/Debian12/Dockerfile
index 202405b84..fc756aac2 100644
--- a/.ci/Debian12/Dockerfile
+++ b/.ci/Debian12/Dockerfile
@@ -15,11 +15,14 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
+ libssl-dev \
ninja-build \
protobuf-compiler \
+ qt6-declarative-dev \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
+ qt6-shadertools-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
diff --git a/.ci/Debian13/Dockerfile b/.ci/Debian13/Dockerfile
index d7ab6ac86..bdecb56df 100644
--- a/.ci/Debian13/Dockerfile
+++ b/.ci/Debian13/Dockerfile
@@ -16,11 +16,14 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
+ libssl-dev \
ninja-build \
protobuf-compiler \
+ qt6-declarative-dev \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
+ qt6-shadertools-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
diff --git a/.ci/Fedora43/Dockerfile b/.ci/Fedora43/Dockerfile
index 27570cf99..463da5a51 100644
--- a/.ci/Fedora43/Dockerfile
+++ b/.ci/Fedora43/Dockerfile
@@ -7,8 +7,9 @@ RUN dnf install -y \
git \
mariadb-devel \
ninja-build \
+ openssl-devel \
protobuf-devel \
- qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
+ qt6-{qtdeclarative,qtmultimedia,qtshadertools,qtsvg,qttools,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \
diff --git a/.ci/Fedora44/Dockerfile b/.ci/Fedora44/Dockerfile
index e6c8da7f3..62238e760 100644
--- a/.ci/Fedora44/Dockerfile
+++ b/.ci/Fedora44/Dockerfile
@@ -7,8 +7,9 @@ RUN dnf install -y \
git \
mariadb-devel \
ninja-build \
+ openssl-devel \
protobuf-devel \
- qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
+ qt6-{qtdeclarative,qtmultimedia,qtshadertools,qtsvg,qttools,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \
diff --git a/.ci/Servatrice_Debian12/Dockerfile b/.ci/Servatrice_Debian12/Dockerfile
index 21f6a036e..321aa7c0f 100644
--- a/.ci/Servatrice_Debian12/Dockerfile
+++ b/.ci/Servatrice_Debian12/Dockerfile
@@ -12,6 +12,7 @@ RUN apt-get update && \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6sql6-mysql \
+ libssl-dev \
ninja-build \
protobuf-compiler \
qt6-tools-dev \
diff --git a/.ci/Ubuntu24.04/Dockerfile b/.ci/Ubuntu24.04/Dockerfile
index 809b2e43a..715997474 100644
--- a/.ci/Ubuntu24.04/Dockerfile
+++ b/.ci/Ubuntu24.04/Dockerfile
@@ -15,11 +15,14 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
+ libssl-dev \
ninja-build \
protobuf-compiler \
+ qt6-declarative-dev \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
+ qt6-shadertools-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
diff --git a/.ci/Ubuntu26.04/Dockerfile b/.ci/Ubuntu26.04/Dockerfile
index 7b0cd389f..96dd10763 100644
--- a/.ci/Ubuntu26.04/Dockerfile
+++ b/.ci/Ubuntu26.04/Dockerfile
@@ -16,11 +16,14 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
+ libssl-dev \
ninja-build \
protobuf-compiler \
+ qt6-declarative-dev \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
+ qt6-shadertools-dev \
qt6-svg-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
diff --git a/.ci/compile.sh b/.ci/compile.sh
index be9923a66..379b4eac4 100755
--- a/.ci/compile.sh
+++ b/.ci/compile.sh
@@ -157,6 +157,9 @@ if [[ $MAKE_TEST ]]; then
fi
if [[ $USE_CCACHE == "1" ]]; then
flags+=("-DUSE_CCACHE=1")
+ # PCH-aware caching is required or ccache refuses to cache any TU that
+ # consumes a precompiled header, silently recompiling everything on every run.
+ ccache --set-config sloppiness=pch_defines,time_macros
if [[ -n $CCACHE_SIZE ]]; then
# Note, this setting persists after running the script
ccache --max-size "$CCACHE_SIZE"
@@ -169,6 +172,7 @@ if [[ -n $PACKAGE_TYPE ]]; then
fi
if [[ $USE_VCPKG ]]; then
flags+=("-DUSE_VCPKG=1")
+ flags+=("-DVCPKG_INSTALL_OPTIONS=--x-abi-tools-use-exact-versions")
# if [[ $MAKE_PACKAGE && $RUNNER_OS == Windows ]]; then
# flags+=("-DVCPKG_APPLOCAL_DEPS=OFF") # disable copying of runtime DLLs into build output
# fi
diff --git a/.ci/release_template.md b/.ci/release_template.md
index ac78a193a..23b475150 100644
--- a/.ci/release_template.md
+++ b/.ci/release_template.md
@@ -12,9 +12,9 @@ Available pre-compiled binaries for installation:
• Windows 10+
macOS
- • macOS 15+ Sequoia Apple M
- • macOS 14+ Sonoma Apple M
- • macOS 13+ Ventura Intel
+ • macOS 15+ Sequoia
+ • macOS 14+ Sonoma
+ • macOS 13+ Ventura (x86)
Linux
• Ubuntu 26.04 LTS Resolute Racoon
@@ -24,10 +24,10 @@ Available pre-compiled binaries for installation:
• Fedora 44
• Fedora 43
-We are also packaged in Arch Linux's official extra repository, courtesy of @FFY00.
-General Linux support is available via a flatpak package at Flathub!
+ General Linux support is available via a flatpak package hosted at Flathub (x86 & ARM)!
+ Thanks to courtesy of @FFY00, the app is also available in Arch Linux's official extra repository.
-We provide a Docker image for "Servatrice" in GHCR. You can docker pull it or use our Docker Compose files!
+ We maintain a Docker image for "Servatrice" in GHCR (x86 & ARM). You can docker pull it or use our Docker Compose files!
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 56ad64283..589cae1d8 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -334,7 +334,7 @@ the tr() call, also you can add an extra string as a hint for translators:
QString message = tr("Everyone draws %n cards", "english hint for translators", amount);
```
See [Qt's wiki on translations](
-https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals)
+https://doc.qt.io/qt-6/i18n-source-translation.html#handling-plurals)
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
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 000000000..75fbc59f1
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,95 @@
+# GitHub Docs on Code Scanning:
+# https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning
+# https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration
+# https://docs.github.com/en/code-security/reference/code-scanning/workflow-configuration-options
+#
+# CodeQL Docs:
+# https://codeql.github.com/docs/
+
+name: CodeQL
+
+permissions:
+ contents: read
+ security-events: write # needed to post results
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+
+# Cancel earlier, unfinished runs of this workflow on the same branch
+concurrency:
+ group: "${{ github.workflow }} @ ${{ github.ref_name }}"
+ cancel-in-progress: true
+
+jobs:
+ analyze:
+ name: Analyze (${{ matrix.language }})
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ # https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/
+ - language: cpp
+ build-mode: manual
+ - language: actions
+ build-mode: none
+
+ steps:
+ - name: "Checkout repository"
+ uses: actions/checkout@v7
+
+ - name: "Initialize CodeQL"
+ uses: github/codeql-action/init@v4
+ with:
+ languages: ${{ matrix.language }}
+ build-mode: ${{ matrix.build-mode }}
+ # https://docs.github.com/en/code-security/reference/code-scanning/codeql/codeql-queries/c-cpp-built-in-queries
+ # https://docs.github.com/en/code-security/reference/code-scanning/codeql/codeql-queries/actions-built-in-queries
+ queries: security-extended
+ dependency-caching: true
+
+ - name: "[C++] Install dependencies"
+ if: matrix.language == 'cpp' && matrix.build-mode == 'manual'
+ shell: bash
+ env:
+ DEBIAN_FRONTEND: noninteractive
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends \
+ cmake \
+ g++ \
+ libgl-dev \
+ liblzma-dev \
+ libmariadb-dev-compat \
+ libprotobuf-dev \
+ ninja-build \
+ protobuf-compiler \
+ qt6-declarative-dev \
+ qt6-multimedia-dev \
+ qt6-shadertools-dev \
+ qt6-svg-dev \
+ qt6-tools-dev \
+ qt6-tools-dev-tools \
+ qt6-websockets-dev
+
+# Minimize dependency install
+# Add ccache usage for faster compilation, (install ccache dep, actions/cache step + append DUSE_CCACHE=1 in cmake config, CCACHE env values)
+
+ - name: "[C++] Configure CMake"
+ if: matrix.language == 'cpp' && matrix.build-mode == 'manual'
+ shell: bash
+ run: cmake -S . -B build -G Ninja -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=Release
+
+ - name: "[C++] Build application"
+ if: matrix.language == 'cpp' && matrix.build-mode == 'manual'
+ shell: bash
+ run: cmake --build build
+
+ - name: "Perform CodeQL Analysis"
+ uses: github/codeql-action/analyze@v4
+ with:
+ category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index 6c1e475f3..6e60a77be 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -3,7 +3,7 @@ name: Build Desktop
permissions:
actions: write # needed to delete entries in GHA cache (update ccache)
attestations: write # needed to persist the attestation.
- contents: write
+ contents: write # needed for e.g. vcpkg dependency graph updates
id-token: write # needed for signing certificate in attestation
on:
@@ -34,7 +34,7 @@ on:
- 'vcpkg.json'
- 'vcpkg' # needed to match submodule bumps (gitlink)
-# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
+# Cancel earlier, unfinished runs of this workflow on the same branch (unless on tag --> release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_type != 'tag' }}
@@ -42,7 +42,7 @@ concurrency:
jobs:
configure:
name: Configure
- runs-on: ubuntu-slim
+ runs-on: ubuntu-slim # https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md
outputs:
tag: ${{ steps.configure.outputs.tag }}
sha: ${{ steps.configure.outputs.sha }}
@@ -146,13 +146,13 @@ jobs:
name: ${{ matrix.distro }} ${{ matrix.version }}
needs: configure
- runs-on: ubuntu-latest
+ runs-on: ubuntu-latest # https://github.com/actions/runner-images
continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70
env:
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
CCACHE_EVICTION_AGE: 7d
- CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
+ CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
@@ -161,14 +161,11 @@ jobs:
uses: actions/checkout@v7
- name: "Restore compiler cache (ccache)"
- id: ccache_restore
+ id: restore_ccache
uses: actions/cache/restore@v6
- env:
- BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
- key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }}
+ key: ccache-${{ matrix.distro }}${{ matrix.version }}
path: ${{ env.CACHE }}
- restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}-
- name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
shell: bash
@@ -179,8 +176,12 @@ jobs:
shell: bash
run: |
source .ci/docker.sh
- RUN --server --debug --test --ccache "$CCACHE_SIZE" \
- --cmake-generator "$CMAKE_GENERATOR"
+ args=()
+ [[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
+ args+=(--ccache "$CCACHE_SIZE")
+ args+=(--cmake-generator "$CMAKE_GENERATOR")
+
+ RUN --server --debug --test "${args[@]}"
- name: "Build release package"
id: build
@@ -203,10 +204,10 @@ jobs:
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: "Delete remote compiler cache (ccache)"
- if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
+ if: github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
continue-on-error: true
env:
- CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
+ CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
@@ -217,7 +218,7 @@ jobs:
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
- key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
+ key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
path: ${{ env.CACHE }}
- name: "Upload artifact"
@@ -262,73 +263,76 @@ jobs:
matrix:
include:
- os: macOS
- target: 13
- runner: macos-15-intel
+ target: 13 # EOL 2025-09-15
+ runner: macos-15-intel # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
+ # macos-26-intel is very slow and fails in CPack during DMG config if not increasing Finder timeout
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 13
package_suffix: "-macOS13_Intel"
- qt_version: 6.11.0
- qt_modules: qtimageformats qtmultimedia qtwebsockets
+ qt_version: 6.11.*
+ qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Intel
type: Release
use_ccache: 1
- xcode: "16.4"
+ xcode: "26.3"
- os: macOS
- target: 14
- runner: macos-14
+ target: 14 # EOL 2026-??
+ runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
+ override_target: 14
package_suffix: "-macOS14"
- qt_version: 6.11.0
- qt_modules: qtimageformats qtmultimedia qtwebsockets
+ qt_version: 6.11.*
+ qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
type: Release
use_ccache: 1
- xcode: "15.4"
+ xcode: "26.6"
- os: macOS
target: 15
- runner: macos-15
+ runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
+ override_target: 15
package_suffix: "-macOS15"
- qt_version: 6.11.0
- qt_modules: qtimageformats qtmultimedia qtwebsockets
+ qt_version: 6.11.*
+ qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
type: Release
use_ccache: 1
- xcode: "16.4"
+ xcode: "26.6"
- os: macOS
- target: 15
- runner: macos-15
+ target: 26
+ runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
- qt_version: 6.11.0
- qt_modules: qtimageformats qtmultimedia qtwebsockets
+ qt_version: 6.11.*
+ qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
type: Debug
use_ccache: 1
- xcode: "16.4"
+ xcode: "26.6"
- os: Windows
target: 10
- runner: windows-2025
+ runner: windows-2025 # https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-VS2026-Readme.md
cmake_generator: Ninja
make_package: 1
package_suffix: "-Win10"
- qt_version: 6.11.0
- qt_modules: qtimageformats qtmultimedia qtwebsockets
+ qt_version: 6.11.*
+ qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
type: Release
- os: Windows
@@ -339,8 +343,8 @@ jobs:
cmake_generator_platform: x64
make_package: 1
package_suffix: "-Win10_VS"
- qt_version: 6.11.0
- qt_modules: qtimageformats qtmultimedia qtwebsockets
+ qt_version: 6.11.*
+ qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
type: Release
name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}${{ matrix.cmake_generator != 'Ninja' && ' VS' || '' }}
@@ -349,7 +353,7 @@ jobs:
timeout-minutes: 100
env:
CCACHE_DIR: ${{ github.workspace }}/.cache/
- CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
+ CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
steps:
- name: "Checkout"
@@ -369,54 +373,54 @@ jobs:
- name: "[macOS] Restore compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1
- id: ccache_restore
+ id: restore_ccache
uses: actions/cache/restore@v6
- env:
- BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
- key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }}
+ key: ccache-${{ matrix.runner }}_${{ matrix.override_target }}-Xcode${{ matrix.xcode }}
path: ${{ env.CCACHE_DIR }}
- restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-
- - name: "Install aqtinstall"
+ - name: "[macOS] Install aqtinstall"
+ if: matrix.os == 'macOS'
run: pipx install aqtinstall
# Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases
- - name: "Resolve latest Qt patch version"
+ - name: "[macOS] Resolve latest Qt from ${{ matrix.qt_version }} input"
+ if: matrix.os == 'macOS'
env:
QT_VERSION: ${{ matrix.qt_version }}
id: resolve_qt_version
shell: bash
run: .ci/resolve_latest_aqt_qt_version.sh "$QT_VERSION"
- - name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
+ - name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }}"
if: matrix.os == 'macOS'
id: restore_qt
uses: actions/cache/restore@v6
with:
- key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
+ key: Qt-${{ steps.resolve_qt_version.outputs.version }}-macOS-${{ matrix.soc }}-${{ matrix.qt_modules }}-thin
path: ${{ github.workspace }}/Qt
# Using jurplel/install-qt-action to install Qt without using brew
# Qt build using vcpkg either just fails or takes too long to build
- - name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}"
+ - name: "[macOS] Install fat Qt ${{ matrix.qt_version }}"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
cache: false
- dir: ${{ github.workspace }}
+ # cache-key-prefix: Qt
+ dir: ${{ github.workspace }} # thinning script depends on this location
modules: ${{ matrix.qt_modules }}
- version: ${{ steps.resolve_qt_version.outputs.version }}
+ version: ${{ matrix.qt_version }}
- name: "[macOS] Create thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh
- name: "[macOS] Cache thin Qt libraries"
- if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
+ if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
- key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
+ key: ${{ steps.restore_qt.outputs.cache-primary-key }}
path: ${{ github.workspace }}/Qt
- name: "[Windows] Install Qt ${{ matrix.qt_version }}"
@@ -425,9 +429,10 @@ jobs:
with:
# Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
aqtsource: git+https://github.com/miurahr/aqtinstall.git
- cache: true
+ cache: ${{ github.ref == 'refs/heads/master' }}
+ cache-key-prefix: Qt
modules: ${{ matrix.qt_modules }}
- version: ${{ steps.resolve_qt_version.outputs.version }}
+ version: ${{ matrix.qt_version }}
- name: "[Windows] Install NSIS"
if: matrix.os == 'Windows'
@@ -450,6 +455,7 @@ jobs:
CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
+ GITHUB_TOKEN: ${{ github.token }} # needed for vcpkg dependency graph updates, see VCPKG_FEATURE_FLAGS
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
@@ -458,27 +464,28 @@ jobs:
PACKAGE_SUFFIX: '${{ matrix.package_suffix }}'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
USE_CCACHE: ${{ matrix.use_ccache }}
- VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
+ VCPKG_BINARY_SOURCES: "clear;files,${{ steps.vcpkg-cache.outputs.path }},${{ case(github.ref == 'refs/heads/master', 'readwrite', 'read') }}"
VCPKG_DISABLE_METRICS: 1
+ VCPKG_FEATURE_FLAGS: dependencygraph
run: .ci/compile.sh --server --test --vcpkg
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: "[macOS] Delete remote compiler cache (ccache)"
- if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
continue-on-error: true
env:
- CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
+ CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
echo "Cache deleted successfully"
fi
- - name: "[macOS] Save updated compiler cache (ccache)"
+ - name: "[macOS] Cache updated compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
- key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
+ key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
path: ${{ env.CCACHE_DIR }}
- name: "[macOS] Sign app bundle"
diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml
index 5f31ea59c..93ba79464 100644
--- a/.github/workflows/desktop-lint.yml
+++ b/.github/workflows/desktop-lint.yml
@@ -1,5 +1,8 @@
name: Code Style (C++)
+permissions:
+ contents: read
+
on:
# Push trigger not needed for linting, we do not allow direct pushes to master
pull_request:
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 5384c9e64..967d94c58 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -1,8 +1,8 @@
-name: Build Docker Image
+name: Build Docker
permissions:
- contents: read
- packages: write
+ contents: read # needed to checkout repo
+ packages: write # needed for interacting with GHCR
on:
push:
@@ -13,7 +13,10 @@ on:
- master
paths:
- '.github/workflows/docker-release.yml'
+ - '.dockerignore'
- 'Dockerfile'
+ - 'docker-compose.yml'
+ - 'docker-compose.yml.windows'
release:
types:
- released # publishing of stable releases
@@ -23,36 +26,38 @@ concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.event_name != 'release' }}
+env:
+ GHCR_IMAGE: ghcr.io/cockatrice/servatrice
+ OCI_DESCRIPTION: Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
+ OCI_TITLE: Servatrice
+ OCI_URL: https://cockatrice.github.io/
+
jobs:
- docker:
- name: amd64 & arm64
- if: ${{ github.repository_owner == 'Cockatrice' }}
- runs-on: ubuntu-latest
-
+ # Create one platform-specific image and publish its OCI image manifest per matrix job
+ build:
+ name: "Servatrice (${{ matrix.label }})"
+ if: github.repository_owner == 'Cockatrice'
+ runs-on: ${{ matrix.runner }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - label: x86
+ platform: linux/amd64
+ runner: ubuntu-latest # https://github.com/actions/runner-images
+
+ - label: arm
+ platform: linux/arm64
+ runner: ubuntu-24.04-arm # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Arm64-Readme.md, replace with "ubuntu-latest-arm" once available
+
+ env:
+ CACHE_SCOPE: servatrice-${{ matrix.label }}
+
steps:
- name: "Checkout"
uses: actions/checkout@v7
- - name: "Docker metadata"
- id: metadata
- uses: docker/metadata-action@v6
- env:
- DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR
- with:
- annotations: |
- org.opencontainers.image.title=Servatrice
- org.opencontainers.image.url=https://cockatrice.github.io/
- org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
- images: |
- ghcr.io/cockatrice/servatrice
- labels: |
- org.opencontainers.image.title=Servatrice
- org.opencontainers.image.url=https://cockatrice.github.io/
- org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
-
- - name: "Set up QEMU"
- uses: docker/setup-qemu-action@v4
-
- name: "Set up Docker buildx"
uses: docker/setup-buildx-action@v4
@@ -61,18 +66,117 @@ jobs:
id: login
uses: docker/login-action@v4
with:
- password: ${{ github.token }}
registry: ghcr.io
username: ${{ github.actor }}
+ password: ${{ github.token }}
- - name: "Build and push Docker image"
+ # Don't push for non-release triggers
+ - name: "Build image"
+ if: steps.login.outcome != 'success'
uses: docker/build-push-action@v7
with:
- annotations: ${{ steps.metadata.outputs.annotations }}
- cache-from: type=gha,scope=servatrice
- cache-to: type=gha,mode=max,scope=servatrice
+ cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
+ cache-to: ${{ case(github.ref == 'refs/heads/master', format('type=gha,mode=max,scope={0}', env.CACHE_SCOPE), '') }}
context: .
- labels: ${{ steps.metadata.outputs.labels }}
- platforms: linux/amd64,linux/arm64
- push: ${{ steps.login.outcome == 'success' }}
- tags: ${{ steps.metadata.outputs.tags }}
+ platforms: ${{ matrix.platform }}
+ push: false
+
+ # Add OCI labels and push single-platform image by digest (without tags)
+ - name: "Build image and push by digest"
+ if: steps.login.outcome == 'success'
+ id: build
+ uses: docker/build-push-action@v7
+ with:
+ cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
+ cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
+ context: .
+ labels: |
+ org.opencontainers.image.description=${{ env.OCI_DESCRIPTION }}
+ org.opencontainers.image.title=${{ env.OCI_TITLE }}
+ org.opencontainers.image.url=${{ env.OCI_URL }}
+ outputs: type=image,name=${{ env.GHCR_IMAGE }},name-canonical=true,push=true,push-by-digest=true
+ platforms: ${{ matrix.platform }}
+ provenance: mode=max # Do not pass secrets as build arguments with this option
+ sbom: true
+
+ - name: "Export digest"
+ if: steps.login.outcome == 'success'
+ env:
+ DIGEST: ${{ steps.build.outputs.digest }}
+ run: |
+ mkdir -p "$RUNNER_TEMP/digests"
+ touch "$RUNNER_TEMP/digests/${DIGEST#sha256:}"
+
+ - name: "Upload digest"
+ if: steps.login.outcome == 'success'
+ uses: actions/upload-artifact@v7
+ with:
+ archive: false
+ if-no-files-found: error
+ name: digest-${{ matrix.label }}
+ path: ${{ runner.temp }}/digests/*
+ retention-days: 1
+
+
+ # Create an OCI image index from the platform-specific image manifests
+ index:
+ name: "Publish multi-platform Servatrice image"
+ if: github.repository_owner == 'Cockatrice' && github.event_name == 'release' && github.event.release.prerelease == false
+ needs: build
+ runs-on: ubuntu-slim # https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md
+
+ steps:
+ - name: "Download digests"
+ uses: actions/download-artifact@v8
+ with:
+ path: ${{ runner.temp }}/digests
+ pattern: digest-*
+ merge-multiple: true
+
+ - name: "Login to GitHub Container Registry (GHCR)"
+ uses: docker/login-action@v4
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ github.token }}
+
+ - name: "Docker metadata"
+ id: metadata
+ uses: docker/metadata-action@v6
+ with:
+ images: ${{ env.GHCR_IMAGE }}
+ flavor: |
+ latest=auto
+ tags: |
+ type=ref,event=tag # if semver, also: type=semver,pattern={{version}} / {{major}}.{{minor}}
+
+ # Add OCI annotations to image index and publish tags
+ - name: "Create image index"
+ env:
+ DOCKER_TAGS: ${{ steps.metadata.outputs.tags }}
+ working-directory: ${{ runner.temp }}/digests
+ run: |
+ TAG_ARGS=()
+ while IFS= read -r tag; do
+ TAG_ARGS+=(--tag "$tag")
+ done <<< "$DOCKER_TAGS"
+
+ DIGEST_ARGS=()
+ for digest in *; do
+ DIGEST_ARGS+=("$GHCR_IMAGE@sha256:$digest")
+ done
+
+ docker buildx imagetools create \
+ --prefer-index=true \
+ --annotation "index:org.opencontainers.image.description=$OCI_DESCRIPTION" \
+ --annotation "index:org.opencontainers.image.title=$OCI_TITLE" \
+ --annotation "index:org.opencontainers.image.url=$OCI_URL" \
+ "${TAG_ARGS[@]}" \
+ "${DIGEST_ARGS[@]}"
+
+ - name: "Inspect images"
+ env:
+ GITHUB_TAG: ${{ github.ref_name }}
+ run: |
+ docker buildx imagetools inspect "$GHCR_IMAGE:latest"
+ docker buildx imagetools inspect "$GHCR_IMAGE:$GITHUB_TAG"
diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml
index 4b9ca79ab..419cbfbfb 100644
--- a/.github/workflows/documentation-build.yml
+++ b/.github/workflows/documentation-build.yml
@@ -1,5 +1,8 @@
name: Generate Docs
+permissions:
+ contents: read # write permission to the destination repo come from 'deploy_key'
+
on:
pull_request:
paths:
@@ -11,6 +14,11 @@ on:
- published # publishing of stable releases and pre-releases
workflow_dispatch:
+# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
+concurrency:
+ group: "${{ github.workflow }} @ ${{ github.ref_name }}"
+ cancel-in-progress: ${{ github.event_name != 'release' }}
+
env:
COCKATRICE_REF: ${{ github.ref_name }} # tag name if the commit is tagged, otherwise branch name
diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml
index a3db5f86d..71b0b4c22 100644
--- a/.github/workflows/translations-pull.yml
+++ b/.github/workflows/translations-pull.yml
@@ -1,5 +1,9 @@
name: Update Translations
+permissions:
+ contents: read
+ pull-requests: write
+
on:
pull_request:
paths:
diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml
index c4d3f61fb..41a7aef40 100644
--- a/.github/workflows/translations-push.yml
+++ b/.github/workflows/translations-push.yml
@@ -1,5 +1,9 @@
name: Update Translation Source
+permissions:
+ contents: read
+ pull-requests: write
+
on:
pull_request:
paths:
diff --git a/.gitignore b/.gitignore
index 33c1ae31d..54939bdcf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ mysql.cnf
.DS_Store
.idea/
*.aps
+*.cache
cmake-build*
preferences
compile_commands.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4ac9201ae..ab63028ae 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,23 +5,23 @@
# This file sets all the variables shared between the projects
# like the installation path, compilation flags etc..
-# cmake 3.16 is required if using qt6
-cmake_minimum_required(VERSION 3.10)
+# 3.16 required for Qt6 and target_precompile_headers()
+cmake_minimum_required(VERSION 3.16)
-# Early detect ccache
+# Use compiler cache (ccache)
option(USE_CCACHE "Cache the build results with ccache" ON)
# Treat warnings as errors (Debug builds only)
option(WARNING_AS_ERROR "Treat warnings as errors in debug builds" ON)
# Check for translation updates
option(UPDATE_TRANSLATIONS "Update translations on compile" OFF)
-# Compile servatrice
-option(WITH_SERVER "build servatrice" OFF)
-# Compile cockatrice
-option(WITH_CLIENT "build cockatrice" ON)
-# Compile oracle
-option(WITH_ORACLE "build oracle" ON)
+# Compile Cockatrice
+option(WITH_CLIENT "Build Cockatrice client" ON)
+# Compile Oracle
+option(WITH_ORACLE "Build Cockatrice card database tool (Oracle)" ON)
+# Compile Servatrice
+option(WITH_SERVER "Build Cockatrice server (Servatrice)" OFF)
# Compile tests
-option(TEST "build tests" OFF)
+option(TEST "Build tests" OFF)
# Use vcpkg regardless of OS
option(USE_VCPKG "Use vcpkg regardless of OS" OFF)
@@ -34,13 +34,24 @@ if(NOT CMAKE_BUILD_TYPE)
)
endif()
-if(USE_CCACHE)
+# ccache does not support MSVC and must not auto-engage on Windows
+# (it is installed unintentionally on the Windows CI runner).
+# NOTE: this keys off the target OS, so a mingw/Ninja configuration on Windows
+# also opts out of ccache even though the GNUCXX branch below supports it.
+if(USE_CCACHE AND NOT WIN32)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
# Support Unix Makefiles and Ninja
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
+ # PCH-aware caching, matching .ci/compile.sh: without this ccache refuses
+ # to cache any TU that consumes a precompiled header, so every PCH-backed
+ # target recompiles from scratch on each build.
+ execute_process(COMMAND ${CCACHE_PROGRAM} --set-config sloppiness=pch_defines,time_macros)
message(STATUS "Found CCache ${CCACHE_PROGRAM}")
endif()
+elseif(USE_CCACHE AND WIN32)
+ # An explicit opt-in must not disappear silently on Windows.
+ message(STATUS "ccache disabled: not supported for the MSVC toolchain on Windows")
endif()
if(WIN32 OR USE_VCPKG)
@@ -59,10 +70,11 @@ if(WIN32 OR USE_VCPKG)
else()
set(QTDIR
""
- CACHE PATH "Path to Qt (e.g. C:/Qt/5.7/msvc2015_64)"
+ CACHE PATH "Path to Qt (e.g. C:/Qt/6.4.2/msvc2019_64)"
)
message(
- WARNING "QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. C:/Qt/5.7/msvc2015_64)"
+ WARNING
+ "QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. C:/Qt/6.4.2/msvc2019_64)"
)
endif()
endif()
@@ -191,6 +203,9 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${FLAG}")
endif()
endforeach()
+
+ # Reduce compiler I/O by using pipes between stages instead of temp files
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
else()
# other: osx/llvm, bsd/llvm
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
@@ -199,6 +214,9 @@ else()
else()
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra")
endif()
+
+ # Reduce compiler I/O by using pipes between stages instead of temp files
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
endif()
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning
@@ -246,11 +264,6 @@ if(WIN32)
find_package(OpenSSL REQUIRED)
if(OPENSSL_FOUND)
include_directories(${OPENSSL_INCLUDE_DIRS})
- else()
- message(
- WARNING
- "Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime."
- )
endif()
endif()
@@ -288,11 +301,7 @@ if(UNIX)
if(CPACK_GENERATOR STREQUAL "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_MAIN_COMPONENT "cockatrice")
- if(Qt6_FOUND)
- set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia, qt6-qtimageformats")
- elseif(Qt5_FOUND)
- set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt5-qttools, qt5-qtsvg, qt5-qtmultimedia")
- endif()
+ set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qtimageformats, qt6-qtmultimedia, qt6-qtsvg, qt6-qttools")
set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games")
set(CPACK_RPM_PACKAGE_URL "http://github.com/Cockatrice/Cockatrice")
# stop directories from making package conflicts
@@ -310,12 +319,8 @@ if(UNIX)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
- if(Qt6_FOUND)
- set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins")
- set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
- elseif(Qt5_FOUND)
- set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins, libqt5svg5")
- endif()
+ set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-image-formats-plugins, qt6-qpa-plugins")
+ set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
endif()
endif()
elseif(WIN32)
diff --git a/Dockerfile b/Dockerfile
index 7c5c773c9..7d3deb5fb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,35 +3,47 @@ FROM ubuntu:26.04 AS build
ARG DEBIAN_FRONTEND=noninteractive
-RUN apt-get update && apt-get install -y --no-install-recommends \
- build-essential \
- cmake \
- file \
- g++ \
- git \
- libmariadb-dev-compat \
- libprotobuf-dev \
- libqt6sql6-mysql \
- qt6-websockets-dev \
- protobuf-compiler \
- qt6-tools-dev \
- qt6-tools-dev-tools
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ build-essential \
+ cmake \
+ ninja-build \
+ file \
+ g++ \
+ git \
+ libmariadb-dev-compat \
+ libprotobuf-dev \
+ libqt6sql6-mysql \
+ libssl-dev \
+ qt6-websockets-dev \
+ protobuf-compiler \
+ qt6-tools-dev \
+ qt6-tools-dev-tools
WORKDIR /src
COPY . .
-RUN mkdir build && cd build && \
- cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 && \
- make -j$(nproc) && \
- make install
+RUN cmake \
+ -S . \
+ -B build \
+ -G Ninja \
+ -DWITH_CLIENT=0 \
+ -DWITH_ORACLE=0 \
+ -DWITH_SERVER=1 \
+ && cmake --build build \
+ && cmake --install build
# -------- Runtime Stage (clean) --------
FROM ubuntu:26.04
-RUN apt-get update && apt-get install -y --no-install-recommends \
- libprotobuf32t64 \
- libqt6sql6-mysql \
- libqt6websockets6 \
+ARG DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ libprotobuf32t64 \
+ libqt6sql6-mysql \
+ libqt6websockets6 \
+ libssl3 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
diff --git a/Doxyfile b/Doxyfile
index fc96b5f22..9b05682cc 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -349,7 +349,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
#
# Note see also the list of default file extension mappings.
-EXTENSION_MAPPING =
+EXTENSION_MAPPING = proto=C++
# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
@@ -1086,7 +1086,8 @@ FILE_PATTERNS = *.cc \
*.h++ \
*.markdown \
*.md \
- *.dox
+ *.dox \
+ *.proto
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@@ -1103,6 +1104,7 @@ RECURSIVE = YES
EXCLUDE = build/ \
cmake/ \
+ cmake-build-debug/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
vcpkg/
@@ -1195,7 +1197,7 @@ INPUT_FILTER =
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by Doxygen.
-FILTER_PATTERNS =
+FILTER_PATTERNS = "*.proto=python doc/doxygen/filters/proto2cpp.py"
# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER) will also be used to filter the input files that are used for
diff --git a/README.md b/README.md
index 9a27601cd..5935bb540 100644
--- a/README.md
+++ b/README.md
@@ -149,16 +149,15 @@ You can then
The following flags (with their non-default values) can be passed to `cmake`:
-| Flag | Description |
-| --- | --- |
-| `-DWITH_SERVER=1` | Build Servatrice server |
-| `-DWITH_CLIENT=0` | Don't build Cockatrice client |
-| `-DWITH_ORACLE=0` | Don't build Oracle card database tool |
-| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode
Enables extra logging output, debug symbols, and much more verbose compiler warnings |
-| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode |
-| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code
**Note:** `make clean` will remove the .ts files |
-| `-DTEST=1` | Enable regression tests
**Note:** `make test` to run tests, *googletest* will be downloaded if not available |
-| `-DFORCE_USE_QT5=1` | Skip looking for Qt6 before trying to find Qt5 |
+| Flag | Description |
+| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| `-DWITH_SERVER=1` | Build Servatrice server |
+| `-DWITH_CLIENT=0` | Don't build Cockatrice client |
+| `-DWITH_ORACLE=0` | Don't build Oracle card database tool |
+| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode
Enables extra logging output, debug symbols, and much more verbose compiler warnings |
+| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode |
+| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code
**Note:** `make clean` will remove the .ts files |
+| `-DTEST=1` | Enable regression tests
**Note:** `make test` to run tests, *googletest* will be downloaded if not available |
# Run
diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake
index 485affe52..971c9094d 100644
--- a/cmake/FindQtRuntime.cmake
+++ b/cmake/FindQtRuntime.cmake
@@ -1,8 +1,7 @@
# Find a compatible Qt version
-# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, FORCE_USE_QT5
+# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE
# Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system
-# Optional Input: QT5_DIR -- Hint as to where Qt5 lives on the system
-# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6
+# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt6
# Output: SERVATRICE_QT_MODULES
# Output: COCKATRICE_QT_MODULES
# Output: ORACLE_QT_MODULES
@@ -19,14 +18,17 @@ if(WITH_CLIENT)
Multimedia
Network
PrintSupport
+ ShaderTools
Svg
WebSockets
Widgets
Xml
+ Quick
+ QuickWidgets
)
endif()
if(WITH_ORACLE)
- set(_ORACLE_NEEDED Concurrent Network Svg Widgets)
+ set(_ORACLE_NEEDED Concurrent Network Svg Widgets Xml)
endif()
if(TEST)
# Union of Qt modules required across all test targets (independent of application targets).
@@ -39,69 +41,37 @@ set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_CO
)
list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
-if(NOT FORCE_USE_QT5)
- # Linguist is now a component in Qt6 instead of an external package
- find_package(
- Qt6 6.4.2
- COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
- QUIET HINTS ${Qt6_DIR}
- )
+# Linguist is now a component in Qt6 instead of an external package
+find_package(
+ Qt6 6.4.2
+ COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
+ QUIET HINTS ${Qt6_DIR}
+)
+if(NOT Qt6_FOUND)
+ message(FATAL_ERROR "No suitable version of Qt was found")
endif()
-if(Qt6_FOUND)
- set(COCKATRICE_QT_VERSION_NAME Qt6)
+set(COCKATRICE_QT_VERSION_NAME Qt6)
- list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
- if(QT6_LRELEASE_INDEX EQUAL -1)
- message(WARNING "Qt6 lrelease not found.")
- endif()
-
- list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
- if(QT6_LUPDATE_INDEX EQUAL -1)
- message(WARNING "Qt6 lupdate not found.")
- endif()
-else()
- find_package(
- Qt5 5.15.2
- COMPONENTS ${REQUIRED_QT_COMPONENTS}
- QUIET HINTS ${Qt5_DIR}
- )
- if(Qt5_FOUND)
- set(COCKATRICE_QT_VERSION_NAME Qt5)
- else()
- message(FATAL_ERROR "No suitable version of Qt was found")
- endif()
-
- # Qt5 Linguist is in a separate package
- find_package(Qt5LinguistTools QUIET)
- if(Qt5LinguistTools_FOUND)
- if(NOT Qt5_LRELEASE_EXECUTABLE)
- message(WARNING "Qt5 lrelease not found.")
- endif()
- if(NOT Qt5_LUPDATE_EXECUTABLE)
- message(WARNING "Qt5 lupdate not found.")
- endif()
- else()
- message(WARNING "Linguist Tools not found, cannot handle translations")
- endif()
+list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
+if(QT6_LRELEASE_INDEX EQUAL -1)
+ message(WARNING "Qt6 lrelease not found.")
endif()
-if(Qt5_POSITION_INDEPENDENT_CODE OR Qt6_FOUND)
- set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
+if(QT6_LUPDATE_INDEX EQUAL -1)
+ message(WARNING "Qt6 lupdate not found.")
endif()
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
# Establish Qt Plugins directory & Library directories
get_target_property(QT_LIBRARY_DIR ${COCKATRICE_QT_VERSION_NAME}::Core LOCATION)
get_filename_component(QT_LIBRARY_DIR ${QT_LIBRARY_DIR} DIRECTORY)
-if(Qt6_FOUND)
- get_filename_component(QT_PLUGINS_DIR "${Qt6Core_DIR}/../../../${QT6_INSTALL_PLUGINS}" ABSOLUTE)
- get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/../../.." ABSOLUTE)
- if(UNIX AND APPLE)
- # Mac needs a bit more help finding all necessary components
- list(APPEND QT_LIBRARY_DIR "/usr/local/lib")
- endif()
-elseif(Qt5_FOUND)
- get_filename_component(QT_PLUGINS_DIR "${Qt5Core_DIR}/../../../plugins" ABSOLUTE)
- get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/.." ABSOLUTE)
+get_filename_component(QT_PLUGINS_DIR "${Qt6Core_DIR}/../../../${QT6_INSTALL_PLUGINS}" ABSOLUTE)
+get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/../../.." ABSOLUTE)
+if(UNIX AND APPLE)
+ # Mac needs a bit more help finding all necessary components
+ list(APPEND QT_LIBRARY_DIR "/usr/local/lib")
endif()
message(DEBUG "QT_PLUGINS_DIR = ${QT_PLUGINS_DIR}")
message(DEBUG "QT_LIBRARY_DIR = ${QT_LIBRARY_DIR}")
diff --git a/cmake/Info.plist b/cmake/Info.plist
index 614d82509..7f01befcb 100644
--- a/cmake/Info.plist
+++ b/cmake/Info.plist
@@ -1,38 +1,118 @@
-
+
+
+
+
+
+
CFBundleDevelopmentRegion
English
+
CFBundleExecutable
${MACOSX_BUNDLE_EXECUTABLE_NAME}
+
CFBundleGetInfoString
${MACOSX_BUNDLE_INFO_STRING}
+
CFBundleIconFile
${MACOSX_BUNDLE_ICON_FILE}
+
CFBundleIdentifier
${MACOSX_BUNDLE_GUI_IDENTIFIER}
+
CFBundleInfoDictionaryVersion
6.0
+
CFBundleLongVersionString
${MACOSX_BUNDLE_LONG_VERSION_STRING}
+
CFBundleName
${MACOSX_BUNDLE_BUNDLE_NAME}
+
CFBundlePackageType
APPL
+
CFBundleShortVersionString
${MACOSX_BUNDLE_SHORT_VERSION_STRING}
+
CFBundleSignature
????
+
CFBundleVersion
${MACOSX_BUNDLE_BUNDLE_VERSION}
- CSResourcesFileMapped
-
- LSRequiresCarbon
-
+
NSHumanReadableCopyright
${MACOSX_BUNDLE_COPYRIGHT}
+
NSHighResolutionCapable
+
+
+
+
+
+ UTExportedTypeDeclarations
+
+
+ UTTypeIdentifier
+ org.cockatrice.deck
+
+ UTTypeDescription
+ Cockatrice Deck
+
+ UTTypeConformsTo
+
+ public.data
+
+
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ cod
+
+
+
+
+
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeName
+ Cockatrice Deck
+
+ CFBundleTypeRole
+ Editor
+
+ LSHandlerRank
+ Default
+
+ LSItemContentTypes
+
+ org.cockatrice.deck
+
+
+
+
+
+
+
+
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ Cockatrice URL Scheme
+
+ CFBundleURLSchemes
+
+ cockatrice
+
+
+
+
diff --git a/cmake/pch/qtcore_pch.h b/cmake/pch/qtcore_pch.h
new file mode 100644
index 000000000..cc3dd12ee
--- /dev/null
+++ b/cmake/pch/qtcore_pch.h
@@ -0,0 +1,24 @@
+/** @file qtcore_pch.h
+ * @brief Precompiled header for all Qt targets (Qt Core only).
+ *
+ * Safe for every target that links Qt Core, including the headless
+ * Servatrice binary. Keep this header free of any widget/gui types.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
diff --git a/cmake/pch/qtwidgets_pch.h b/cmake/pch/qtwidgets_pch.h
new file mode 100644
index 000000000..2c63f450e
--- /dev/null
+++ b/cmake/pch/qtwidgets_pch.h
@@ -0,0 +1,30 @@
+/** @file qtwidgets_pch.h
+ * @brief Precompiled header for GUI targets (Cockatrice client, Oracle).
+ *
+ * Includes the Qt Core precompiled header plus the heavy Gui, Widgets and
+ * Network layers that virtually every client translation unit re-parses.
+ * Do not use on Servatrice (headless, QT_DONT_USE_QTGUI).
+ */
+
+#include "qtcore_pch.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index 60ba06593..4263fc6e2 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -15,6 +15,9 @@ set(cockatrice_SOURCES
src/client/network/update/client/client_update_checker.cpp
src/client/network/update/client/release_channel.cpp
src/client/network/update/card_spoiler/spoiler_background_updater.cpp
+ src/client/latency_graph_widget.cpp
+ src/client/latency_status_widget.cpp
+ src/client/lag_monitor.cpp
src/client/sound_engine.cpp
src/client/settings/cache_settings.cpp
src/client/settings/card_counter_settings.cpp
@@ -36,13 +39,16 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp
src/interface/widgets/dialogs/dlg_forgot_password_request.cpp
src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp
+ src/interface/widgets/dialogs/dlg_invite_to_game.cpp
src/interface/widgets/dialogs/dlg_load_deck.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
src/interface/widgets/dialogs/dlg_load_remote_deck.cpp
src/interface/widgets/dialogs/dlg_local_game_options.cpp
src/interface/widgets/dialogs/dlg_manage_sets.cpp
+ src/interface/widgets/dialogs/dlg_my_reports.cpp
src/interface/widgets/dialogs/dlg_register.cpp
+ src/interface/widgets/dialogs/dlg_report_user.cpp
src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp
src/interface/widgets/dialogs/dlg_settings.cpp
src/interface/widgets/dialogs/dlg_startup_card_check.cpp
@@ -97,7 +103,9 @@ set(cockatrice_SOURCES
src/game_graphics/player/menu/rfg_menu.cpp
src/game_graphics/player/menu/say_menu.cpp
src/game_graphics/player/menu/sideboard_menu.cpp
+ src/game_graphics/player/menu/tally_menu.cpp
src/game_graphics/player/menu/utility_menu.cpp
+ src/game_graphics/tally/stats_tally.cpp
src/game_graphics/tally/subtype_tally.cpp
src/game_graphics/tally/tally.cpp
src/game/player/player_actions.cpp
@@ -133,9 +141,19 @@ set(cockatrice_SOURCES
src/interface/card_picture_loader/card_picture_loader_worker.cpp
src/interface/card_picture_loader/card_picture_loader_worker_work.cpp
src/interface/card_picture_loader/card_picture_to_load.cpp
+ src/interface/intents/intent.cpp
+ src/interface/intents/intent.h
+ src/interface/intents/intent_open_local_deck.cpp
+ src/interface/intents/intent_open_local_deck.h
+ src/interface/intents/intent_wait_for_database_load.cpp
+ src/interface/intents/intent_wait_for_database_load.h
src/interface/layouts/flow_layout.cpp
src/interface/layouts/overlap_layout.cpp
+ src/interface/widgets/utility/card_completer_delegate.cpp
+ src/interface/widgets/utility/card_completer_styler.cpp
+ src/interface/widgets/utility/completer_utils.cpp
src/interface/widgets/utility/line_edit_completer.cpp
+ src/interface/widgets/utility/reversed_completer_model.cpp
src/interface/pixel_map_generator.cpp
src/interface/theme_config.cpp
src/interface/theme_manager.cpp
@@ -147,6 +165,8 @@ set(cockatrice_SOURCES
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
+ src/interface/widgets/cards/art_crop_attribution.cpp
+ src/interface/widgets/cards/card_art_utils.cpp
src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp
src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp
src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp
@@ -195,6 +215,7 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
src/interface/widgets/deck_editor/deck_state_manager.cpp
+ src/interface/widgets/deck_editor/deck_zone_dialog.cpp
src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp
src/interface/widgets/general/background_sources.cpp
src/interface/widgets/general/display/background_plate_widget.cpp
@@ -211,6 +232,7 @@ set(cockatrice_SOURCES
src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp
src/interface/widgets/general/display/charts/pies/color_pie.cpp
src/interface/widgets/general/home_styled_button.cpp
+ src/interface/widgets/general/home_tab_button_color.h
src/interface/widgets/general/home_widget.cpp
src/interface/widgets/general/layout_containers/flow_widget.cpp
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
@@ -226,12 +248,19 @@ set(cockatrice_SOURCES
src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
+ src/interface/widgets/playmat/playmat_collection_dialog.cpp
+ src/interface/widgets/playmat/playmat_collection_dialog.h
+ src/interface/widgets/playmat/playmat_preview_widget.cpp
+ src/interface/widgets/playmat/playmat_settings_dialog.cpp
src/interface/widgets/quick_settings/settings_button_widget.cpp
src/interface/widgets/quick_settings/settings_popup_widget.cpp
src/interface/widgets/replay/replay_manager.cpp
+ src/interface/widgets/replay/replay_quick_settings_widget.cpp
src/interface/widgets/replay/replay_timeline_widget.cpp
+ src/interface/widgets/replay/replay_widget.cpp
src/interface/widgets/server/chat_view/chat_view.cpp
src/interface/widgets/server/game_filter_configs.cpp
+ src/interface/widgets/server/game_link.cpp
src/interface/widgets/server/game_selector.cpp
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp
@@ -244,19 +273,26 @@ set(cockatrice_SOURCES
src/interface/widgets/server/user/user_context_menu.cpp
src/interface/widgets/server/user/user_info_box.cpp
src/interface/widgets/server/user/user_info_connection.cpp
+ src/interface/widgets/server/user/user_list_dialog.cpp
src/interface/widgets/server/user/user_list_manager.cpp
src/interface/widgets/server/user/user_list_painter.cpp
+ src/interface/widgets/server/user/user_list_panel_widget.cpp
src/interface/widgets/server/user/user_list_widget.cpp
+ src/interface/widgets/settings_page/abstract_settings_page.cpp
src/interface/widgets/settings_page/appearance_settings_page.cpp
src/interface/widgets/settings_page/deck_editor_settings_page.cpp
src/interface/widgets/settings_page/general_settings_page.cpp
src/interface/widgets/settings_page/messages_settings_page.cpp
+ src/interface/widgets/settings_page/settings_search_delegate.cpp
+ src/interface/widgets/settings_page/settings_search_model.cpp
src/interface/widgets/settings_page/shortcut_settings_page.cpp
src/interface/widgets/settings_page/sound_settings_page.cpp
src/interface/widgets/settings_page/storage_settings_page.cpp
src/interface/widgets/settings_page/user_interface_settings_page.cpp
src/interface/widgets/utility/custom_line_edit.cpp
src/interface/widgets/utility/get_text_with_max.cpp
+ src/interface/widgets/utility/report_utils.cpp
+ src/interface/widgets/utility/report_utils.h
src/interface/widgets/utility/sequence_edit.cpp
src/interface/widgets/utility/visibility_change_listener.cpp
src/interface/widgets/utility/visibility_change_listener.h
@@ -282,13 +318,16 @@ set(cockatrice_SOURCES
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
+ src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
+ src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
src/interface/window_main.cpp
src/main.cpp
+ src/single_instance_manager.cpp
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
@@ -304,6 +343,13 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/api_response/card_in_deck_request.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_deck_request.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_card_result.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_variant_result.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/api_response/commander_spellbook_estimate_bracket_result.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.cpp
src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
@@ -336,10 +382,13 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/tab_card_art_rules.cpp
src/interface/widgets/tabs/tab_deck_editor.cpp
src/interface/widgets/tabs/tab_deck_storage.cpp
+ src/interface/widgets/tabs/tab_developer.cpp
src/interface/widgets/tabs/tab_game.cpp
src/interface/widgets/tabs/tab_home.cpp
src/interface/widgets/tabs/tab_logs.cpp
src/interface/widgets/tabs/tab_message.cpp
+ src/interface/widgets/tabs/tab_moderation.cpp
+ src/interface/widgets/tabs/tab_report.cpp
src/interface/widgets/tabs/tab_replays.cpp
src/interface/widgets/tabs/tab_room.cpp
src/interface/widgets/tabs/tab_server.cpp
@@ -350,12 +399,53 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/interface/key_signals.cpp
src/interface/logger.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp
+ src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp
+ src/interface/widgets/onboarding/banner_shader_config.h
+ src/interface/widgets/onboarding/brand_colors.h
+ src/interface/widgets/onboarding/first_run_wizard.cpp
+ src/interface/widgets/onboarding/first_run_wizard.h
+ src/interface/widgets/onboarding/first_run_wizard_page.cpp
+ src/interface/widgets/onboarding/first_run_wizard_page.h
+ src/interface/widgets/onboarding/pages/account_setup_page.cpp
+ src/interface/widgets/onboarding/pages/account_setup_page.h
+ src/interface/widgets/onboarding/pages/card_database_setup_page.cpp
+ src/interface/widgets/onboarding/pages/card_database_setup_page.h
+ src/interface/widgets/onboarding/pages/finish_page.cpp
+ src/interface/widgets/onboarding/pages/finish_page.h
+ src/interface/widgets/onboarding/pages/preferences_setup_page.cpp
+ src/interface/widgets/onboarding/pages/preferences_setup_page.h
+ src/interface/widgets/onboarding/pages/theme_setup_page.cpp
+ src/interface/widgets/onboarding/pages/theme_setup_page.h
+ src/interface/widgets/onboarding/pages/welcome_page.cpp
+ src/interface/widgets/onboarding/pages/welcome_page.h
+ src/interface/widgets/onboarding/shader_banner_widget.cpp
+ src/interface/widgets/onboarding/shader_banner_widget.h
+ src/interface/widgets/onboarding/step_indicator_widget.cpp
+ src/interface/widgets/onboarding/step_indicator_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
src/interface/widgets/utility/compact_push_button.cpp
src/interface/widgets/utility/compact_push_button.h
+ src/single_instance_manager.h
+ src/client/url_scheme_event_filter.h
+ src/interface/intents/intent_connect_to_server.cpp
+ src/interface/intents/intent_connect_to_server.h
+ src/interface/intents/intent_disconnect_from_server.cpp
+ src/interface/intents/intent_disconnect_from_server.h
+ src/interface/intents/intent_join_server_game.cpp
+ src/interface/intents/intent_join_server_game.h
+ src/interface/intents/intent_join_server_room.cpp
+ src/interface/intents/intent_join_server_room.h
+ src/interface/intents/intent_login.cpp
+ src/interface/intents/intent_login.h
+ src/interface/intents/intent_open_server_room_by_name.cpp
+ src/interface/intents/intent_open_server_room_by_name.h
+ src/interface/intents/url_parser.cpp
+ src/interface/intents/url_parser.h
src/interface/widgets/server/user/user_info_popup.cpp
src/interface/widgets/server/user/user_info_popup.h
)
@@ -399,11 +489,7 @@ if(APPLE)
set(cockatrice_SOURCES ${cockatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns)
endif(APPLE)
-if(Qt6_FOUND)
- qt6_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
-elseif(Qt5_FOUND)
- qt5_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
-endif()
+qt6_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
# Declare path variables
set(ICONDIR
@@ -415,71 +501,62 @@ set(DESKTOPDIR
CACHE STRING "desktop file destination"
)
+set(MIMEDIR
+ share/mime/packages
+ CACHE STRING "mime file destination"
+)
+
set(COCKATRICE_MAC_QM_INSTALL_DIR "cockatrice.app/Contents/Resources/translations")
set(COCKATRICE_UNIX_QM_INSTALL_DIR "share/cockatrice/translations")
set(COCKATRICE_WIN32_QM_INSTALL_DIR "translations")
-if(Qt6_FOUND)
- qt6_add_executable(
- cockatrice
- WIN32
- MACOSX_BUNDLE
- ${cockatrice_SOURCES}
- ${cockatrice_RESOURCES_RCC}
- ${cockatrice_MOC_SRCS}
- MANUAL_FINALIZATION
- )
-elseif(Qt5_FOUND)
- # Qt5 Translations need to be linked at executable creation time
- if(Qt5LinguistTools_FOUND)
- if(UPDATE_TRANSLATIONS)
- qt5_create_translation(cockatrice_QM ${translate_SRCS} ${cockatrice_TS})
- else()
- qt5_add_translation(cockatrice_QM ${cockatrice_TS})
- endif()
- endif()
- add_executable(
- cockatrice WIN32 MACOSX_BUNDLE ${cockatrice_MOC_SRCS} ${cockatrice_QM} ${cockatrice_RESOURCES_RCC}
- ${cockatrice_SOURCES}
- )
- if(UNIX)
- if(APPLE)
- install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_MAC_QM_INSTALL_DIR})
- else()
- install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_UNIX_QM_INSTALL_DIR})
- endif()
- elseif(WIN32)
- install(FILES ${cockatrice_QM} DESTINATION ${COCKATRICE_WIN32_QM_INSTALL_DIR})
- endif()
-endif()
+qt6_add_executable(
+ cockatrice
+ WIN32
+ MACOSX_BUNDLE
+ ${cockatrice_SOURCES}
+ ${cockatrice_RESOURCES_RCC}
+ ${cockatrice_MOC_SRCS}
+ MANUAL_FINALIZATION
+)
-if(Qt5_FOUND)
- target_link_libraries(
- cockatrice
- libcockatrice_card
- libcockatrice_deck_list
- libcockatrice_filters
- libcockatrice_utility
- libcockatrice_network
- libcockatrice_models
- libcockatrice_rng
- libcockatrice_settings
- ${COCKATRICE_QT_MODULES}
- )
-else()
- target_link_libraries(
- cockatrice
- PUBLIC libcockatrice_card
- libcockatrice_deck_list
- libcockatrice_filters
- libcockatrice_utility
- libcockatrice_network
- libcockatrice_models
- libcockatrice_rng
- libcockatrice_settings
- ${COCKATRICE_QT_MODULES}
- )
-endif()
+target_precompile_headers(cockatrice PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtwidgets_pch.h")
+
+qt6_add_shaders(
+ cockatrice
+ "onboarding_shaders"
+ PREFIX
+ "/onboarding/shaders"
+ BASE
+ "src/interface/widgets/onboarding/shaders"
+ FILES
+ src/interface/widgets/onboarding/shaders/brand_banner.frag
+ src/interface/widgets/onboarding/shaders/brand_plate.frag
+)
+
+qt6_add_resources(
+ cockatrice
+ "onboarding_qml"
+ PREFIX
+ "/onboarding/qml"
+ BASE
+ "src/interface/widgets/onboarding/qml"
+ FILES
+ src/interface/widgets/onboarding/qml/BrandBanner.qml
+)
+
+target_link_libraries(
+ cockatrice
+ PUBLIC libcockatrice_card
+ libcockatrice_deck_list
+ libcockatrice_filters
+ libcockatrice_utility
+ libcockatrice_network
+ libcockatrice_models
+ libcockatrice_rng
+ libcockatrice_settings
+ ${COCKATRICE_QT_MODULES}
+)
if(UNIX)
if(APPLE)
@@ -499,6 +576,23 @@ if(UNIX)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.png DESTINATION ${ICONDIR}/hicolor/48x48/apps)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.svg DESTINATION ${ICONDIR}/hicolor/scalable/apps)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice.desktop DESTINATION ${DESKTOPDIR})
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice-cod.xml DESTINATION ${MIMEDIR})
+
+ # Refresh the freedesktop databases so the file associations and scheme
+ # handler register without requiring the user to run them manually. The
+ # tools may be missing on minimal systems; that is fine, packaging systems
+ # usually refresh these databases through their own triggers.
+ find_program(UPDATE_MIME_DATABASE update-mime-database)
+ if(UPDATE_MIME_DATABASE)
+ install(CODE "execute_process(COMMAND \"${UPDATE_MIME_DATABASE}\" \"${CMAKE_INSTALL_PREFIX}/share/mime\")")
+ endif()
+
+ find_program(UPDATE_DESKTOP_DATABASE update-desktop-database)
+ if(UPDATE_DESKTOP_DATABASE)
+ install(
+ CODE "execute_process(COMMAND \"${UPDATE_DESKTOP_DATABASE}\" \"${CMAKE_INSTALL_PREFIX}/share/applications\")"
+ )
+ endif()
endif()
elseif(WIN32)
install(TARGETS cockatrice RUNTIME DESTINATION ./)
@@ -509,7 +603,7 @@ if(APPLE)
set(plugin_dest_dir cockatrice.app/Contents/Plugins)
set(qtconf_dest_dir cockatrice.app/Contents/Resources)
- # Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6), platforms, printsupport (Qt5), styles, tls (Qt6)
+ # Qt plugins: audio, iconengines, imageformats, multimedia, platforms, printsupport, styles, tls
install(
DIRECTORY "${QT_PLUGINS_DIR}/"
DESTINATION ${plugin_dest_dir}
@@ -576,7 +670,7 @@ if(WIN32)
PATTERN "*.ini"
)
- # Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6) platforms, printsupport (Qt5), styles, tls (Qt6)
+ # Qt plugins: audio, iconengines, imageformats, multimedia, platforms, printsupport, styles, tls
install(
DIRECTORY "${QT_PLUGINS_DIR}/"
DESTINATION ${plugin_dest_dir}
@@ -632,7 +726,7 @@ Data = Resources\")
endif()
endif()
-if(Qt6_FOUND AND Qt6LinguistTools_FOUND)
+if(Qt6LinguistTools_FOUND)
#Qt6 Translations happen after the executable is built up
if(UPDATE_TRANSLATIONS)
qt6_add_translations(
@@ -659,6 +753,4 @@ if(Qt6_FOUND AND Qt6LinguistTools_FOUND)
endif()
endif()
-if(Qt6_FOUND)
- qt6_finalize_target(cockatrice)
-endif()
+qt6_finalize_target(cockatrice)
diff --git a/cockatrice/cockatrice-cod.xml b/cockatrice/cockatrice-cod.xml
new file mode 100644
index 000000000..1a0199433
--- /dev/null
+++ b/cockatrice/cockatrice-cod.xml
@@ -0,0 +1,7 @@
+
+
+
+ Cockatrice Deck File
+
+
+
diff --git a/cockatrice/cockatrice.desktop b/cockatrice/cockatrice.desktop
index 092d84ef5..4b15fa9c1 100644
--- a/cockatrice/cockatrice.desktop
+++ b/cockatrice/cockatrice.desktop
@@ -3,6 +3,8 @@
Version=1.0
Type=Application
Name=Cockatrice
-Exec=cockatrice
+Exec=cockatrice %U
Icon=cockatrice
Categories=Game;CardGame;
+MimeType=application/x-cockatrice;
+X-Scheme-Handler/cockatrice=true
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index 9c34929b7..14cf15b2f 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -2,6 +2,7 @@
resources/cardback.svg
resources/cockatrice.svg
+ resources/cockatrice-logo-white.svg
resources/hand.svg
resources/hr.jpg
@@ -62,6 +63,8 @@
resources/icons/mana/W.svg
resources/backgrounds/home.png
+ resources/backgrounds/home-dark.png
+ resources/backgrounds/home-light.png
resources/backgrounds/card_triplet.svg
resources/backgrounds/placeholder_printing_selector.svg
@@ -362,6 +365,8 @@
resources/usericons/pawn_single.svg
resources/usericons/pawn_double.svg
+ resources/usericons/pawn_dev_single.svg
+ resources/usericons/pawn_dev_double.svg
resources/usericons/pawn_donator_single.svg
resources/usericons/pawn_donator_double.svg
resources/usericons/pawn_judge_single.svg
diff --git a/cockatrice/resources/backgrounds/home-dark.png b/cockatrice/resources/backgrounds/home-dark.png
new file mode 100644
index 000000000..68f48e2c2
Binary files /dev/null and b/cockatrice/resources/backgrounds/home-dark.png differ
diff --git a/cockatrice/resources/backgrounds/home-light.png b/cockatrice/resources/backgrounds/home-light.png
new file mode 100644
index 000000000..eaaaba932
Binary files /dev/null and b/cockatrice/resources/backgrounds/home-light.png differ
diff --git a/cockatrice/resources/backgrounds/home.png b/cockatrice/resources/backgrounds/home.png
index 68f48e2c2..eaaaba932 100644
Binary files a/cockatrice/resources/backgrounds/home.png and b/cockatrice/resources/backgrounds/home.png differ
diff --git a/cockatrice/resources/cockatrice-logo-white.svg b/cockatrice/resources/cockatrice-logo-white.svg
new file mode 100644
index 000000000..b3b31077f
--- /dev/null
+++ b/cockatrice/resources/cockatrice-logo-white.svg
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/cockatrice/resources/cockatrice.svg b/cockatrice/resources/cockatrice.svg
index d2e22da31..89ba62dcf 100644
--- a/cockatrice/resources/cockatrice.svg
+++ b/cockatrice/resources/cockatrice.svg
@@ -2,20 +2,20 @@
diff --git a/cockatrice/resources/countries/cn.svg b/cockatrice/resources/countries/cn.svg
index f510cf049..45b608127 100644
--- a/cockatrice/resources/countries/cn.svg
+++ b/cockatrice/resources/countries/cn.svg
@@ -52,8 +52,7 @@
id="defs8">
-
+
diff --git a/cockatrice/resources/help/search.md b/cockatrice/resources/help/search.md
index 0c8bdb450..fd0a12507 100644
--- a/cockatrice/resources/help/search.md
+++ b/cockatrice/resources/help/search.md
@@ -52,6 +52,7 @@ In this list of examples below, each entry has an explanation and can be clicked
Edition:
[set:lea](#set:lea) (Cards that appear in Alpha, which has the set code LEA)
[e:lea OR e:leb](#e:lea OR e:leb) (Cards that appear in Alpha or Beta)
+[e<8ED](#e<8ED) (Cards that appear before 8th edition)
Negate:
[c:wu -c:m](#c:wu -c:m) (Any card that is white or blue, but not multicolored)
diff --git a/cockatrice/resources/usericons/pawn_dev_double.svg b/cockatrice/resources/usericons/pawn_dev_double.svg
new file mode 100644
index 000000000..57ed5c2da
--- /dev/null
+++ b/cockatrice/resources/usericons/pawn_dev_double.svg
@@ -0,0 +1,343 @@
+
+
+
+
diff --git a/cockatrice/resources/usericons/pawn_dev_single.svg b/cockatrice/resources/usericons/pawn_dev_single.svg
new file mode 100644
index 000000000..f7c4e7018
--- /dev/null
+++ b/cockatrice/resources/usericons/pawn_dev_single.svg
@@ -0,0 +1,211 @@
+
+
+
+
diff --git a/cockatrice/src/client/lag_monitor.cpp b/cockatrice/src/client/lag_monitor.cpp
new file mode 100644
index 000000000..383d64644
--- /dev/null
+++ b/cockatrice/src/client/lag_monitor.cpp
@@ -0,0 +1,63 @@
+#include "lag_monitor.h"
+
+#include
+#include
+#include
+
+LagMonitor::LagMonitor(QObject *parent) : QObject(parent)
+{
+ qApp->installEventFilter(this);
+
+ timer = new QTimer(this);
+ timer->setInterval(TICK_INTERVAL_MS);
+ connect(timer, &QTimer::timeout, this, &LagMonitor::checkTick);
+ tickClock.start();
+ timer->start();
+}
+
+QList LagMonitor::recentStalls() const
+{
+ return stalls;
+}
+
+void LagMonitor::clearStalls()
+{
+ stalls.clear();
+}
+
+bool LagMonitor::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::ApplicationStateChange) {
+ // The transition may span a suspend or an arbitrary unfocused period;
+ // discard the gap so it cannot be mistaken for a stall.
+ tickClock.restart();
+ }
+ return QObject::eventFilter(obj, event);
+}
+
+void LagMonitor::checkTick()
+{
+ recordGap(tickClock.restart());
+}
+
+void LagMonitor::recordGap(qint64 gapMs)
+{
+ if (gapMs <= STALL_THRESHOLD_MS) {
+ return;
+ }
+
+ if (gapMs > MAX_PLAUSIBLE_STALL_MS) {
+ qCDebug(LagMonitorLog, "Ignoring implausible %lld ms gap (likely suspend)", static_cast(gapMs));
+ return;
+ }
+
+ const StallRecord record{.timestampMsSinceEpoch = QDateTime::currentMSecsSinceEpoch(), .durationMs = gapMs};
+
+ stalls.append(record);
+ while (stalls.size() > MAX_RECORDED_STALLS) {
+ stalls.removeFirst();
+ }
+
+ qCWarning(LagMonitorLog, "Event loop stalled for %lld ms (threshold: %d ms)", static_cast(gapMs),
+ STALL_THRESHOLD_MS);
+}
diff --git a/cockatrice/src/client/lag_monitor.h b/cockatrice/src/client/lag_monitor.h
new file mode 100644
index 000000000..9fdf6b283
--- /dev/null
+++ b/cockatrice/src/client/lag_monitor.h
@@ -0,0 +1,87 @@
+/**
+ * @file lag_monitor.h
+ * @ingroup Client
+ */
+
+#ifndef LAG_MONITOR_H
+#define LAG_MONITOR_H
+
+#include
+#include
+#include
+#include
+#include
+
+inline Q_LOGGING_CATEGORY(LagMonitorLog, "lag_monitor");
+
+class QEvent;
+class QTimer;
+
+/**
+ * @brief Detects main-thread event loop stalls ("UI freezes") from the inside.
+ *
+ * A timer is expected to fire every TICK_INTERVAL_MS of wall time. When the
+ * observed gap greatly exceeds that interval, some other task blocked the
+ * event loop for roughly the overshooting duration. This is what separates
+ * "my client froze" from "the network is lagging" in user reports.
+ *
+ * Gaps that span an application state change (suspend, minimize, focus
+ * loss) are discarded, and implausibly huge gaps are dropped, so operating
+ * system power events do not fabricate stalls. This handling is load-bearing
+ * on Windows, where the monotonic clock used by Qt counts sleep time.
+ *
+ * Healthy operation costs one timer wakeup per tick and two integer
+ * comparisons. Allocations happen only when a stall is actually recorded.
+ */
+class LagMonitor : public QObject
+{
+ Q_OBJECT
+
+public:
+ struct StallRecord
+ {
+ qint64 timestampMsSinceEpoch = 0; ///< when the stalled period ended
+ qint64 durationMs = 0; ///< approximate length of the freeze; measured tick to tick, so it can exceed the true
+ ///< stall by up to TICK_INTERVAL_MS
+ };
+
+ static constexpr int TICK_INTERVAL_MS = 500;
+ static constexpr int STALL_THRESHOLD_MS = 2000;
+ static constexpr int MAX_RECORDED_STALLS = 32;
+
+ /// Gaps beyond this are treated as suspend artifacts rather than stalls.
+ static constexpr qint64 MAX_PLAUSIBLE_STALL_MS = 600000;
+
+ explicit LagMonitor(QObject *parent = nullptr);
+
+ /**
+ * @brief Stalls recorded during this session, oldest first.
+ *
+ * Intended consumers are log output and the diagnostics export. The list
+ * holds at most MAX_RECORDED_STALLS entries.
+ */
+ QList recentStalls() const;
+
+ void clearStalls();
+
+ /**
+ * @brief Feeds a measured tick-to-tick gap through the detection logic.
+ *
+ * Split out of checkTick so threshold, plausibility, and trim behavior
+ * stay unit-testable without real timing.
+ */
+ void recordGap(qint64 gapMs);
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *event) override;
+
+private slots:
+ void checkTick();
+
+private:
+ QTimer *timer;
+ QElapsedTimer tickClock; ///< monotonic clock, so wall clock steps do not fabricate stalls
+ QList stalls;
+};
+
+#endif
diff --git a/cockatrice/src/client/latency_graph_widget.cpp b/cockatrice/src/client/latency_graph_widget.cpp
new file mode 100644
index 000000000..46d6ccdd3
--- /dev/null
+++ b/cockatrice/src/client/latency_graph_widget.cpp
@@ -0,0 +1,51 @@
+/**
+ * @file latency_graph_widget.cpp
+ * @ingroup Client
+ */
+
+#include "latency_graph_widget.h"
+
+#include
+
+LatencyGraphWidget::LatencyGraphWidget(QWidget *parent) : QWidget(parent)
+{
+}
+
+void LatencyGraphWidget::setSamples(const QList &samplesMs)
+{
+ samples = samplesMs;
+ update();
+}
+
+void LatencyGraphWidget::paintEvent(QPaintEvent * /* event */)
+{
+ if (samples.isEmpty()) {
+ return;
+ }
+
+ QPainter painter(this);
+
+ // Heights are relative to the window's own worst sample (floored at
+ // MinScaleMs) so the shape of the variance stays readable even when every
+ // value is small.
+ qint64 heightScaleMs = MinScaleMs;
+ for (int sample : samples) {
+ heightScaleMs = qMax(heightScaleMs, static_cast(sample));
+ }
+
+ const qreal widthPerBar = static_cast(width()) / samples.size();
+ for (int i = 0; i < samples.size(); ++i) {
+ const qreal heightRatio = qBound(0.0, static_cast(samples.at(i)) / heightScaleMs, 1.0);
+ const qreal barHeight = heightRatio * height();
+
+ // Colors follow an absolute quality ramp: a steady good ping stays
+ // green no matter how uniform the window is.
+ const qreal colorRatio = qBound(0.0, static_cast(samples.at(i)) / ColorScaleMs, 1.0);
+ QColor color;
+ color.setHsv(qRound(120.0 * (1.0 - colorRatio)), 255, 255);
+
+ const QRectF bar(static_cast(i) * widthPerBar + 1.0, static_cast(height()) - barHeight,
+ qMax(1.0, widthPerBar - 2.0), barHeight);
+ painter.fillRect(bar, color);
+ }
+}
diff --git a/cockatrice/src/client/latency_graph_widget.h b/cockatrice/src/client/latency_graph_widget.h
new file mode 100644
index 000000000..4f6f38f8b
--- /dev/null
+++ b/cockatrice/src/client/latency_graph_widget.h
@@ -0,0 +1,43 @@
+/**
+ * @file latency_graph_widget.h
+ * @ingroup Client
+ */
+
+#ifndef LATENCY_GRAPH_WIDGET_H
+#define LATENCY_GRAPH_WIDGET_H
+
+#include
+#include
+
+/**
+ * @brief Bar graph of recent network round-trip samples.
+ *
+ * Draws one bar per sample, oldest on the left. Bar height is relative to the
+ * window's own scale so the shape of the variance stays readable, while bar
+ * color maps each sample onto an absolute quality ramp (green at rest through
+ * red at ColorScaleMs) so a steady good ping never looks alarming. Size
+ * agnostic: the status bar embeds a small instance while the latency detail
+ * popup shows a large one.
+ */
+class LatencyGraphWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit LatencyGraphWidget(QWidget *parent = nullptr);
+
+ /// Sample in milliseconds that maps to a fully red bar.
+ static constexpr qint64 ColorScaleMs = 500;
+
+ void setSamples(const QList &samplesMs);
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ /// Floor of the vertical scale in milliseconds. Keeps small windows readable.
+ static constexpr qint64 MinScaleMs = 100;
+
+ QList samples;
+};
+
+#endif
diff --git a/cockatrice/src/client/latency_status_widget.cpp b/cockatrice/src/client/latency_status_widget.cpp
new file mode 100644
index 000000000..779c726eb
--- /dev/null
+++ b/cockatrice/src/client/latency_status_widget.cpp
@@ -0,0 +1,111 @@
+/**
+ * @file latency_status_widget.cpp
+ * @ingroup Client
+ */
+
+#include "latency_status_widget.h"
+
+#include "latency_graph_widget.h"
+
+#include
+#include
+#include
+#include
+
+LatencyStatusWidget::LatencyStatusWidget(QWidget *parent) : QWidget(parent)
+{
+ pingLabel = new QLabel(this);
+ pingLabel->setAccessibleName(tr("Ping"));
+
+ latencyGraph = new LatencyGraphWidget(this);
+ latencyGraph->setFixedSize(90, 14);
+
+ auto *layout = new QHBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(4);
+ layout->addWidget(latencyGraph);
+ layout->addWidget(pingLabel);
+
+ // Clicking anywhere in the area opens the detail view.
+ for (QObject *child : QList{pingLabel, latencyGraph}) {
+ child->installEventFilter(this);
+ }
+ setCursor(Qt::PointingHandCursor);
+
+ hide();
+}
+
+void LatencyStatusWidget::updateData(const LatencyTracker::Stats &stats, const QList &samplesMs)
+{
+ latestSamples = samplesMs;
+ latencyGraph->setSamples(samplesMs);
+ if (popup && popup->isVisible() && detailGraph) {
+ detailGraph->setSamples(samplesMs);
+ }
+
+ if (stats.sampleCount == 0) {
+ hide();
+ return;
+ }
+
+ const QString statsStr = statsText(stats);
+
+ pingLabel->setText(tr("Ping: %1 ms").arg(stats.lastMs));
+ pingLabel->setToolTip(statsStr);
+ pingLabel->setAccessibleDescription(statsStr);
+ if (popup && popup->isVisible() && detailLabel) {
+ detailLabel->setText(statsStr);
+ }
+ show();
+}
+
+bool LatencyStatusWidget::eventFilter(QObject *watched, QEvent *event)
+{
+ if ((watched == pingLabel || watched == latencyGraph) && event->type() == QEvent::MouseButtonPress) {
+ togglePopup();
+ return true;
+ }
+ return QWidget::eventFilter(watched, event);
+}
+
+void LatencyStatusWidget::togglePopup()
+{
+ if (!popup) {
+ popup = new QWidget(this, Qt::Popup | Qt::FramelessWindowHint);
+ auto *layout = new QVBoxLayout(popup);
+ layout->setContentsMargins(8, 8, 8, 8);
+
+ detailLabel = new QLabel(popup);
+ detailLabel->setAccessibleName(tr("Connection latency details"));
+ detailLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
+
+ detailGraph = new LatencyGraphWidget(popup);
+ detailGraph->setFixedSize(280, 80);
+
+ layout->addWidget(detailLabel, 0, Qt::AlignLeft);
+ layout->addWidget(detailGraph, 0, Qt::AlignHCenter);
+ }
+
+ if (popup->isVisible()) {
+ popup->hide();
+ return;
+ }
+
+ // Qt::Popup closes itself on any outside click, so just position and show.
+ if (latestSamples.isEmpty()) {
+ return;
+ }
+ detailGraph->setSamples(latestSamples);
+ detailLabel->setText(pingLabel->toolTip());
+ popup->adjustSize();
+ const QPoint anchor = mapToGlobal(QPoint(width() / 2, 0));
+ popup->move(anchor.x() - popup->width() / 2, anchor.y() - popup->height() - 6);
+ popup->show();
+}
+
+QString LatencyStatusWidget::statsText(const LatencyTracker::Stats &stats) const
+{
+ return tr("Connection quality over the last %n sample(s):", "", stats.sampleCount) + "\n" +
+ tr("Last: %1 ms").arg(stats.lastMs) + "\n" + tr("Median: %1 ms").arg(stats.medianMs) + "\n" +
+ tr("95th percentile: %1 ms").arg(stats.p95Ms) + "\n" + tr("Maximum: %1 ms").arg(stats.maxMs);
+}
diff --git a/cockatrice/src/client/latency_status_widget.h b/cockatrice/src/client/latency_status_widget.h
new file mode 100644
index 000000000..d9e1d130c
--- /dev/null
+++ b/cockatrice/src/client/latency_status_widget.h
@@ -0,0 +1,50 @@
+/**
+ * @file latency_status_widget.h
+ * @ingroup Client
+ */
+
+#ifndef LATENCY_STATUS_WIDGET_H
+#define LATENCY_STATUS_WIDGET_H
+
+#include
+#include
+#include
+
+class QLabel;
+class LatencyGraphWidget;
+
+/**
+ * @brief Status bar presentation of server round-trip health.
+ *
+ * Combines the textual "Ping" readout with a small LatencyGraphWidget
+ * sparkline of the rolling sample window. Clicking anywhere in the area opens
+ * a popup with a larger graph and the numeric statistics. It closes on any
+ * outside click. Stays hidden while disconnected or before any samples exist.
+ * Owns all latency display state so MainWindow only needs to forward one
+ * signal here.
+ */
+class LatencyStatusWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit LatencyStatusWidget(QWidget *parent = nullptr);
+
+public slots:
+ void updateData(const LatencyTracker::Stats &stats, const QList &samplesMs);
+
+protected:
+ bool eventFilter(QObject *watched, QEvent *event) override;
+
+private:
+ void togglePopup();
+ QString statsText(const LatencyTracker::Stats &stats) const;
+
+ QLabel *pingLabel = nullptr;
+ LatencyGraphWidget *latencyGraph = nullptr;
+ QWidget *popup = nullptr;
+ LatencyGraphWidget *detailGraph = nullptr;
+ QLabel *detailLabel = nullptr;
+ QList latestSamples;
+};
+
+#endif
diff --git a/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
index 75cedcafc..c1598bd25 100644
--- a/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
+++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
@@ -1,5 +1,6 @@
#include "remote_connection_controller.h"
+#include "../../../interface/pixel_map_generator.h"
#include "../../settings/cache_settings.h"
#include "../interface/widgets/dialogs/dlg_connect.h"
#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h"
@@ -14,6 +15,7 @@
#include
#include
#include
+#include
ConnectionController::ConnectionController(QWidget *dialogParent, QObject *parent)
: QObject(parent), dialogParent(dialogParent)
@@ -43,6 +45,8 @@ void ConnectionController::wireClientSignals()
connect(remoteClient, &RemoteClient::statusChanged, this, &ConnectionController::onStatusChanged);
+ connect(remoteClient, &AbstractClient::pingStatsUpdated, this, &ConnectionController::pingStatsUpdated);
+
connect(remoteClient, &RemoteClient::userInfoChanged, this, &ConnectionController::onUserInfoReceived,
Qt::BlockingQueuedConnection);
@@ -177,7 +181,7 @@ void ConnectionController::onServerShutdownEvent(const Event_ServerShutdown &eve
"games will be lost.\nReason for shutdown: %1",
"", event.minutes())
.arg(QString::fromStdString(event.reason())));
- serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64));
+ serverShutdownMessageBox.setIconPixmap(themePixmap(QStringLiteral("cockatrice")).scaled(64, 64));
serverShutdownMessageBox.setText(tr("Scheduled server shutdown"));
serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal);
serverShutdownMessageBox.setVisible(true);
@@ -295,6 +299,15 @@ void ConnectionController::onLoginError(int r,
return;
}
+ case Response::RespPasswordChangeRequired: {
+ QMessageBox::information(
+ dialogParent, tr("Password Change Required"),
+ tr("An administrator has reset your password. Please contact your server administrator to obtain "
+ "your temporary password, then log in and change it via Account -> Change Password."));
+ remoteClient->disconnectFromServer();
+ return;
+ }
+
case Response::RespServerFull: {
QMessageBox::critical(dialogParent, tr("Server Full"),
tr("The server has reached its maximum user capacity, please check back later."));
diff --git a/cockatrice/src/client/network/connection_controller/remote_connection_controller.h b/cockatrice/src/client/network/connection_controller/remote_connection_controller.h
index 7486bc81a..bae99a3e0 100644
--- a/cockatrice/src/client/network/connection_controller/remote_connection_controller.h
+++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.h
@@ -54,6 +54,10 @@ signals:
// action enable/disable logic
void statusChanged(ClientStatus status);
+ // Forwarded from AbstractClient::pingStatsUpdated. See that signal for the
+ // meaning of the parameters.
+ void pingStatsUpdated(const LatencyTracker::Stats &stats, const QList &samplesMs);
+
private slots:
// Slots wired directly to RemoteClient signals
void onStatusChanged(ClientStatus status);
diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp
index 8689a19e9..42292b2aa 100644
--- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp
+++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp
@@ -72,7 +72,7 @@ void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList
{
auto copyIfNotAToken = [&destination](const auto node, const auto card) {
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
- if (dbCard && !dbCard->getIsToken()) {
+ if (dbCard && !dbCard->getIsToken() && node->getName() != DECK_ZONE_MAYBEBOARD) {
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1);
addedCard->setNumber(card->getNumber());
}
diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp
index 5dc77fa2c..627b7fe34 100644
--- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp
+++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp
@@ -99,7 +99,7 @@ void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckLi
{
auto copyMainOrSide = [&mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
- if (!dbCard || dbCard->getIsToken()) {
+ if (!dbCard || dbCard->getIsToken() || node->getName() == DECK_ZONE_MAYBEBOARD) {
return;
}
diff --git a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp
index 7194c46e5..dae633717 100644
--- a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp
+++ b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp
@@ -14,6 +14,8 @@
#include
#include
#include
+#include
+#include
#include
#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
@@ -21,7 +23,7 @@
SpoilerBackgroundUpdater::SpoilerBackgroundUpdater(QObject *apParent) : QObject(apParent), cardUpdateProcess(nullptr)
{
- isSpoilerDownloadEnabled = SettingsCache::instance().getDownloadSpoilersStatus();
+ isSpoilerDownloadEnabled = SettingsCache::instance().downloads().getDownloadSpoilersStatus();
if (isSpoilerDownloadEnabled) {
// Start the process of checking if we're in spoiler season
// File exists means we're in spoiler season
@@ -75,7 +77,7 @@ void SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile()
bool SpoilerBackgroundUpdater::deleteSpoilerFile()
{
- QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
+ QString fileName = SettingsCache::instance().paths().getSpoilerCardDatabasePath();
QFileInfo fi(fileName);
QDir fileDir(fi.path());
QFile file(fileName);
@@ -126,7 +128,7 @@ void SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled()
bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
{
- QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
+ QString fileName = SettingsCache::instance().paths().getSpoilerCardDatabasePath();
QFileInfo fi(fileName);
QDir fileDir(fi.path());
diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp
index b6bc8a47d..eaa5d96ac 100644
--- a/cockatrice/src/client/settings/cache_settings.cpp
+++ b/cockatrice/src/client/settings/cache_settings.cpp
@@ -1,20 +1,41 @@
#include "cache_settings.h"
-#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h"
-#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h"
#include "../network/update/client/release_channel.h"
#include "card_counter_settings.h"
+#include "shortcuts_settings.h"
#include "version_string.h"
-#include
-#include
#include
#include
#include
#include
#include
#include
+#include
+#include
+#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
#include
Q_GLOBAL_STATIC(SettingsCache, settingsCache)
@@ -52,104 +73,6 @@ QString SettingsCache::getNetworkCachePath() const
return getCachePath() + "/downloaded/";
}
-void SettingsCache::translateLegacySettings()
-{
- if (isPortableBuild) {
- return;
- }
-
- // Layouts
- QFile layoutFile(getSettingsPath() + "layouts/deckLayout.ini");
- if (layoutFile.exists()) {
- if (layoutFile.copy(getSettingsPath() + "layouts.ini")) {
- layoutFile.remove();
- }
- }
-
- QStringList usedKeys;
- QSettings legacySetting;
-
- // Sets
- legacySetting.beginGroup("sets");
- QStringList setsGroups = legacySetting.childGroups();
- for (int i = 0; i < setsGroups.size(); i++) {
- legacySetting.beginGroup(setsGroups.at(i));
- cardDatabase()->setEnabled(setsGroups.at(i), legacySetting.value("enabled").toBool());
- cardDatabase()->setIsKnown(setsGroups.at(i), legacySetting.value("isknown").toBool());
- cardDatabase()->setSortKey(setsGroups.at(i), legacySetting.value("sortkey").toUInt());
- legacySetting.endGroup();
- }
- QStringList setsKeys = legacySetting.allKeys();
- for (int i = 0; i < setsKeys.size(); ++i) {
- usedKeys.append("sets/" + setsKeys.at(i));
- }
- legacySetting.endGroup();
-
- // Servers
- legacySetting.beginGroup("server");
- servers().setPreviousHostLogin(legacySetting.value("previoushostlogin").toInt());
- servers().setPreviousHostList(legacySetting.value("previoushosts").toStringList());
- servers().setAutoConnect(legacySetting.value("auto_connect").toInt());
- servers().setFPHostName(legacySetting.value("fphostname").toString());
- servers().setFPPort(legacySetting.value("fpport").toString());
- servers().setFPPlayerName(legacySetting.value("fpplayername").toString());
- usedKeys.append(legacySetting.allKeys());
- QStringList allKeysServer = legacySetting.allKeys();
- for (int i = 0; i < allKeysServer.size(); ++i) {
- usedKeys.append("server/" + allKeysServer.at(i));
- }
- legacySetting.endGroup();
-
- // Messages
- legacySetting.beginGroup("messages");
- QStringList allMessages = legacySetting.allKeys();
- for (int i = 0; i < allMessages.size(); ++i) {
- if (allMessages.at(i) != "count") {
- QString temp = allMessages.at(i);
- int index = temp.remove("msg").toInt();
- messages().setMessageAt(index, legacySetting.value(allMessages.at(i)).toString());
- }
- }
- messages().setCount(legacySetting.value("count").toInt());
- QStringList allKeysmessages = legacySetting.allKeys();
- for (int i = 0; i < allKeysmessages.size(); ++i) {
- usedKeys.append("messages/" + allKeysmessages.at(i));
- }
- legacySetting.endGroup();
-
- // Game filters
- legacySetting.beginGroup("filter_games");
- gameFilters().setGameNameFilter(legacySetting.value("game_name_filter").toString());
- gameFilters().setHideIgnoredUserGames(legacySetting.value("hide_ignored_user_games").toBool());
- gameFilters().setMinPlayers(legacySetting.value("min_players").toInt());
-
- if (legacySetting.value("max_players").toInt() > 1) {
- gameFilters().setMaxPlayers(legacySetting.value("max_players").toInt());
- } else {
- gameFilters().setMaxPlayers(99); // This prevents a bug where no games will show if max was not set before
- }
-
- QStringList allFilters = legacySetting.allKeys();
- for (int i = 0; i < allFilters.size(); ++i) {
- if (allFilters.at(i).startsWith("game_type")) {
- gameFilters().setGameHashedTypeEnabled(allFilters.at(i), legacySetting.value(allFilters.at(i)).toBool());
- }
- }
- QStringList allKeysfilter_games = legacySetting.allKeys();
- for (int i = 0; i < allKeysfilter_games.size(); ++i) {
- usedKeys.append("filter_games/" + allKeysfilter_games.at(i));
- }
- legacySetting.endGroup();
-
- QStringList allLegacyKeys = legacySetting.allKeys();
- for (int i = 0; i < allLegacyKeys.size(); ++i) {
- if (usedKeys.contains(allLegacyKeys.at(i))) {
- continue;
- }
- settings->setValue(allLegacyKeys.at(i), legacySetting.value(allLegacyKeys.at(i)));
- }
-}
-
QString SettingsCache::getSafeConfigPath(QString configEntry, QString defaultPath) const
{
QString tmp = settings->value(configEntry).toString();
@@ -183,11 +106,18 @@ SettingsCache::SettingsCache()
qCInfo(SettingsCacheLog) << "Portable mode enabled";
}
- // define a dummy context that will be used where needed
- QString dummy = QT_TRANSLATE_NOOP("i18n", "English");
-
QString settingsPath = getSettingsPath();
+
settings = new QSettings(settingsPath + "global.ini", QSettings::IniFormat, this);
+
+ // Migrate from legacy NativeFormat settings to per-class INI files (runs at most once)
+ if (!isPortableBuild) {
+ SettingsMigration::migrateLegacySettings(settingsPath);
+ }
+
+ // Migrate settings from global.ini to per-class INI files (runs at most once)
+ SettingsMigration::migrateSettingsFromGlobalIni(settingsPath);
+
shortcutsSettings = new ShortcutsSettings(settingsPath, this);
cardDatabaseSettings = new CardDatabaseSettings(settingsPath, this);
serversSettings = new ServersSettings(settingsPath, this);
@@ -198,997 +128,51 @@ SettingsCache::SettingsCache()
recentsSettings = new RecentsSettings(settingsPath, this);
cardOverrideSettings = new CardOverrideSettings(settingsPath, this);
debugSettings = new DebugSettings(settingsPath, this);
-
cardCounterSettings = new CardCounterSettings(settingsPath, this);
- if (!QFile(settingsPath + "global.ini").exists()) {
- translateLegacySettings();
- }
+ tabsSettings = new TabsSettings(settingsPath, this);
+ soundSettings = new SoundSettings(settingsPath, this);
+ gameSettings = new GameSettings(settingsPath, this);
+ chatSettings = new ChatSettings(settingsPath, this);
+ cacheStorageSettings = new CacheStorageSettings(settingsPath, this);
+ updatesSettings = new UpdatesSettings(settingsPath, this);
+ personalSettings = new PersonalSettings(settingsPath, this);
+ cardsDisplaySettings = new CardsDisplaySettings(settingsPath, this);
+ interfaceSettings = new InterfaceSettings(settingsPath, this);
+ deckEditorSettings = new DeckEditorSettings(settingsPath, this);
+ pathsSettings = new PathsSettings(settingsPath, this);
+ visualDeckStorageSettings = new VisualDeckStorageSettings(settingsPath, this);
+ appearanceSettings = new AppearanceSettings(settingsPath, this);
+ networkSettings = new NetworkSettings(settingsPath, this);
+ commanderBracketSettings = new CommanderBracketSettings(settingsPath, this);
+
+ // Forward ICardDatabasePathProvider signal from PathsSettings
+ connect(pathsSettings, &PathsSettings::cardDatabasePathChanged, this,
+ &ICardDatabasePathProvider::cardDatabasePathChanged);
// updates - don't reorder them or their index in the settings won't match
// append channels one by one, or msvc will add them in the wrong order.
releaseChannels << new StableReleaseChannel();
releaseChannels << new BetaReleaseChannel();
- mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool();
+ themeName = appearanceSettings->getThemeName();
- if (settings->contains("personal/startupUpdateCheck")) {
- checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool();
- } else if (QString(VERSION_STRING).contains("custom", Qt::CaseInsensitive)) {
- checkUpdatesOnStartup = false; // do not run auto updater on custom version
- } else {
- checkUpdatesOnStartup = true; // default to run auto updater
- }
- startupCardUpdateCheckPromptForUpdate =
- settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool();
- startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
- cardUpdateCheckInterval = settings->value("personal/cardUpdateCheckInterval", 7).toInt();
- lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
- alwaysEnableNewSets = settings->value("personal/alwaysEnableNewSets", false).toBool();
- notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
- notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
-
- if (settings->contains("personal/updatereleasechannel")) {
- updateReleaseChannel = settings->value("personal/updatereleasechannel").toInt();
- } else if (QString(VERSION_STRING).contains("beta", Qt::CaseInsensitive)) {
- // default to beta if this is a beta release
- updateReleaseChannel = 1;
- } else {
- updateReleaseChannel = 0; // stable
- }
-
- lang = settings->value("personal/lang").toString();
- keepalive = settings->value("personal/keepalive", 3).toInt();
- timeout = settings->value("personal/timeout", 5).toInt();
-
- // tip of the day settings
- showTipsOnStartup = settings->value("tipOfDay/showTips", true).toBool();
- for (const auto &tipNumber : settings->value("tipOfDay/seenTips").toList()) {
- seenTips.append(tipNumber.toInt());
+ auto definitions = commanderBracketSettings->loadDefinitions();
+ if (definitions.isEmpty()) {
+ definitions = CommanderBracketSettings::defaultDefinitions();
}
+ commanderBracketSettings->reloadDefinitions(definitions);
loadPaths();
-
- themeName = settings->value("theme/name").toString();
-
- homeTabBackgroundSource = settings->value("home/background", "themed").toString();
- homeTabBackgroundShuffleFrequency = settings->value("home/background/shuffleTimer", 0).toInt();
- homeTabDisplayCardName = settings->value("home/background/displayCardName", true).toBool();
-
- tabVisualDeckStorageOpen = settings->value("tabs/visualDeckStorage", true).toBool();
- tabServerOpen = settings->value("tabs/server", true).toBool();
- tabAccountOpen = settings->value("tabs/account", true).toBool();
- tabDeckStorageOpen = settings->value("tabs/deckStorage", true).toBool();
- tabReplaysOpen = settings->value("tabs/replays", true).toBool();
- tabAdminOpen = settings->value("tabs/admin", true).toBool();
- tabLogOpen = settings->value("tabs/log", true).toBool();
-
- // we only want to reset the cache once, then its up to the user
- bool updateCache = settings->value("revert/pixmapCacheSize", false).toBool();
- if (!updateCache) {
- pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT;
- settings->setValue("personal/pixmapCacheSize", pixmapCacheSize);
- settings->setValue("personal/picturedownloadhq", false);
- settings->setValue("revert/pixmapCacheSize", true);
- } else {
- pixmapCacheSize = settings->value("personal/pixmapCacheSize", PIXMAPCACHE_SIZE_DEFAULT).toInt();
- }
- // sanity check
- if (pixmapCacheSize < PIXMAPCACHE_SIZE_MIN || pixmapCacheSize > PIXMAPCACHE_SIZE_MAX) {
- pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT;
- }
-
- networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt();
- redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt();
- cardPictureLoaderCacheMethod =
- settings
- ->value("personal/cardPictureLoaderCacheMethod",
- static_cast(CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE))
- .toInt();
- localCardImageStorageNamingScheme =
- settings
- ->value("personal/localCardImageStorageNamingScheme",
- static_cast(CardPictureLoaderLocalSchemes::NamingScheme::Set_Folder_Name_Set_Collector))
- .toInt();
-
- picDownload = settings->value("personal/picturedownload", true).toBool();
- showStatusBar = settings->value("personal/showStatusBar", false).toBool();
-
- notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool();
- spectatorNotificationsEnabled = settings->value("interface/specnotificationsenabled", false).toBool();
- buddyConnectNotificationsEnabled = settings->value("interface/buddyconnectnotificationsenabled", true).toBool();
- doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool();
- clickPlaysAllSelected = settings->value("interface/clickPlaysAllSelected", true).toBool();
- playToStack = settings->value("interface/playtostack", true).toBool();
- doNotDeleteArrowsInSubPhases = settings->value("interface/doNotDeleteArrowsInSubPhases", true).toBool();
- startingHandSize = settings->value("interface/startinghandsize", 7).toInt();
- annotateTokens = settings->value("interface/annotatetokens", false).toBool();
- knownMissingFeatures = settings->value("interface/knownmissingfeatures", "").toString();
- useTearOffMenus = settings->value("interface/usetearoffmenus", true).toBool();
- cardViewInitialRowsMax = settings->value("interface/cardViewInitialRowsMax", 14).toInt();
- cardViewExpandedRowsMax = settings->value("interface/cardViewExpandedRowsMax", 20).toInt();
- closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool();
- focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool();
- keepGameChatFocus = settings->value("interface/keepGameChatFocus", false).toBool();
-
- showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool();
- showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool();
- showSubtypeSelectionTally = settings->value("interface/showsubtypeselectiontally", true).toBool();
-
- showShortcuts = settings->value("menu/showshortcuts", true).toBool();
- showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool();
- displayCardNames = settings->value("cards/displaycardnames", true).toBool();
- roundCardCorners = settings->value("cards/roundcardcorners", true).toBool();
- overrideAllCardArtWithPersonalPreference =
- settings->value("cards/overrideallcardartwithpersonalpreference", false).toBool();
- bumpSetsWithCardsInDeckToTop = settings->value("cards/bumpsetswithcardsindecktotop", true).toBool();
- printingSelectorSortOrder = settings->value("cards/printingselectorsortorder", 1).toInt();
- printingSelectorCardSize = settings->value("cards/printingselectorcardsize", 100).toInt();
- includeRebalancedCards = settings->value("cards/includerebalancedcards", true).toBool();
- printingSelectorNavigationButtonsVisible =
- settings->value("cards/printingselectornavigationbuttonsvisible", true).toBool();
- deckEditorBannerCardComboBoxVisible =
- settings->value("interface/deckeditorbannercardcomboboxvisible", true).toBool();
- deckEditorTagsWidgetVisible = settings->value("interface/deckeditortagswidgetvisible", true).toBool();
- visualDeckStorageCardSize = settings->value("interface/visualdeckstoragecardsize", 100).toInt();
- visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).toInt();
- visualDeckStorageShowFolders = settings->value("interface/visualdeckstorageshowfolders", true).toBool();
- visualDeckStorageShowTagFilter = settings->value("interface/visualdeckstorageshowtagfilter", true).toBool();
- visualDeckStorageDefaultTagsList =
- settings->value("interface/visualdeckstoragedefaulttagslist", defaultTags).toStringList();
- visualDeckStorageSearchFolderNames = settings->value("interface/visualdeckstoragesearchfoldernames", true).toBool();
- visualDeckStorageShowColorIdentity = settings->value("interface/visualdeckstorageshowcoloridentity", true).toBool();
- visualDeckStorageShowBannerCardComboBox =
- settings->value("interface/visualdeckstorageshowbannercardcombobox", true).toBool();
- visualDeckStorageShowTagsOnDeckPreviews =
- settings->value("interface/visualdeckstorageshowtagsondeckpreviews", true).toBool();
- visualDeckStorageDrawUnusedColorIdentities =
- settings->value("interface/visualdeckstoragedrawunusedcoloridentities", true).toBool();
- visualDeckStorageUnusedColorIdentitiesOpacity =
- settings->value("interface/visualdeckstorageunusedcoloridentitiesopacity", 15).toInt();
- visualDeckStorageTooltipType = settings->value("interface/visualdeckstoragetooltiptype", 0).toInt();
- visualDeckStoragePromptForConversion =
- settings->value("interface/visualdeckstoragepromptforconversion", true).toBool();
- visualDeckStorageAlwaysConvert = settings->value("interface/visualdeckstoragealwaysconvert", false).toBool();
- visualDeckStorageInGame = settings->value("interface/visualdeckstorageingame", true).toBool();
- visualDeckStorageSelectionAnimation =
- settings->value("interface/visualdeckstorageselectionanimation", true).toBool();
- defaultDeckEditorType = settings->value("interface/defaultDeckEditorType", 1).toInt();
- visualDatabaseDisplayFilterToMostRecentSetsEnabled =
- settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsenabled", false).toBool();
- visualDatabaseDisplayFilterToMostRecentSetsAmount =
- settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsamount", 10).toInt();
- visualDeckEditorSampleHandSize = settings->value("interface/visualdeckeditorsamplehandsize", 7).toInt();
- visualDeckEditorCardSize = settings->value("interface/visualdeckeditorcardsize", 100).toInt();
- visualDatabaseDisplayCardSize = settings->value("interface/visualdatabasedisplaycardsize", 100).toInt();
- edhrecCardSize = settings->value("interface/edhreccardsize", 100).toInt();
- archidektPreviewSize = settings->value("interface/archidektpreviewsize", 100).toInt();
-
- horizontalHand = settings->value("hand/horizontal", true).toBool();
- invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool();
- minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt();
- tapAnimation = settings->value("cards/tapanimation", true).toBool();
- autoRotateSidewaysLayoutCards = settings->value("cards/autorotatesidewayslayoutcards", true).toBool();
-
- openDeckInNewTab = settings->value("editor/openDeckInNewTab", false).toBool();
- rewindBufferingMs = settings->value("replay/rewindBufferingMs", 200).toInt();
- styleUserList = settings->value("appearance/styleUserList", true).toBool();
- chatMention = settings->value("chat/mention", true).toBool();
- chatMentionCompleter = settings->value("chat/mentioncompleter", true).toBool();
- chatMentionForeground = settings->value("chat/mentionforeground", true).toBool();
- chatHighlightForeground = settings->value("chat/highlightforeground", true).toBool();
- chatMentionColor = settings->value("chat/mentioncolor", "A6120D").toString();
- chatHighlightColor = settings->value("chat/highlightcolor", "A6120D").toString();
-
- zoneViewGroupByIndex = settings->value("zoneview/groupby", 1).toInt();
- zoneViewSortByIndex = settings->value("zoneview/sortby", 1).toInt();
- zoneViewPileView = settings->value("zoneview/pileview", true).toBool();
-
- soundEnabled = settings->value("sound/enabled", false).toBool();
- soundThemeName = settings->value("sound/theme").toString();
-
- maxFontSize = settings->value("game/maxfontsize", DEFAULT_FONT_SIZE).toInt();
-
- ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool();
- ignoreUnregisteredUserMessages = settings->value("chat/ignore_unregistered_messages", false).toBool();
- ignoreNonBuddyUserMessages = settings->value("chat/ignore_nonbuddy_messages", false).toBool();
-
- scaleCards = settings->value("cards/scaleCards", true).toBool();
- verticalCardOverlapPercent = settings->value("cards/verticalCardOverlapPercent", 33).toInt();
- showMessagePopups = settings->value("chat/showmessagepopups", true).toBool();
- showMentionPopups = settings->value("chat/showmentionpopups", true).toBool();
- roomHistory = settings->value("chat/roomhistory", true).toBool();
-
- leftJustified = settings->value("interface/leftjustified", false).toBool();
-
- masterVolume = settings->value("sound/mastervolume", 100).toInt();
-
- cardInfoViewMode = settings->value("cards/cardinfoviewmode", 0).toInt();
- highlightWords = settings->value("personal/highlightWords", QString()).toString();
- gameDescription = settings->value("game/gamedescription", "").toString();
- maxPlayers = settings->value("game/maxplayers", 2).toInt();
- gameTypes = settings->value("game/gametypes", "").toString();
- onlyBuddies = settings->value("game/onlybuddies", false).toBool();
- onlyRegistered = settings->value("game/onlyregistered", true).toBool();
- spectatorsAllowed = settings->value("game/spectatorsallowed", true).toBool();
- spectatorsNeedPassword = settings->value("game/spectatorsneedpassword", false).toBool();
- spectatorsCanTalk = settings->value("game/spectatorscantalk", false).toBool();
- spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool();
- createGameAsSpectator = settings->value("game/creategameasspectator", false).toBool();
- defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt();
- shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool();
- rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
-
- // Local game settings use "localgameoptions/" prefix to keep them separate
- // from server game settings which use "game/" prefix
- localGameRememberSettings = settings->value("localgameoptions/remembersettings", false).toBool();
- localGameMaxPlayers = settings->value("localgameoptions/maxplayers", 1).toInt();
- localGameStartingLifeTotal = settings->value("localgameoptions/startinglifetotal", 20).toInt();
-
- clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString();
- clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString();
-}
-
-void SettingsCache::setUseTearOffMenus(bool _useTearOffMenus)
-{
- useTearOffMenus = _useTearOffMenus;
- settings->setValue("interface/usetearoffmenus", useTearOffMenus);
- emit useTearOffMenusChanged(useTearOffMenus);
-}
-
-void SettingsCache::setCardViewInitialRowsMax(int _cardViewInitialRowsMax)
-{
- cardViewInitialRowsMax = _cardViewInitialRowsMax;
- settings->setValue("interface/cardViewInitialRowsMax", cardViewInitialRowsMax);
-}
-
-void SettingsCache::setCardViewExpandedRowsMax(int value)
-{
- cardViewExpandedRowsMax = value;
- settings->setValue("interface/cardViewExpandedRowsMax", cardViewExpandedRowsMax);
-}
-
-void SettingsCache::setCloseEmptyCardView(QT_STATE_CHANGED_T value)
-{
- closeEmptyCardView = value;
- settings->setValue("interface/closeEmptyCardView", closeEmptyCardView);
-}
-
-void SettingsCache::setFocusCardViewSearchBar(QT_STATE_CHANGED_T value)
-{
- focusCardViewSearchBar = value;
- settings->setValue("interface/focusCardViewSearchBar", focusCardViewSearchBar);
-}
-
-void SettingsCache::setKeepGameChatFocus(QT_STATE_CHANGED_T value)
-{
- keepGameChatFocus = value;
- settings->setValue("interface/keepGameChatFocus", keepGameChatFocus);
- emit keepGameChatFocusChanged(keepGameChatFocus);
-}
-
-void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures)
-{
- knownMissingFeatures = _knownMissingFeatures;
- settings->setValue("interface/knownmissingfeatures", knownMissingFeatures);
-}
-
-void SettingsCache::setCardInfoViewMode(const int _viewMode)
-{
- cardInfoViewMode = _viewMode;
- settings->setValue("cards/cardinfoviewmode", cardInfoViewMode);
-}
-
-void SettingsCache::setHighlightWords(const QString &_highlightWords)
-{
- highlightWords = _highlightWords;
- settings->setValue("personal/highlightWords", highlightWords);
-}
-
-void SettingsCache::setMasterVolume(int _masterVolume)
-{
- masterVolume = _masterVolume;
- settings->setValue("sound/mastervolume", masterVolume);
- emit masterVolumeChanged(masterVolume);
-}
-
-void SettingsCache::setLeftJustified(const QT_STATE_CHANGED_T _leftJustified)
-{
- leftJustified = (bool)_leftJustified;
- settings->setValue("interface/leftjustified", leftJustified);
- emit handJustificationChanged();
-}
-
-void SettingsCache::setCardScaling(const QT_STATE_CHANGED_T _scaleCards)
-{
- scaleCards = (bool)_scaleCards;
- settings->setValue("cards/scaleCards", scaleCards);
-}
-
-void SettingsCache::setStackCardOverlapPercent(const int _verticalCardOverlapPercent)
-{
- verticalCardOverlapPercent = _verticalCardOverlapPercent;
- settings->setValue("cards/verticalCardOverlapPercent", verticalCardOverlapPercent);
-}
-
-void SettingsCache::setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups)
-{
- showMessagePopups = (bool)_showMessagePopups;
- settings->setValue("chat/showmessagepopups", showMessagePopups);
-}
-
-void SettingsCache::setShowMentionPopups(const QT_STATE_CHANGED_T _showMentionPopus)
-{
- showMentionPopups = (bool)_showMentionPopus;
- settings->setValue("chat/showmentionpopups", showMentionPopups);
-}
-
-void SettingsCache::setRoomHistory(const QT_STATE_CHANGED_T _roomHistory)
-{
- roomHistory = (bool)_roomHistory;
- settings->setValue("chat/roomhistory", roomHistory);
-}
-
-void SettingsCache::setLang(const QString &_lang)
-{
- lang = _lang;
- settings->setValue("personal/lang", lang);
- emit langChanged();
-}
-
-void SettingsCache::setShowTipsOnStartup(bool _showTipsOnStartup)
-{
- showTipsOnStartup = _showTipsOnStartup;
- settings->setValue("tipOfDay/showTips", showTipsOnStartup);
-}
-
-void SettingsCache::setSeenTips(const QList &_seenTips)
-{
- seenTips = _seenTips;
- QList storedTipList;
- for (auto tipNumber : seenTips) {
- storedTipList.append(tipNumber);
- }
- settings->setValue("tipOfDay/seenTips", storedTipList);
-}
-
-void SettingsCache::setDeckPath(const QString &_deckPath)
-{
- deckPath = _deckPath;
- settings->setValue("paths/decks", deckPath);
-}
-
-void SettingsCache::setFiltersPath(const QString &_filtersPath)
-{
- filtersPath = _filtersPath;
- settings->setValue("paths/filters", filtersPath);
-}
-
-void SettingsCache::setReplaysPath(const QString &_replaysPath)
-{
- replaysPath = _replaysPath;
- settings->setValue("paths/replays", replaysPath);
-}
-
-void SettingsCache::setThemesPath(const QString &_themesPath)
-{
- themesPath = _themesPath;
- settings->setValue("paths/themes", themesPath);
- emit themeChanged();
-}
-
-void SettingsCache::setCustomCardDatabasePath(const QString &_customCardDatabasePath)
-{
- customCardDatabasePath = _customCardDatabasePath;
- settings->setValue("paths/customsets", customCardDatabasePath);
- emit cardDatabasePathChanged();
-}
-
-void SettingsCache::setPicsPath(const QString &_picsPath)
-{
- picsPath = _picsPath;
- settings->setValue("paths/pics", picsPath);
- // get a new value for customPicsPath, currently derived from picsPath
- if (picsPath.endsWith("/")) {
- customPicsPath = getSafeConfigPath("paths/custompics", picsPath + "CUSTOM/");
- } else {
- customPicsPath = getSafeConfigPath("paths/custompics", picsPath + "/CUSTOM/");
- }
- emit picsPathChanged();
-}
-
-void SettingsCache::setCardDatabasePath(const QString &_cardDatabasePath)
-{
- cardDatabasePath = _cardDatabasePath;
- settings->setValue("paths/carddatabase", cardDatabasePath);
- emit cardDatabasePathChanged();
-}
-
-void SettingsCache::setSpoilerDatabasePath(const QString &_spoilerDatabasePath)
-{
- spoilerDatabasePath = _spoilerDatabasePath;
- settings->setValue("paths/spoilerdatabase", spoilerDatabasePath);
- emit cardDatabasePathChanged();
-}
-
-void SettingsCache::setTokenDatabasePath(const QString &_tokenDatabasePath)
-{
- tokenDatabasePath = _tokenDatabasePath;
- settings->setValue("paths/tokendatabase", tokenDatabasePath);
- emit cardDatabasePathChanged();
}
void SettingsCache::setThemeName(const QString &_themeName)
{
themeName = _themeName;
- settings->setValue("theme/name", themeName);
+ appearanceSettings->setThemeName(themeName);
emit themeChanged();
}
-void SettingsCache::setHomeTabBackgroundSource(const QString &_backgroundSource)
-{
- homeTabBackgroundSource = _backgroundSource;
- settings->setValue("home/background", homeTabBackgroundSource);
- emit homeTabBackgroundSourceChanged();
-}
-
-void SettingsCache::setHomeTabBackgroundShuffleFrequency(int _frequency)
-{
- homeTabBackgroundShuffleFrequency = _frequency;
- settings->setValue("home/background/shuffleTimer", homeTabBackgroundShuffleFrequency);
- emit homeTabBackgroundShuffleFrequencyChanged();
-}
-
-void SettingsCache::setHomeTabDisplayCardName(QT_STATE_CHANGED_T _displayCardName)
-{
- homeTabDisplayCardName = static_cast(_displayCardName);
- settings->setValue("home/background/displayCardName", homeTabDisplayCardName);
- emit homeTabDisplayCardNameChanged();
-}
-
-void SettingsCache::setTabVisualDeckStorageOpen(bool value)
-{
- tabVisualDeckStorageOpen = value;
- settings->setValue("tabs/visualDeckStorage", tabVisualDeckStorageOpen);
-}
-
-void SettingsCache::setTabServerOpen(bool value)
-{
- tabServerOpen = value;
- settings->setValue("tabs/server", tabServerOpen);
-}
-
-void SettingsCache::setTabAccountOpen(bool value)
-{
- tabAccountOpen = value;
- settings->setValue("tabs/account", tabAccountOpen);
-}
-
-void SettingsCache::setTabDeckStorageOpen(bool value)
-{
- tabDeckStorageOpen = value;
- settings->setValue("tabs/deckStorage", tabDeckStorageOpen);
-}
-
-void SettingsCache::setTabReplaysOpen(bool value)
-{
- tabReplaysOpen = value;
- settings->setValue("tabs/replays", tabReplaysOpen);
-}
-
-void SettingsCache::setTabAdminOpen(bool value)
-{
- tabAdminOpen = value;
- settings->setValue("tabs/admin", tabAdminOpen);
-}
-
-void SettingsCache::setTabLogOpen(bool value)
-{
- tabLogOpen = value;
- settings->setValue("tabs/log", tabLogOpen);
-}
-
-void SettingsCache::setPicDownload(QT_STATE_CHANGED_T _picDownload)
-{
- picDownload = static_cast(_picDownload);
- settings->setValue("personal/picturedownload", picDownload);
- emit picDownloadChanged();
-}
-
-void SettingsCache::setShowStatusBar(bool value)
-{
- showStatusBar = value;
- settings->setValue("personal/showStatusBar", showStatusBar);
- emit showStatusBarChanged(value);
-}
-
-void SettingsCache::setNotificationsEnabled(QT_STATE_CHANGED_T _notificationsEnabled)
-{
- notificationsEnabled = static_cast(_notificationsEnabled);
- settings->setValue("interface/notificationsenabled", notificationsEnabled);
-}
-
-void SettingsCache::setSpectatorNotificationsEnabled(QT_STATE_CHANGED_T _spectatorNotificationsEnabled)
-{
- spectatorNotificationsEnabled = static_cast(_spectatorNotificationsEnabled);
- settings->setValue("interface/specnotificationsenabled", spectatorNotificationsEnabled);
-}
-
-void SettingsCache::setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T _buddyConnectNotificationsEnabled)
-{
- buddyConnectNotificationsEnabled = static_cast(_buddyConnectNotificationsEnabled);
- settings->setValue("interface/buddyconnectnotificationsenabled", buddyConnectNotificationsEnabled);
-}
-
-void SettingsCache::setDoubleClickToPlay(QT_STATE_CHANGED_T _doubleClickToPlay)
-{
- doubleClickToPlay = static_cast(_doubleClickToPlay);
- settings->setValue("interface/doubleclicktoplay", doubleClickToPlay);
-}
-
-void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T _clickPlaysAllSelected)
-{
- clickPlaysAllSelected = static_cast(_clickPlaysAllSelected);
- settings->setValue("interface/clickPlaysAllSelected", clickPlaysAllSelected);
-}
-
-void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T _playToStack)
-{
- playToStack = static_cast(_playToStack);
- settings->setValue("interface/playtostack", playToStack);
-}
-
-void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases)
-{
- doNotDeleteArrowsInSubPhases = static_cast(_doNotDeleteArrowsInSubPhases);
- settings->setValue("interface/doNotDeleteArrowsInSubPhases", doNotDeleteArrowsInSubPhases);
-}
-
-void SettingsCache::setStartingHandSize(int _startingHandSize)
-{
- startingHandSize = _startingHandSize;
- settings->setValue("interface/startinghandsize", startingHandSize);
-}
-
-void SettingsCache::setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens)
-{
- annotateTokens = static_cast(_annotateTokens);
- settings->setValue("interface/annotatetokens", annotateTokens);
-}
-
-void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts)
-{
- showShortcuts = static_cast(_showShortcuts);
- settings->setValue("menu/showshortcuts", showShortcuts);
-}
-
-void SettingsCache::setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar)
-{
- showGameSelectorFilterToolbar = static_cast(_showGameSelectorFilterToolbar);
- settings->setValue("menu/showgameselectorfiltertoolbar", showGameSelectorFilterToolbar);
- emit showGameSelectorFilterToolbarChanged(showGameSelectorFilterToolbar);
-}
-
-void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames)
-{
- displayCardNames = static_cast(_displayCardNames);
- settings->setValue("cards/displaycardnames", displayCardNames);
- emit displayCardNamesChanged();
-}
-
-void SettingsCache::setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt)
-{
- overrideAllCardArtWithPersonalPreference = static_cast(_overrideAllCardArt);
- settings->setValue("cards/overrideallcardartwithpersonalpreference", overrideAllCardArtWithPersonalPreference);
- emit overrideAllCardArtWithPersonalPreferenceChanged(overrideAllCardArtWithPersonalPreference);
-}
-
-void SettingsCache::setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop)
-{
- bumpSetsWithCardsInDeckToTop = static_cast(_bumpSetsWithCardsInDeckToTop);
- settings->setValue("cards/bumpsetswithcardsindecktotop", bumpSetsWithCardsInDeckToTop);
- emit bumpSetsWithCardsInDeckToTopChanged();
-}
-
-void SettingsCache::setPrintingSelectorSortOrder(int _printingSelectorSortOrder)
-{
- printingSelectorSortOrder = _printingSelectorSortOrder;
- settings->setValue("cards/printingselectorsortorder", printingSelectorSortOrder);
- emit printingSelectorSortOrderChanged();
-}
-
-void SettingsCache::setPrintingSelectorCardSize(int _printingSelectorCardSize)
-{
- printingSelectorCardSize = _printingSelectorCardSize;
- settings->setValue("cards/printingselectorcardsize", printingSelectorCardSize);
- emit printingSelectorCardSizeChanged();
-}
-
-void SettingsCache::setIncludeRebalancedCards(bool _includeRebalancedCards)
-{
- if (includeRebalancedCards == _includeRebalancedCards) {
- return;
- }
-
- includeRebalancedCards = _includeRebalancedCards;
- settings->setValue("cards/includerebalancedcards", includeRebalancedCards);
- emit includeRebalancedCardsChanged(includeRebalancedCards);
-}
-
-void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible)
-{
- printingSelectorNavigationButtonsVisible = _navigationButtonsVisible;
- settings->setValue("cards/printingselectornavigationbuttonsvisible", printingSelectorNavigationButtonsVisible);
- emit printingSelectorNavigationButtonsVisibleChanged();
-}
-
-void SettingsCache::setDeckEditorBannerCardComboBoxVisible(QT_STATE_CHANGED_T _deckEditorBannerCardComboBoxVisible)
-{
- deckEditorBannerCardComboBoxVisible = _deckEditorBannerCardComboBoxVisible;
- settings->setValue("interface/deckeditorbannercardcomboboxvisible", deckEditorBannerCardComboBoxVisible);
- emit deckEditorBannerCardComboBoxVisibleChanged(deckEditorBannerCardComboBoxVisible);
-}
-
-void SettingsCache::setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T _deckEditorTagsWidgetVisible)
-{
- deckEditorTagsWidgetVisible = _deckEditorTagsWidgetVisible;
- settings->setValue("interface/deckeditortagswidgetvisible", deckEditorTagsWidgetVisible);
- emit deckEditorTagsWidgetVisibleChanged(deckEditorTagsWidgetVisible);
-}
-
-void SettingsCache::setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder)
-{
- visualDeckStorageSortingOrder = _visualDeckStorageSortingOrder;
- settings->setValue("interface/visualdeckstoragesortingorder", visualDeckStorageSortingOrder);
-}
-
-void SettingsCache::setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value)
-{
- visualDeckStorageShowFolders = value;
- settings->setValue("interface/visualdeckstorageshowfolders", visualDeckStorageShowFolders);
-}
-
-void SettingsCache::setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags)
-{
- visualDeckStorageShowTagFilter = _showTags;
- settings->setValue("interface/visualdeckstorageshowtagfilter", visualDeckStorageShowTagFilter);
- emit visualDeckStorageShowTagFilterChanged(visualDeckStorageShowTagFilter);
-}
-
-void SettingsCache::setVisualDeckStorageDefaultTagsList(QStringList _defaultTagsList)
-{
- visualDeckStorageDefaultTagsList = _defaultTagsList;
- settings->setValue("interface/visualdeckstoragedefaulttagslist", visualDeckStorageDefaultTagsList);
- emit visualDeckStorageDefaultTagsListChanged();
-}
-
-void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T value)
-{
- visualDeckStorageSearchFolderNames = value;
- settings->setValue("interface/visualdeckstoragesearchfoldernames", visualDeckStorageSearchFolderNames);
-}
-
-void SettingsCache::setVisualDeckStorageShowColorIdentity(QT_STATE_CHANGED_T value)
-{
- visualDeckStorageShowColorIdentity = value;
- settings->setValue("interface/visualdeckstorageshowcoloridentity", visualDeckStorageShowColorIdentity);
- emit visualDeckStorageShowColorIdentityChanged(visualDeckStorageShowColorIdentity);
-}
-
-void SettingsCache::setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox)
-{
- visualDeckStorageShowBannerCardComboBox = _showBannerCardComboBox;
- settings->setValue("interface/visualdeckstorageshowbannercardcombobox", visualDeckStorageShowBannerCardComboBox);
- emit visualDeckStorageShowBannerCardComboBoxChanged(visualDeckStorageShowBannerCardComboBox);
-}
-
-void SettingsCache::setVisualDeckStorageShowTagsOnDeckPreviews(QT_STATE_CHANGED_T _showTags)
-{
- visualDeckStorageShowTagsOnDeckPreviews = _showTags;
- settings->setValue("interface/visualdeckstorageshowtagsondeckpreviews", visualDeckStorageShowTagsOnDeckPreviews);
- emit visualDeckStorageShowTagsOnDeckPreviewsChanged(visualDeckStorageShowTagsOnDeckPreviews);
-}
-
-void SettingsCache::setVisualDeckStorageCardSize(int _visualDeckStorageCardSize)
-{
- visualDeckStorageCardSize = _visualDeckStorageCardSize;
- settings->setValue("interface/visualdeckstoragecardsize", visualDeckStorageCardSize);
- emit visualDeckStorageCardSizeChanged();
-}
-
-void SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities(
- QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities)
-{
- visualDeckStorageDrawUnusedColorIdentities = _visualDeckStorageDrawUnusedColorIdentities;
- settings->setValue("interface/visualdeckstoragedrawunusedcoloridentities",
- visualDeckStorageDrawUnusedColorIdentities);
- emit visualDeckStorageDrawUnusedColorIdentitiesChanged(visualDeckStorageDrawUnusedColorIdentities);
-}
-
-void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity)
-{
- visualDeckStorageUnusedColorIdentitiesOpacity = _visualDeckStorageUnusedColorIdentitiesOpacity;
- settings->setValue("interface/visualdeckstorageunusedcoloridentitiesopacity",
- visualDeckStorageUnusedColorIdentitiesOpacity);
- emit visualDeckStorageUnusedColorIdentitiesOpacityChanged(visualDeckStorageUnusedColorIdentitiesOpacity);
-}
-
-void SettingsCache::setVisualDeckStorageTooltipType(int value)
-{
- visualDeckStorageTooltipType = value;
- settings->setValue("interface/visualdeckstoragetooltiptype", visualDeckStorageTooltipType);
-}
-
-void SettingsCache::setVisualDeckStoragePromptForConversion(bool _visualDeckStoragePromptForConversion)
-{
- visualDeckStoragePromptForConversion = _visualDeckStoragePromptForConversion;
- settings->setValue("interface/visualdeckstoragepromptforconversion", visualDeckStoragePromptForConversion);
-}
-
-void SettingsCache::setVisualDeckStorageAlwaysConvert(bool _visualDeckStorageAlwaysConvert)
-{
- visualDeckStorageAlwaysConvert = _visualDeckStorageAlwaysConvert;
- settings->setValue("interface/visualdeckstoragealwaysconvert", visualDeckStorageAlwaysConvert);
-}
-
-void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T value)
-{
- visualDeckStorageInGame = value;
- settings->setValue("interface/visualdeckstorageingame", visualDeckStorageInGame);
- emit visualDeckStorageInGameChanged(visualDeckStorageInGame);
-}
-
-void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T value)
-{
- visualDeckStorageSelectionAnimation = value;
- settings->setValue("interface/visualdeckstorageselectionanimation", visualDeckStorageSelectionAnimation);
- emit visualDeckStorageSelectionAnimationChanged(visualDeckStorageSelectionAnimation);
-}
-
-void SettingsCache::setVisualDeckEditorCardSize(int _visualDeckEditorCardSize)
-{
- visualDeckEditorCardSize = _visualDeckEditorCardSize;
- settings->setValue("interface/visualdeckeditorcardsize", visualDeckEditorCardSize);
- emit visualDeckEditorCardSizeChanged();
-}
-
-void SettingsCache::setVisualDatabaseDisplayCardSize(int _visualDatabaseDisplayCardSize)
-{
- visualDatabaseDisplayCardSize = _visualDatabaseDisplayCardSize;
- settings->setValue("interface/visualdatabasedisplaycardsize", visualDatabaseDisplayCardSize);
- emit visualDatabaseDisplayCardSizeChanged();
-}
-
-void SettingsCache::setEDHRecCardSize(int _edhrecCardSize)
-{
- edhrecCardSize = _edhrecCardSize;
- settings->setValue("interface/edhreccardsize", edhrecCardSize);
- emit edhRecCardSizeChanged();
-}
-
-void SettingsCache::setArchidektPreviewCardSize(int _archidektPreviewCardSize)
-{
- archidektPreviewSize = _archidektPreviewCardSize;
- settings->setValue("interface/archidektpreviewsize", archidektPreviewSize);
- emit archidektPreviewSizeChanged();
-}
-
-void SettingsCache::setDefaultDeckEditorType(int value)
-{
- defaultDeckEditorType = value;
- settings->setValue("interface/defaultDeckEditorType", defaultDeckEditorType);
-}
-
-void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T _enabled)
-{
- visualDatabaseDisplayFilterToMostRecentSetsEnabled = _enabled;
- settings->setValue("interface/visualdatabasedisplayfiltertomostrecentsetsenabled",
- visualDatabaseDisplayFilterToMostRecentSetsEnabled);
- emit visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged(visualDatabaseDisplayFilterToMostRecentSetsEnabled);
-}
-
-void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int _amount)
-{
- visualDatabaseDisplayFilterToMostRecentSetsAmount = _amount;
- settings->setValue("interface/visualdatabasedisplayfiltertomostrecentsetsamount",
- visualDatabaseDisplayFilterToMostRecentSetsAmount);
- emit visualDatabaseDisplayFilterToMostRecentSetsAmountChanged(visualDatabaseDisplayFilterToMostRecentSetsAmount);
-}
-
-void SettingsCache::setVisualDeckEditorSampleHandSize(int _amount)
-{
- visualDeckEditorSampleHandSize = _amount;
- settings->setValue("interface/visualdeckeditorsamplehandsize", visualDeckEditorSampleHandSize);
- emit visualDeckEditorSampleHandSizeAmountChanged(visualDeckEditorSampleHandSize);
-}
-
-void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand)
-{
- horizontalHand = static_cast(_horizontalHand);
- settings->setValue("hand/horizontal", horizontalHand);
- emit horizontalHandChanged();
-}
-
-void SettingsCache::setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate)
-{
- invertVerticalCoordinate = static_cast(_invertVerticalCoordinate);
- settings->setValue("table/invert_vertical", invertVerticalCoordinate);
- emit invertVerticalCoordinateChanged();
-}
-
-void SettingsCache::setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout)
-{
- minPlayersForMultiColumnLayout = _minPlayersForMultiColumnLayout;
- settings->setValue("interface/min_players_multicolumn", minPlayersForMultiColumnLayout);
- emit minPlayersForMultiColumnLayoutChanged();
-}
-
-void SettingsCache::setTapAnimation(QT_STATE_CHANGED_T _tapAnimation)
-{
- tapAnimation = static_cast(_tapAnimation);
- settings->setValue("cards/tapanimation", tapAnimation);
-}
-
-void SettingsCache::setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T _autoRotateSidewaysLayoutCards)
-{
- autoRotateSidewaysLayoutCards = static_cast(_autoRotateSidewaysLayoutCards);
- settings->setValue("cards/autorotatesidewayslayoutcards", autoRotateSidewaysLayoutCards);
-}
-
-void SettingsCache::setOpenDeckInNewTab(QT_STATE_CHANGED_T _openDeckInNewTab)
-{
- openDeckInNewTab = static_cast(_openDeckInNewTab);
- settings->setValue("editor/openDeckInNewTab", openDeckInNewTab);
-}
-
-void SettingsCache::setRewindBufferingMs(int _rewindBufferingMs)
-{
- rewindBufferingMs = _rewindBufferingMs;
- settings->setValue("replay/rewindBufferingMs", rewindBufferingMs);
-}
-
-void SettingsCache::setStyleUserList(QT_STATE_CHANGED_T _styleUserList)
-{
- styleUserList = static_cast(_styleUserList);
- settings->setValue("appearance/styleUserList", styleUserList);
- emit styleUserListChanged();
-}
-
-void SettingsCache::setChatMention(QT_STATE_CHANGED_T _chatMention)
-{
- chatMention = static_cast(_chatMention);
- settings->setValue("chat/mention", chatMention);
-}
-
-void SettingsCache::setChatMentionCompleter(const QT_STATE_CHANGED_T _enableMentionCompleter)
-{
- chatMentionCompleter = (bool)_enableMentionCompleter;
- settings->setValue("chat/mentioncompleter", chatMentionCompleter);
- emit chatMentionCompleterChanged();
-}
-
-void SettingsCache::setChatMentionForeground(QT_STATE_CHANGED_T _chatMentionForeground)
-{
- chatMentionForeground = static_cast(_chatMentionForeground);
- settings->setValue("chat/mentionforeground", chatMentionForeground);
-}
-
-void SettingsCache::setChatHighlightForeground(QT_STATE_CHANGED_T _chatHighlightForeground)
-{
- chatHighlightForeground = static_cast(_chatHighlightForeground);
- settings->setValue("chat/highlightforeground", chatHighlightForeground);
-}
-
-void SettingsCache::setChatMentionColor(const QString &_chatMentionColor)
-{
- chatMentionColor = _chatMentionColor;
- settings->setValue("chat/mentioncolor", chatMentionColor);
-}
-
-void SettingsCache::setChatHighlightColor(const QString &_chatHighlightColor)
-{
- chatHighlightColor = _chatHighlightColor;
- settings->setValue("chat/highlightcolor", chatHighlightColor);
-}
-
-void SettingsCache::setZoneViewGroupByIndex(int _zoneViewGroupByIndex)
-{
- zoneViewGroupByIndex = _zoneViewGroupByIndex;
- settings->setValue("zoneview/groupby", zoneViewGroupByIndex);
-}
-
-void SettingsCache::setZoneViewSortByIndex(int _zoneViewSortByIndex)
-{
- zoneViewSortByIndex = _zoneViewSortByIndex;
- settings->setValue("zoneview/sortby", zoneViewSortByIndex);
-}
-
-void SettingsCache::setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView)
-{
- zoneViewPileView = static_cast(_zoneViewPileView);
- settings->setValue("zoneview/pileview", zoneViewPileView);
-}
-
-void SettingsCache::setSoundEnabled(QT_STATE_CHANGED_T _soundEnabled)
-{
- soundEnabled = static_cast(_soundEnabled);
- settings->setValue("sound/enabled", soundEnabled);
- emit soundEnabledChanged();
-}
-
-void SettingsCache::setSoundThemeName(const QString &_soundThemeName)
-{
- soundThemeName = _soundThemeName;
- settings->setValue("sound/theme", soundThemeName);
- emit soundThemeChanged();
-}
-
-void SettingsCache::setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers)
-{
- ignoreUnregisteredUsers = static_cast(_ignoreUnregisteredUsers);
- settings->setValue("chat/ignore_unregistered", ignoreUnregisteredUsers);
-}
-
-void SettingsCache::setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages)
-{
- ignoreUnregisteredUserMessages = static_cast(_ignoreUnregisteredUserMessages);
- settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages);
-}
-
-void SettingsCache::setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages)
-{
- ignoreNonBuddyUserMessages = static_cast(_ignoreNonBuddyUserMessages);
- settings->setValue("chat/ignore_nonbuddy_messages", ignoreNonBuddyUserMessages);
-}
-
-void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize)
-{
- pixmapCacheSize = _pixmapCacheSize;
- settings->setValue("personal/pixmapCacheSize", pixmapCacheSize);
- emit pixmapCacheSizeChanged(pixmapCacheSize);
-}
-
-void SettingsCache::setCardImageCacheMethod(const CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod)
-{
- cardPictureLoaderCacheMethod = static_cast(_cardImageCachingMethod);
- settings->setValue("personal/cardPictureLoaderCacheMethod", cardPictureLoaderCacheMethod);
- emit cardPictureLoaderCacheMethodChanged(cardPictureLoaderCacheMethod);
-}
-
-void SettingsCache::setNetworkCacheSizeInMB(const int _networkCacheSize)
-{
- networkCacheSize = _networkCacheSize;
- settings->setValue("personal/networkCacheSize", networkCacheSize);
- emit networkCacheSizeChanged(networkCacheSize);
-}
-
-void SettingsCache::setNetworkRedirectCacheTtl(const int _redirectCacheTtl)
-{
- redirectCacheTtl = _redirectCacheTtl;
- settings->setValue("personal/redirectCacheSize", redirectCacheTtl);
- emit redirectCacheTtlChanged(redirectCacheTtl);
-}
-
-void SettingsCache::setLocalCardImageStorageNamingScheme(
- const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme)
-{
- localCardImageStorageNamingScheme = static_cast(_localCardImageStorageNamingScheme);
- settings->setValue("personal/localCardImageStorageNamingScheme", localCardImageStorageNamingScheme);
- emit localCardImageStorageNamingSchemeChanged(localCardImageStorageNamingScheme);
-}
-
-void SettingsCache::setClientID(const QString &_clientID)
-{
- clientID = _clientID;
- settings->setValue("personal/clientid", clientID);
-}
-
-void SettingsCache::setClientVersion(const QString &_clientVersion)
-{
- clientVersion = _clientVersion;
- settings->setValue("personal/clientversion", clientVersion);
-}
-
QStringList SettingsCache::getCountries() const
{
static const QStringList countries = {
@@ -1210,236 +194,264 @@ QStringList SettingsCache::getCountries() const
return countries;
}
-void SettingsCache::setGameDescription(const QString _gameDescription)
-{
- gameDescription = _gameDescription;
- settings->setValue("game/gamedescription", gameDescription);
-}
-
-void SettingsCache::setMaxPlayers(const int _maxPlayers)
-{
- maxPlayers = _maxPlayers;
- settings->setValue("game/maxplayers", maxPlayers);
-}
-
-void SettingsCache::setGameTypes(const QString _gameTypes)
-{
- gameTypes = _gameTypes;
- settings->setValue("game/gametypes", gameTypes);
-}
-
-void SettingsCache::setOnlyBuddies(const bool _onlyBuddies)
-{
- onlyBuddies = _onlyBuddies;
- settings->setValue("game/onlybuddies", onlyBuddies);
-}
-
-void SettingsCache::setOnlyRegistered(const bool _onlyRegistered)
-{
- onlyRegistered = _onlyRegistered;
- settings->setValue("game/onlyregistered", onlyRegistered);
-}
-
-void SettingsCache::setSpectatorsAllowed(const bool _spectatorsAllowed)
-{
- spectatorsAllowed = _spectatorsAllowed;
- settings->setValue("game/spectatorsallowed", spectatorsAllowed);
-}
-
-void SettingsCache::setSpectatorsNeedPassword(const bool _spectatorsNeedPassword)
-{
- spectatorsNeedPassword = _spectatorsNeedPassword;
- settings->setValue("game/spectatorsneedpassword", spectatorsNeedPassword);
-}
-
-void SettingsCache::setSpectatorsCanTalk(const bool _spectatorsCanTalk)
-{
- spectatorsCanTalk = _spectatorsCanTalk;
- settings->setValue("game/spectatorscantalk", spectatorsCanTalk);
-}
-
-void SettingsCache::setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything)
-{
- spectatorsCanSeeEverything = _spectatorsCanSeeEverything;
- settings->setValue("game/spectatorscanseeeverything", spectatorsCanSeeEverything);
-}
-
-void SettingsCache::setCreateGameAsSpectator(const bool _createGameAsSpectator)
-{
- createGameAsSpectator = _createGameAsSpectator;
- settings->setValue("game/creategameasspectator", createGameAsSpectator);
-}
-
-void SettingsCache::setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal)
-{
- defaultStartingLifeTotal = _defaultStartingLifeTotal;
- settings->setValue("game/defaultstartinglifetotal", defaultStartingLifeTotal);
-}
-
-void SettingsCache::setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad)
-{
- shareDecklistsOnLoad = _shareDecklistsOnLoad;
- settings->setValue("game/sharedecklistsonload", shareDecklistsOnLoad);
-}
-
-void SettingsCache::setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value)
-{
- checkUpdatesOnStartup = static_cast(value);
- settings->setValue("personal/startupUpdateCheck", checkUpdatesOnStartup);
-}
-
-void SettingsCache::setStartupCardUpdateCheckPromptForUpdate(bool value)
-{
- startupCardUpdateCheckPromptForUpdate = value;
- settings->setValue("personal/startupCardUpdateCheckPromptForUpdate", startupCardUpdateCheckPromptForUpdate);
-}
-
-void SettingsCache::setStartupCardUpdateCheckAlwaysUpdate(bool value)
-{
- startupCardUpdateCheckAlwaysUpdate = value;
- settings->setValue("personal/startupCardUpdateCheckAlwaysUpdate", startupCardUpdateCheckAlwaysUpdate);
-}
-
-void SettingsCache::setCardUpdateCheckInterval(int value)
-{
- cardUpdateCheckInterval = value;
- settings->setValue("personal/cardUpdateCheckInterval", cardUpdateCheckInterval);
-}
-
-void SettingsCache::setLastCardUpdateCheck(QDate value)
-{
- lastCardUpdateCheck = value;
- settings->setValue("personal/lastCardUpdateCheck", lastCardUpdateCheck);
-}
-
-void SettingsCache::setAlwaysEnableNewSets(bool value)
-{
- alwaysEnableNewSets = value;
- settings->setValue("personal/alwaysEnableNewSets", alwaysEnableNewSets);
-}
-
-void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
-{
- rememberGameSettings = _rememberGameSettings;
- settings->setValue("game/remembergamesettings", rememberGameSettings);
-}
-
-void SettingsCache::setLocalGameRememberSettings(bool value)
-{
- localGameRememberSettings = value;
- settings->setValue("localgameoptions/remembersettings", value);
-}
-
-void SettingsCache::setLocalGameMaxPlayers(int value)
-{
- localGameMaxPlayers = value;
- settings->setValue("localgameoptions/maxplayers", value);
-}
-
-void SettingsCache::setLocalGameStartingLifeTotal(int value)
-{
- localGameStartingLifeTotal = value;
- settings->setValue("localgameoptions/startinglifetotal", value);
-}
-
-void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate)
-{
- notifyAboutUpdates = static_cast(_notifyaboutupdate);
- settings->setValue("personal/updatenotification", notifyAboutUpdates);
-}
-
-void SettingsCache::setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion)
-{
- notifyAboutNewVersion = static_cast(_notifyaboutnewversion);
- settings->setValue("personal/newversionnotification", notifyAboutNewVersion);
-}
-
-void SettingsCache::setDownloadSpoilerStatus(bool _spoilerStatus)
-{
- mbDownloadSpoilers = _spoilerStatus;
- settings->setValue("personal/downloadspoilers", mbDownloadSpoilers);
- emit downloadSpoilerStatusChanged();
-}
-
-void SettingsCache::setUpdateReleaseChannelIndex(int value)
-{
- updateReleaseChannel = value;
- settings->setValue("personal/updatereleasechannel", updateReleaseChannel);
-}
-
-void SettingsCache::setMaxFontSize(int _max)
-{
- maxFontSize = _max;
- settings->setValue("game/maxfontsize", maxFontSize);
-}
-
-void SettingsCache::setRoundCardCorners(bool _roundCardCorners)
-{
- if (_roundCardCorners == roundCardCorners) {
- return;
- }
-
- roundCardCorners = _roundCardCorners;
- settings->setValue("cards/roundcardcorners", _roundCardCorners);
- emit roundCardCornersChanged(roundCardCorners);
-}
-
-void SettingsCache::setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount)
-{
- showDragSelectionCount = static_cast(_showDragSelectionCount);
- settings->setValue("interface/showlassoselectioncount", showDragSelectionCount);
-}
-
-void SettingsCache::setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount)
-{
- showTotalSelectionCount = static_cast(_showTotalSelectionCount);
- settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount);
-}
-
-void SettingsCache::setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally)
-{
- showSubtypeSelectionTally = static_cast(_showSubtypeSelectionTally);
- settings->setValue("interface/showsubtypeselectiontally", showSubtypeSelectionTally);
-}
-
void SettingsCache::loadPaths()
{
QString dataPath = getDataPath();
- deckPath = getSafeConfigPath("paths/decks", dataPath + "/decks/");
- filtersPath = getSafeConfigPath("paths/filters", dataPath + "/filters/");
- replaysPath = getSafeConfigPath("paths/replays", dataPath + "/replays/");
- themesPath = getSafeConfigPath("paths/themes", dataPath + "/themes/");
- picsPath = getSafeConfigPath("paths/pics", dataPath + "/pics/");
- redirectCachePath = getSafeConfigPath("paths/redirects", getCachePath() + "/redirects/");
- // this has never been exposed as an user-configurable setting
- if (picsPath.endsWith("/")) {
- customPicsPath = getSafeConfigPath("paths/custompics", picsPath + "CUSTOM/");
- } else {
- customPicsPath = getSafeConfigPath("paths/custompics", picsPath + "/CUSTOM/");
- }
- customCardDatabasePath = getSafeConfigPath("paths/customsets", dataPath + "/customsets/");
+ QSettings pathsIni(getSettingsPath() + "paths.ini", QSettings::IniFormat);
- cardDatabasePath = getSafeConfigFilePath("paths/carddatabase", dataPath + "/cards.xml");
- tokenDatabasePath = getSafeConfigFilePath("paths/tokendatabase", dataPath + "/tokens.xml");
- spoilerDatabasePath = getSafeConfigFilePath("paths/spoilerdatabase", dataPath + "/spoiler.xml");
+ auto computePath = [&](const QString &key, const QString &defaultPath) -> QString {
+ QString val = pathsIni.value("paths/" + key).toString();
+ if (val.isEmpty() || !QDir(val).exists()) {
+ if (!QDir().mkpath(defaultPath)) {
+ qCInfo(SettingsCacheLog) << "[SettingsCache] Could not create folder:" << defaultPath;
+ }
+ val = defaultPath;
+ pathsIni.setValue("paths/" + key, val);
+ }
+ return val;
+ };
+
+ auto computeFilePath = [&](const QString &key, const QString &defaultPath) -> QString {
+ QString val = pathsIni.value("paths/" + key).toString();
+ if (!QFile::exists(val) || val.isEmpty()) {
+ val = defaultPath;
+ pathsIni.setValue("paths/" + key, val);
+ }
+ return val;
+ };
+
+ computePath("decks", dataPath + "/decks/");
+ computePath("filters", dataPath + "/filters/");
+ computePath("replays", dataPath + "/replays/");
+ computePath("themes", dataPath + "/themes/");
+ computePath("pics", dataPath + "/pics/");
+ computePath("redirects", getCachePath() + "/redirects/");
+
+ // customPicsPath derived from picsPath
+ QString picsPath = pathsIni.value("paths/pics").toString();
+ if (picsPath.endsWith("/")) {
+ computePath("customPics", picsPath + "CUSTOM/");
+ } else {
+ computePath("customPics", picsPath + "/CUSTOM/");
+ }
+
+ computePath("customSets", dataPath + "/customsets/");
+ computeFilePath("cardDatabase", dataPath + "/cards.xml");
+ computeFilePath("tokenDatabase", dataPath + "/tokens.xml");
+ computeFilePath("spoilerDatabase", dataPath + "/spoiler.xml");
}
void SettingsCache::resetPaths()
{
- QStringList databasePaths{customCardDatabasePath, cardDatabasePath, spoilerDatabasePath, tokenDatabasePath};
- QString picsPath_ = picsPath;
- settings->remove("paths"); // removes all keys in paths/*
+ QStringList databasePaths{pathsSettings->getCustomCardDatabasePath(), pathsSettings->getCardDatabasePath(),
+ pathsSettings->getSpoilerCardDatabasePath(), pathsSettings->getTokenDatabasePath()};
+ QString picsPath_ = pathsSettings->getPicsPath();
+
+ QSettings pathsIni(getSettingsPath() + "paths.ini", QSettings::IniFormat);
+ pathsIni.remove("paths");
+
loadPaths();
- if (databasePaths !=
- QStringList{customCardDatabasePath, cardDatabasePath, spoilerDatabasePath, tokenDatabasePath}) {
+
+ if (databasePaths != QStringList{pathsSettings->getCustomCardDatabasePath(), pathsSettings->getCardDatabasePath(),
+ pathsSettings->getSpoilerCardDatabasePath(),
+ pathsSettings->getTokenDatabasePath()}) {
emit cardDatabasePathChanged();
}
- if (picsPath_ != picsPath) {
- emit picsPathChanged();
+ if (picsPath_ != pathsSettings->getPicsPath()) {
+ emit pathsSettings->picsPathChanged();
}
}
+// ICardDatabasePathProvider - delegate to pathsSettings
+QString SettingsCache::getCustomCardDatabasePath() const
+{
+ return pathsSettings->getCustomCardDatabasePath();
+}
+
+QString SettingsCache::getCardDatabasePath() const
+{
+ return pathsSettings->getCardDatabasePath();
+}
+
+QString SettingsCache::getSpoilerCardDatabasePath() const
+{
+ return pathsSettings->getSpoilerCardDatabasePath();
+}
+
+QString SettingsCache::getTokenDatabasePath() const
+{
+ return pathsSettings->getTokenDatabasePath();
+}
+
+// INetworkSettingsProvider - delegate to sub-objects
+int SettingsCache::getKeepAlive() const
+{
+ return networkSettings->getKeepAlive();
+}
+
+int SettingsCache::getTimeOut() const
+{
+ return networkSettings->getTimeOut();
+}
+
+bool SettingsCache::getNotifyAboutUpdates() const
+{
+ return updatesSettings->getNotifyAboutUpdates();
+}
+
+void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures)
+{
+ networkSettings->setKnownMissingFeatures(_knownMissingFeatures);
+}
+
+QString SettingsCache::getKnownMissingFeatures()
+{
+ return networkSettings->getKnownMissingFeatures();
+}
+
+QString SettingsCache::getClientID()
+{
+ return networkSettings->getClientID();
+}
+
+// Release channels
+ReleaseChannel *SettingsCache::getUpdateReleaseChannel() const
+{
+ return releaseChannels.at(qMax(0, updatesSettings->getUpdateReleaseChannelIndex()));
+}
+
+QList SettingsCache::getUpdateReleaseChannels() const
+{
+ return releaseChannels;
+}
+
+int SettingsCache::getUpdateReleaseChannelIndex() const
+{
+ return updatesSettings->getUpdateReleaseChannelIndex();
+}
+
+// Sub-settings accessors
+ShortcutsSettings &SettingsCache::shortcuts() const
+{
+ return *shortcutsSettings;
+}
+
+CardDatabaseSettings &SettingsCache::cardDatabase() const
+{
+ return *cardDatabaseSettings;
+}
+
+ServersSettings &SettingsCache::servers() const
+{
+ return *serversSettings;
+}
+
+MessageSettings &SettingsCache::messages() const
+{
+ return *messageSettings;
+}
+
+GameFiltersSettings &SettingsCache::gameFilters() const
+{
+ return *gameFiltersSettings;
+}
+
+LayoutsSettings &SettingsCache::layouts() const
+{
+ return *layoutsSettings;
+}
+
+DownloadSettings &SettingsCache::downloads() const
+{
+ return *downloadSettings;
+}
+
+RecentsSettings &SettingsCache::recents() const
+{
+ return *recentsSettings;
+}
+
+CardOverrideSettings &SettingsCache::cardOverrides() const
+{
+ return *cardOverrideSettings;
+}
+
+DebugSettings &SettingsCache::debug() const
+{
+ return *debugSettings;
+}
+
CardCounterSettings &SettingsCache::cardCounters() const
{
return *cardCounterSettings;
}
+
+TabsSettings &SettingsCache::tabs() const
+{
+ return *tabsSettings;
+}
+
+SoundSettings &SettingsCache::sound() const
+{
+ return *soundSettings;
+}
+
+GameSettings &SettingsCache::game() const
+{
+ return *gameSettings;
+}
+
+ChatSettings &SettingsCache::chat() const
+{
+ return *chatSettings;
+}
+
+CacheStorageSettings &SettingsCache::cacheStorage() const
+{
+ return *cacheStorageSettings;
+}
+
+UpdatesSettings &SettingsCache::updates() const
+{
+ return *updatesSettings;
+}
+
+PersonalSettings &SettingsCache::personal() const
+{
+ return *personalSettings;
+}
+
+CardsDisplaySettings &SettingsCache::cardsDisplay() const
+{
+ return *cardsDisplaySettings;
+}
+
+InterfaceSettings &SettingsCache::userInterface() const
+{
+ return *interfaceSettings;
+}
+
+PathsSettings &SettingsCache::paths() const
+{
+ return *pathsSettings;
+}
+
+DeckEditorSettings &SettingsCache::deckEditor() const
+{
+ return *deckEditorSettings;
+}
+
+VisualDeckStorageSettings &SettingsCache::visualDeckStorage() const
+{
+ return *visualDeckStorageSettings;
+}
+
+AppearanceSettings &SettingsCache::appearance() const
+{
+ return *appearanceSettings;
+}
+
+NetworkSettings &SettingsCache::network() const
+{
+ return *networkSettings;
+}
diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h
index 29af89587..23cdb4dbf 100644
--- a/cockatrice/src/client/settings/cache_settings.h
+++ b/cockatrice/src/client/settings/cache_settings.h
@@ -7,196 +7,55 @@
#ifndef SETTINGSCACHE_H
#define SETTINGSCACHE_H
-#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h"
-#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h"
-#include "shortcuts_settings.h"
-
-#include
#include
-#include
#include
#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
#include
inline Q_LOGGING_CATEGORY(SettingsCacheLog, "settings_cache");
class ReleaseChannel;
-// In MB (Increments of 64)
-#define PIXMAPCACHE_SIZE_DEFAULT 2048
-#define PIXMAPCACHE_SIZE_MIN 64
-#define PIXMAPCACHE_SIZE_MAX 4096
-
-// In MB
-constexpr int NETWORK_CACHE_SIZE_DEFAULT = 1024 * 4; // 4 GB
-constexpr int NETWORK_CACHE_SIZE_MIN = 1; // 1 MB
-constexpr int NETWORK_CACHE_SIZE_MAX = 1024 * 1024; // 1 TB
-
-// In Days
-#define NETWORK_REDIRECT_CACHE_TTL_DEFAULT 30
-#define NETWORK_REDIRECT_CACHE_TTL_MIN 1
-#define NETWORK_REDIRECT_CACHE_TTL_MAX 90
-
#define DEFAULT_LANG_NAME "English"
#define CLIENT_INFO_NOT_SET "notset"
#define DEFAULT_FONT_SIZE 12
-inline QStringList defaultTags = {
- // Strategies
- "🏃️ Aggro",
- "🧙️ Control",
- "⚔️ Midrange",
- "🌀 Combo",
- "🪓 Mill",
- "🔒 Stax",
- "🗺️ Landfall",
- "🛡️ Pillowfort",
- "🌱 Ramp",
- "⚡ Storm",
- "💀 Aristocrats",
- "☠️ Reanimator",
- "👹 Sacrifice",
- "🔥 Burn",
- "🌟 Lifegain",
- "🔮 Spellslinger",
- "👥 Tokens",
- "🎭 Blink",
- "⏳ Time Manipulation",
- "🌍 Domain",
- "💫 Proliferate",
- "📜 Saga",
- "🎲 Chaos",
- "🪄 Auras",
- "🔫 Pingers",
-
- // Themes
- "👑 Monarch",
- "🚀 Vehicles",
- "💉 Infect",
- "🩸 Madness",
- "🌀 Morph",
-
- // Card Types
- "⚔️ Creature",
- "💎 Artifact",
- "🌔 Enchantment",
- "📖 Sorcery",
- "⚡ Instant",
- "🌌 Planeswalker",
- "🌏 Land",
- "🪄 Aura",
-
- // Kindred Types
- "🐉 Kindred",
- "🧙 Humans",
- "⚔️ Soldiers",
- "🛡️ Knights",
- "🎻 Bards",
- "🧝 Elves",
- "🌲 Dryads",
- "😇 Angels",
- "🎩 Wizards",
- "🧛 Vampires",
- "🦴 Skeletons",
- "💀 Zombies",
- "👹 Demons",
- "👾 Eldrazi",
- "🐉 Dragons",
- "🐠 Merfolk",
- "🦁 Cats",
- "🐺 Wolves",
- "🐺 Werewolves",
- "🦇 Bats",
- "🐀 Rats",
- "🦅 Birds",
- "🦗 Insects",
- "🍄 Fungus",
- "🐚 Sea Creatures",
- "🐗 Boars",
- "🦊 Foxes",
- "🦄 Unicorns",
- "🐘 Elephants",
- "🐻 Bears",
- "🦏 Rhinos",
- "🦂 Scorpions",
-};
-
-class QSettings;
+class CacheStorageSettings;
class CardCounterSettings;
+class CardDatabaseSettings;
+class CardOverrideSettings;
+class CardsDisplaySettings;
+class ChatSettings;
+class CommanderBracketSettings;
+class DebugSettings;
+class DeckEditorSettings;
+class DownloadSettings;
+class GameFiltersSettings;
+class GameSettings;
+class InterfaceSettings;
+class LayoutsSettings;
+class MessageSettings;
+class PathsSettings;
+class PersonalSettings;
+class RecentsSettings;
+class ServersSettings;
+class ShortcutsSettings;
+class SoundSettings;
+class TabsSettings;
+class UpdatesSettings;
+class VisualDeckStorageSettings;
+class AppearanceSettings;
+class NetworkSettings;
+class QSettings;
class SettingsCache : public ICardDatabasePathProvider, public INetworkSettingsProvider
{
Q_OBJECT
signals:
- void langChanged();
- void picsPathChanged();
void themeChanged();
- void homeTabBackgroundSourceChanged();
- void homeTabBackgroundShuffleFrequencyChanged();
- void homeTabDisplayCardNameChanged();
- void picDownloadChanged();
- void showStatusBarChanged(bool state);
- void showGameSelectorFilterToolbarChanged(bool state);
- void displayCardNamesChanged();
- void overrideAllCardArtWithPersonalPreferenceChanged(bool _overrideAllCardArtWithPersonalPreference);
- void bumpSetsWithCardsInDeckToTopChanged();
- void printingSelectorSortOrderChanged();
- void printingSelectorCardSizeChanged();
- void includeRebalancedCardsChanged(bool _includeRebalancedCards);
- void printingSelectorNavigationButtonsVisibleChanged();
- void deckEditorBannerCardComboBoxVisibleChanged(bool _visible);
- void deckEditorTagsWidgetVisibleChanged(bool _visible);
- void visualDeckStorageShowTagFilterChanged(bool _visible);
- void visualDeckStorageDefaultTagsListChanged();
- void visualDeckStorageShowColorIdentityChanged(bool _visible);
- void visualDeckStorageShowBannerCardComboBoxChanged(bool _visible);
- void visualDeckStorageShowTagsOnDeckPreviewsChanged(bool _visible);
- void visualDeckStorageCardSizeChanged();
- void visualDeckStorageDrawUnusedColorIdentitiesChanged(bool _visible);
- void visualDeckStorageUnusedColorIdentitiesOpacityChanged(bool value);
- void visualDeckStorageInGameChanged(bool enabled);
- void visualDeckStorageSelectionAnimationChanged(bool enabled);
- void visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged(bool enabled);
- void visualDatabaseDisplayFilterToMostRecentSetsAmountChanged(int amount);
- void visualDeckEditorSampleHandSizeAmountChanged(int amount);
- void visualDeckEditorCardSizeChanged();
- void visualDatabaseDisplayCardSizeChanged();
- void edhRecCardSizeChanged();
- void archidektPreviewSizeChanged();
- void horizontalHandChanged();
- void handJustificationChanged();
- void invertVerticalCoordinateChanged();
- void minPlayersForMultiColumnLayoutChanged();
- void soundEnabledChanged();
- void soundThemeChanged();
- void ignoreUnregisteredUsersChanged();
- void ignoreUnregisteredUserMessagesChanged();
- void ignoreNonBuddyUserMessagesChanged();
- void pixmapCacheSizeChanged(int newSizeInMBs);
- void networkCacheSizeChanged(int newSizeInMBs);
- void redirectCacheTtlChanged(int newTtl);
- void cardPictureLoaderCacheMethodChanged(int cardPictureLoaderCacheMethod);
- void localCardImageStorageNamingSchemeChanged(int localCardImageStorageNamingScheme);
- void masterVolumeChanged(int value);
- void styleUserListChanged();
- void chatMentionCompleterChanged();
- void downloadSpoilerTimeIndexChanged();
- void downloadSpoilerStatusChanged();
- void useTearOffMenusChanged(bool state);
- void roundCardCornersChanged(bool roundCardCorners);
- void keepGameChatFocusChanged(bool value);
private:
QSettings *settings;
@@ -211,151 +70,30 @@ private:
CardOverrideSettings *cardOverrideSettings;
DebugSettings *debugSettings;
CardCounterSettings *cardCounterSettings;
+ TabsSettings *tabsSettings;
+ SoundSettings *soundSettings;
+ GameSettings *gameSettings;
+ ChatSettings *chatSettings;
+ CacheStorageSettings *cacheStorageSettings;
+ UpdatesSettings *updatesSettings;
+ PersonalSettings *personalSettings;
+ CardsDisplaySettings *cardsDisplaySettings;
+ InterfaceSettings *interfaceSettings;
+ DeckEditorSettings *deckEditorSettings;
+ PathsSettings *pathsSettings;
+ VisualDeckStorageSettings *visualDeckStorageSettings;
+ AppearanceSettings *appearanceSettings;
+ NetworkSettings *networkSettings;
+ CommanderBracketSettings *commanderBracketSettings;
- QString lang;
- QString deckPath, filtersPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath,
- customCardDatabasePath, themesPath, spoilerDatabasePath, tokenDatabasePath, themeName, homeTabBackgroundSource;
- bool tabVisualDeckStorageOpen, tabServerOpen, tabAccountOpen, tabDeckStorageOpen, tabReplaysOpen, tabAdminOpen,
- tabLogOpen;
- bool checkUpdatesOnStartup;
- bool startupCardUpdateCheckPromptForUpdate;
- bool startupCardUpdateCheckAlwaysUpdate;
- bool checkCardUpdatesOnStartup;
- int cardUpdateCheckInterval;
- QDate lastCardUpdateCheck;
- bool alwaysEnableNewSets;
- bool notifyAboutUpdates;
- bool notifyAboutNewVersion;
- bool showTipsOnStartup;
- QList seenTips;
- int homeTabBackgroundShuffleFrequency;
- bool homeTabDisplayCardName;
- bool mbDownloadSpoilers;
- int updateReleaseChannel;
- int maxFontSize;
- bool picDownload;
- bool notificationsEnabled;
- bool spectatorNotificationsEnabled;
- bool buddyConnectNotificationsEnabled;
- bool doubleClickToPlay;
- bool clickPlaysAllSelected;
- bool playToStack;
- bool doNotDeleteArrowsInSubPhases;
- int startingHandSize;
- bool annotateTokens;
- bool showShortcuts;
- bool showGameSelectorFilterToolbar;
- bool displayCardNames;
- bool overrideAllCardArtWithPersonalPreference;
- bool bumpSetsWithCardsInDeckToTop;
- int printingSelectorSortOrder;
- int printingSelectorCardSize;
- bool includeRebalancedCards;
- bool printingSelectorNavigationButtonsVisible;
- bool deckEditorBannerCardComboBoxVisible;
- bool deckEditorTagsWidgetVisible;
- int visualDeckStorageSortingOrder;
- bool visualDeckStorageShowFolders;
- bool visualDeckStorageShowColorIdentity;
- bool visualDeckStorageShowBannerCardComboBox;
- bool visualDeckStorageShowTagsOnDeckPreviews;
- bool visualDeckStorageShowTagFilter;
- QStringList visualDeckStorageDefaultTagsList;
- bool visualDeckStorageSearchFolderNames;
- int visualDeckStorageCardSize;
- int visualDeckEditorCardSize;
- int visualDatabaseDisplayCardSize;
- int edhrecCardSize;
- int archidektPreviewSize;
- bool visualDeckStorageDrawUnusedColorIdentities;
- int visualDeckStorageUnusedColorIdentitiesOpacity;
- int visualDeckStorageTooltipType;
- bool visualDeckStoragePromptForConversion;
- bool visualDeckStorageAlwaysConvert;
- bool visualDeckStorageInGame;
- bool visualDeckStorageSelectionAnimation;
- int defaultDeckEditorType;
- bool visualDatabaseDisplayFilterToMostRecentSetsEnabled;
- int visualDatabaseDisplayFilterToMostRecentSetsAmount;
- int visualDeckEditorSampleHandSize;
- bool horizontalHand;
- bool invertVerticalCoordinate;
- int minPlayersForMultiColumnLayout;
- bool tapAnimation;
- bool autoRotateSidewaysLayoutCards;
- bool openDeckInNewTab;
- int rewindBufferingMs;
- bool styleUserList;
- bool chatMention;
- bool chatMentionCompleter;
- QString chatMentionColor;
- QString chatHighlightColor;
- bool chatMentionForeground;
- bool chatHighlightForeground;
- int zoneViewSortByIndex, zoneViewGroupByIndex;
- bool zoneViewPileView;
- bool soundEnabled;
- QString soundThemeName;
- bool ignoreUnregisteredUsers;
- bool ignoreUnregisteredUserMessages;
- bool ignoreNonBuddyUserMessages;
- QString picUrl;
- QString picUrlFallback;
- QString clientID;
- QString clientVersion;
- QString knownMissingFeatures;
- bool useTearOffMenus;
- int cardViewInitialRowsMax;
- int cardViewExpandedRowsMax;
- bool closeEmptyCardView;
- bool focusCardViewSearchBar;
- bool keepGameChatFocus;
- int pixmapCacheSize;
- int networkCacheSize;
- int redirectCacheTtl;
- int cardPictureLoaderCacheMethod;
- int localCardImageStorageNamingScheme;
- bool scaleCards;
- int verticalCardOverlapPercent;
- bool showMessagePopups;
- bool showMentionPopups;
- bool roomHistory;
- bool leftJustified;
- int masterVolume;
- int cardInfoViewMode;
- QString highlightWords;
- QString gameDescription;
- int maxPlayers;
- QString gameTypes;
- bool onlyBuddies;
- bool onlyRegistered;
- bool spectatorsAllowed;
- bool spectatorsNeedPassword;
- bool spectatorsCanTalk;
- bool spectatorsCanSeeEverything;
- bool createGameAsSpectator;
- int defaultStartingLifeTotal;
- bool shareDecklistsOnLoad;
- int keepalive;
- int timeout;
- void translateLegacySettings();
- [[nodiscard]] QString getSafeConfigPath(QString configEntry, QString defaultPath) const;
- [[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const;
- void loadPaths();
- bool rememberGameSettings;
-
- // Local game settings (separate from server game settings in game/*)
- bool localGameRememberSettings;
- int localGameMaxPlayers;
- int localGameStartingLifeTotal;
+ QString themeName;
QList releaseChannels;
bool isPortableBuild;
- bool roundCardCorners;
- bool showStatusBar;
- bool showDragSelectionCount;
- bool showTotalSelectionCount;
- bool showSubtypeSelectionTally;
+
+ [[nodiscard]] QString getSafeConfigPath(QString configEntry, QString defaultPath) const;
+ [[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const;
+ void loadPaths();
public:
SettingsCache();
@@ -363,824 +101,71 @@ public:
QString getSettingsPath();
[[nodiscard]] QString getCachePath() const;
[[nodiscard]] QString getNetworkCachePath() const;
- [[nodiscard]] QString getLang() const
- {
- return lang;
- }
- [[nodiscard]] QString getDeckPath() const
- {
- return deckPath;
- }
- [[nodiscard]] QString getFiltersPath() const
- {
- return filtersPath;
- }
- [[nodiscard]] QString getReplaysPath() const
- {
- return replaysPath;
- }
- [[nodiscard]] QString getThemesPath() const
- {
- return themesPath;
- }
- [[nodiscard]] QString getPicsPath() const
- {
- return picsPath;
- }
- [[nodiscard]] QString getRedirectCachePath() const
- {
- return redirectCachePath;
- }
- [[nodiscard]] QString getCustomPicsPath() const
- {
- return customPicsPath;
- }
- [[nodiscard]] QString getCustomCardDatabasePath() const override
- {
- return customCardDatabasePath;
- }
- [[nodiscard]] QString getCardDatabasePath() const override
- {
- return cardDatabasePath;
- }
- [[nodiscard]] QString getSpoilerCardDatabasePath() const override
- {
- return spoilerDatabasePath;
- }
- [[nodiscard]] QString getTokenDatabasePath() const override
- {
- return tokenDatabasePath;
- }
+
+ // ICardDatabasePathProvider - delegate to pathsSettings
+ [[nodiscard]] QString getCustomCardDatabasePath() const override;
+ [[nodiscard]] QString getCardDatabasePath() const override;
+ [[nodiscard]] QString getSpoilerCardDatabasePath() const override;
+ [[nodiscard]] QString getTokenDatabasePath() const override;
+
+ // INetworkSettingsProvider - delegate to sub-objects
+ [[nodiscard]] int getKeepAlive() const override;
+ [[nodiscard]] int getTimeOut() const override;
+ [[nodiscard]] bool getNotifyAboutUpdates() const override;
+ void setKnownMissingFeatures(const QString &_knownMissingFeatures) override;
+ [[nodiscard]] QString getKnownMissingFeatures() override;
+ QString getClientID() override;
+
[[nodiscard]] QString getThemeName() const
{
return themeName;
}
- [[nodiscard]] QString getHomeTabBackgroundSource() const
- {
- return homeTabBackgroundSource;
- }
- [[nodiscard]] int getHomeTabBackgroundShuffleFrequency() const
- {
- return homeTabBackgroundShuffleFrequency;
- }
- [[nodiscard]] bool getHomeTabDisplayCardName() const
- {
- return homeTabDisplayCardName;
- }
- [[nodiscard]] bool getTabVisualDeckStorageOpen() const
- {
- return tabVisualDeckStorageOpen;
- }
- [[nodiscard]] bool getTabServerOpen() const
- {
- return tabServerOpen;
- }
- [[nodiscard]] bool getTabAccountOpen() const
- {
- return tabAccountOpen;
- }
- [[nodiscard]] bool getTabDeckStorageOpen() const
- {
- return tabDeckStorageOpen;
- }
- [[nodiscard]] bool getTabReplaysOpen() const
- {
- return tabReplaysOpen;
- }
- [[nodiscard]] bool getTabAdminOpen() const
- {
- return tabAdminOpen;
- }
- [[nodiscard]] bool getTabLogOpen() const
- {
- return tabLogOpen;
- }
- [[nodiscard]] QString getChatMentionColor() const
- {
- return chatMentionColor;
- }
- [[nodiscard]] QString getChatHighlightColor() const
- {
- return chatHighlightColor;
- }
- [[nodiscard]] bool getPicDownload() const
- {
- return picDownload;
- }
- [[nodiscard]] bool getShowStatusBar() const
- {
- return showStatusBar;
- }
- [[nodiscard]] bool getShowDragSelectionCount() const
- {
- return showDragSelectionCount;
- }
- [[nodiscard]] bool getShowTotalSelectionCount() const
- {
- return showTotalSelectionCount;
- }
- [[nodiscard]] bool getShowSubtypeSelectionTally() const
- {
- return showSubtypeSelectionTally;
- }
- [[nodiscard]] bool getNotificationsEnabled() const
- {
- return notificationsEnabled;
- }
- [[nodiscard]] bool getSpectatorNotificationsEnabled() const
- {
- return spectatorNotificationsEnabled;
- }
- [[nodiscard]] bool getBuddyConnectNotificationsEnabled() const
- {
- return buddyConnectNotificationsEnabled;
- }
- [[nodiscard]] bool getCheckUpdatesOnStartup() const
- {
- return checkUpdatesOnStartup;
- }
- bool getStartupCardUpdateCheckPromptForUpdate()
- {
- return startupCardUpdateCheckPromptForUpdate;
- }
- bool getStartupCardUpdateCheckAlwaysUpdate()
- {
- return startupCardUpdateCheckAlwaysUpdate;
- }
- [[nodiscard]] int getCardUpdateCheckInterval() const
- {
- return cardUpdateCheckInterval;
- }
- [[nodiscard]] QDate getLastCardUpdateCheck() const
- {
- return lastCardUpdateCheck;
- }
- [[nodiscard]] bool getCardUpdateCheckRequired() const
- {
- return getLastCardUpdateCheck().daysTo(QDateTime::currentDateTime().date()) >= getCardUpdateCheckInterval() &&
- getLastCardUpdateCheck() != QDateTime::currentDateTime().date();
- }
- [[nodiscard]] bool getAlwaysEnableNewSets() const
- {
- return alwaysEnableNewSets;
- }
- [[nodiscard]] bool getNotifyAboutUpdates() const override
- {
- return notifyAboutUpdates;
- }
- [[nodiscard]] bool getNotifyAboutNewVersion() const
- {
- return notifyAboutNewVersion;
- }
- [[nodiscard]] bool getShowTipsOnStartup() const
- {
- return showTipsOnStartup;
- }
- [[nodiscard]] QList getSeenTips() const
- {
- return seenTips;
- }
- [[nodiscard]] int getUpdateReleaseChannelIndex() const
- {
- return updateReleaseChannel;
- }
- [[nodiscard]] ReleaseChannel *getUpdateReleaseChannel() const
- {
- return releaseChannels.at(qMax(0, updateReleaseChannel));
- }
- [[nodiscard]] QList getUpdateReleaseChannels() const
- {
- return releaseChannels;
- }
- [[nodiscard]] bool getDoubleClickToPlay() const
- {
- return doubleClickToPlay;
- }
- [[nodiscard]] bool getClickPlaysAllSelected() const
- {
- return clickPlaysAllSelected;
- }
- [[nodiscard]] bool getPlayToStack() const
- {
- return playToStack;
- }
- [[nodiscard]] bool getDoNotDeleteArrowsInSubPhases() const
- {
- return doNotDeleteArrowsInSubPhases;
- }
- [[nodiscard]] int getStartingHandSize() const
- {
- return startingHandSize;
- }
- [[nodiscard]] bool getAnnotateTokens() const
- {
- return annotateTokens;
- }
- [[nodiscard]] bool getShowShortcuts() const
- {
- return showShortcuts;
- }
- [[nodiscard]] bool getShowGameSelectorFilterToolbar() const
- {
- return showGameSelectorFilterToolbar;
- }
- [[nodiscard]] bool getDisplayCardNames() const
- {
- return displayCardNames;
- }
- [[nodiscard]] bool getOverrideAllCardArtWithPersonalPreference() const
- {
- return overrideAllCardArtWithPersonalPreference;
- }
- [[nodiscard]] bool getBumpSetsWithCardsInDeckToTop() const
- {
- return bumpSetsWithCardsInDeckToTop;
- }
- [[nodiscard]] int getPrintingSelectorSortOrder() const
- {
- return printingSelectorSortOrder;
- }
- [[nodiscard]] int getPrintingSelectorCardSize() const
- {
- return printingSelectorCardSize;
- }
- [[nodiscard]] bool getIncludeRebalancedCards() const
- {
- return includeRebalancedCards;
- }
- [[nodiscard]] bool getPrintingSelectorNavigationButtonsVisible() const
- {
- return printingSelectorNavigationButtonsVisible;
- }
- [[nodiscard]] bool getDeckEditorBannerCardComboBoxVisible() const
- {
- return deckEditorBannerCardComboBoxVisible;
- }
- [[nodiscard]] bool getDeckEditorTagsWidgetVisible() const
- {
- return deckEditorTagsWidgetVisible;
- }
- [[nodiscard]] int getVisualDeckStorageSortingOrder() const
- {
- return visualDeckStorageSortingOrder;
- }
- [[nodiscard]] bool getVisualDeckStorageShowFolders() const
- {
- return visualDeckStorageShowFolders;
- }
- [[nodiscard]] bool getVisualDeckStorageShowTagFilter() const
- {
- return visualDeckStorageShowTagFilter;
- }
- [[nodiscard]] QStringList getVisualDeckStorageDefaultTagsList() const
- {
- return visualDeckStorageDefaultTagsList;
- }
- [[nodiscard]] bool getVisualDeckStorageSearchFolderNames() const
- {
- return visualDeckStorageSearchFolderNames;
- }
- [[nodiscard]] bool getVisualDeckStorageShowColorIdentity() const
- {
- return visualDeckStorageShowColorIdentity;
- }
- [[nodiscard]] bool getVisualDeckStorageShowBannerCardComboBox() const
- {
- return visualDeckStorageShowBannerCardComboBox;
- }
- [[nodiscard]] bool getVisualDeckStorageShowTagsOnDeckPreviews() const
- {
- return visualDeckStorageShowTagsOnDeckPreviews;
- }
- [[nodiscard]] int getVisualDeckStorageCardSize() const
- {
- return visualDeckStorageCardSize;
- }
- [[nodiscard]] bool getVisualDeckStorageDrawUnusedColorIdentities() const
- {
- return visualDeckStorageDrawUnusedColorIdentities;
- }
- [[nodiscard]] int getVisualDeckStorageUnusedColorIdentitiesOpacity() const
- {
- return visualDeckStorageUnusedColorIdentitiesOpacity;
- }
- [[nodiscard]] int getVisualDeckStorageTooltipType() const
- {
- return visualDeckStorageTooltipType;
- }
- [[nodiscard]] bool getVisualDeckStoragePromptForConversion() const
- {
- return visualDeckStoragePromptForConversion;
- }
- [[nodiscard]] bool getVisualDeckStorageAlwaysConvert() const
- {
- return visualDeckStorageAlwaysConvert;
- }
- [[nodiscard]] bool getVisualDeckStorageInGame() const
- {
- return visualDeckStorageInGame;
- }
- [[nodiscard]] bool getVisualDeckStorageSelectionAnimation() const
- {
- return visualDeckStorageSelectionAnimation;
- }
- [[nodiscard]] int getVisualDeckEditorCardSize() const
- {
- return visualDeckEditorCardSize;
- }
- [[nodiscard]] int getVisualDatabaseDisplayCardSize() const
- {
- return visualDatabaseDisplayCardSize;
- }
- [[nodiscard]] int getEDHRecCardSize() const
- {
- return edhrecCardSize;
- }
- [[nodiscard]] int getArchidektPreviewSize() const
- {
- return archidektPreviewSize;
- }
- [[nodiscard]] int getDefaultDeckEditorType() const
- {
- return defaultDeckEditorType;
- }
- [[nodiscard]] bool getVisualDatabaseDisplayFilterToMostRecentSetsEnabled() const
- {
- return visualDatabaseDisplayFilterToMostRecentSetsEnabled;
- }
- [[nodiscard]] int getVisualDatabaseDisplayFilterToMostRecentSetsAmount() const
- {
- return visualDatabaseDisplayFilterToMostRecentSetsAmount;
- }
- [[nodiscard]] int getVisualDeckEditorSampleHandSize() const
- {
- return visualDeckEditorSampleHandSize;
- }
- [[nodiscard]] bool getHorizontalHand() const
- {
- return horizontalHand;
- }
- [[nodiscard]] bool getInvertVerticalCoordinate() const
- {
- return invertVerticalCoordinate;
- }
- [[nodiscard]] int getMinPlayersForMultiColumnLayout() const
- {
- return minPlayersForMultiColumnLayout;
- }
- [[nodiscard]] bool getTapAnimation() const
- {
- return tapAnimation;
- }
- [[nodiscard]] bool getAutoRotateSidewaysLayoutCards() const
- {
- return autoRotateSidewaysLayoutCards;
- }
- [[nodiscard]] bool getOpenDeckInNewTab() const
- {
- return openDeckInNewTab;
- }
- [[nodiscard]] int getRewindBufferingMs() const
- {
- return rewindBufferingMs;
- }
- [[nodiscard]] bool getStyleUserList() const
- {
- return styleUserList;
- }
- [[nodiscard]] bool getChatMention() const
- {
- return chatMention;
- }
- [[nodiscard]] bool getChatMentionCompleter() const
- {
- return chatMentionCompleter;
- }
- [[nodiscard]] bool getChatMentionForeground() const
- {
- return chatMentionForeground;
- }
- [[nodiscard]] bool getChatHighlightForeground() const
- {
- return chatHighlightForeground;
- }
- /**
- * Currently selected index for the `Group by X` QComboBox
- */
- [[nodiscard]] int getZoneViewGroupByIndex() const
- {
- return zoneViewGroupByIndex;
- }
- /**
- * Currently selected index for the `Sort by X` QComboBox
- */
- [[nodiscard]] int getZoneViewSortByIndex() const
- {
- return zoneViewSortByIndex;
- }
- /**
- Returns if the view should be sorted into pile view.
- @return zoneViewPileView if the view should be sorted into pile view.
- */
- [[nodiscard]] bool getZoneViewPileView() const
- {
- return zoneViewPileView;
- }
- [[nodiscard]] bool getSoundEnabled() const
- {
- return soundEnabled;
- }
- [[nodiscard]] QString getSoundThemeName() const
- {
- return soundThemeName;
- }
- [[nodiscard]] bool getIgnoreUnregisteredUsers() const
- {
- return ignoreUnregisteredUsers;
- }
- [[nodiscard]] bool getIgnoreUnregisteredUserMessages() const
- {
- return ignoreUnregisteredUserMessages;
- }
- [[nodiscard]] bool getIgnoreNonBuddyUserMessages() const
- {
- return ignoreNonBuddyUserMessages;
- }
- [[nodiscard]] int getPixmapCacheSize() const
- {
- return pixmapCacheSize;
- }
- [[nodiscard]] CardPictureLoaderCacheMethod::CacheMethod getCardPictureLoaderCacheMethod() const
- {
- return static_cast(cardPictureLoaderCacheMethod);
- }
- [[nodiscard]] int getNetworkCacheSizeInMB() const
- {
- return networkCacheSize;
- }
- [[nodiscard]] int getRedirectCacheTtl() const
- {
- return redirectCacheTtl;
- }
- [[nodiscard]] CardPictureLoaderLocalSchemes::NamingScheme getLocalCardImageStorageNamingScheme() const
- {
- return static_cast(localCardImageStorageNamingScheme);
- }
- [[nodiscard]] bool getScaleCards() const
- {
- return scaleCards;
- }
- [[nodiscard]] int getStackCardOverlapPercent() const
- {
- return verticalCardOverlapPercent;
- }
- [[nodiscard]] bool getShowMessagePopup() const
- {
- return showMessagePopups;
- }
- [[nodiscard]] bool getShowMentionPopup() const
- {
- return showMentionPopups;
- }
- [[nodiscard]] bool getRoomHistory() const
- {
- return roomHistory;
- }
- [[nodiscard]] bool getLeftJustified() const
- {
- return leftJustified;
- }
- [[nodiscard]] int getMasterVolume() const
- {
- return masterVolume;
- }
- [[nodiscard]] int getCardInfoViewMode() const
- {
- return cardInfoViewMode;
- }
+ [[nodiscard]] ReleaseChannel *getUpdateReleaseChannel() const;
+ [[nodiscard]] QList getUpdateReleaseChannels() const;
+ [[nodiscard]] int getUpdateReleaseChannelIndex() const;
+
[[nodiscard]] QStringList getCountries() const;
- [[nodiscard]] QString getHighlightWords() const
- {
- return highlightWords;
- }
- [[nodiscard]] QString getGameDescription() const
- {
- return gameDescription;
- }
- [[nodiscard]] int getMaxPlayers() const
- {
- return maxPlayers;
- }
- [[nodiscard]] QString getGameTypes() const
- {
- return gameTypes;
- }
- [[nodiscard]] bool getOnlyBuddies() const
- {
- return onlyBuddies;
- }
- [[nodiscard]] bool getOnlyRegistered() const
- {
- return onlyRegistered;
- }
- [[nodiscard]] bool getSpectatorsAllowed() const
- {
- return spectatorsAllowed;
- }
- [[nodiscard]] bool getSpectatorsNeedPassword() const
- {
- return spectatorsNeedPassword;
- }
- [[nodiscard]] bool getSpectatorsCanTalk() const
- {
- return spectatorsCanTalk;
- }
- [[nodiscard]] bool getSpectatorsCanSeeEverything() const
- {
- return spectatorsCanSeeEverything;
- }
- [[nodiscard]] int getDefaultStartingLifeTotal() const
- {
- return defaultStartingLifeTotal;
- }
- [[nodiscard]] bool getShareDecklistsOnLoad() const
- {
- return shareDecklistsOnLoad;
- }
- [[nodiscard]] bool getCreateGameAsSpectator() const
- {
- return createGameAsSpectator;
- }
- [[nodiscard]] bool getRememberGameSettings() const
- {
- return rememberGameSettings;
- }
- [[nodiscard]] bool getLocalGameRememberSettings() const
- {
- return localGameRememberSettings;
- }
- [[nodiscard]] int getLocalGameMaxPlayers() const
- {
- return localGameMaxPlayers;
- }
- [[nodiscard]] int getLocalGameStartingLifeTotal() const
- {
- return localGameStartingLifeTotal;
- }
- [[nodiscard]] int getKeepAlive() const override
- {
- return keepalive;
- }
- [[nodiscard]] int getTimeOut() const override
- {
- return timeout;
- }
- [[nodiscard]] int getMaxFontSize() const
- {
- return maxFontSize;
- }
- void setClientID(const QString &clientID);
- void setClientVersion(const QString &clientVersion);
- void setKnownMissingFeatures(const QString &_knownMissingFeatures) override;
- void setUseTearOffMenus(bool _useTearOffMenus);
- void setCardViewInitialRowsMax(int _cardViewInitialRowsMax);
- void setCardViewExpandedRowsMax(int value);
- void setCloseEmptyCardView(QT_STATE_CHANGED_T value);
- void setFocusCardViewSearchBar(QT_STATE_CHANGED_T value);
- void setKeepGameChatFocus(QT_STATE_CHANGED_T value);
- QString getClientID() override
- {
- return clientID;
- }
- QString getClientVersion()
- {
- return clientVersion;
- }
- QString getKnownMissingFeatures() override
- {
- return knownMissingFeatures;
- }
- bool getUseTearOffMenus()
- {
- return useTearOffMenus;
- }
- [[nodiscard]] int getCardViewInitialRowsMax() const
- {
- return cardViewInitialRowsMax;
- }
- [[nodiscard]] int getCardViewExpandedRowsMax() const
- {
- return cardViewExpandedRowsMax;
- }
- [[nodiscard]] bool getCloseEmptyCardView() const
- {
- return closeEmptyCardView;
- }
- [[nodiscard]] bool getFocusCardViewSearchBar() const
- {
- return focusCardViewSearchBar;
- }
- [[nodiscard]] bool getKeepGameChatFocus() const
- {
- return keepGameChatFocus;
- }
- [[nodiscard]] ShortcutsSettings &shortcuts() const
- {
- return *shortcutsSettings;
- }
- [[nodiscard]] CardDatabaseSettings *cardDatabase() const
- {
- return cardDatabaseSettings;
- }
- [[nodiscard]] ServersSettings &servers() const
- {
- return *serversSettings;
- }
- [[nodiscard]] MessageSettings &messages() const
- {
- return *messageSettings;
- }
- [[nodiscard]] GameFiltersSettings &gameFilters() const
- {
- return *gameFiltersSettings;
- }
- [[nodiscard]] LayoutsSettings &layouts() const
- {
- return *layoutsSettings;
- }
- [[nodiscard]] DownloadSettings &downloads() const
- {
- return *downloadSettings;
- }
- [[nodiscard]] RecentsSettings &recents() const
- {
- return *recentsSettings;
- }
- [[nodiscard]] CardOverrideSettings &cardOverrides() const
- {
- return *cardOverrideSettings;
- }
- [[nodiscard]] DebugSettings &debug() const
- {
- return *debugSettings;
- }
+
+ [[nodiscard]] ShortcutsSettings &shortcuts() const;
+ [[nodiscard]] CardDatabaseSettings &cardDatabase() const;
+ [[nodiscard]] ServersSettings &servers() const;
+ [[nodiscard]] MessageSettings &messages() const;
+ [[nodiscard]] GameFiltersSettings &gameFilters() const;
+ [[nodiscard]] LayoutsSettings &layouts() const;
+ [[nodiscard]] DownloadSettings &downloads() const;
+ [[nodiscard]] RecentsSettings &recents() const;
+ [[nodiscard]] CardOverrideSettings &cardOverrides() const;
+ [[nodiscard]] DebugSettings &debug() const;
[[nodiscard]] CardCounterSettings &cardCounters() const;
+ [[nodiscard]] TabsSettings &tabs() const;
+ [[nodiscard]] SoundSettings &sound() const;
+ [[nodiscard]] GameSettings &game() const;
+ [[nodiscard]] ChatSettings &chat() const;
+ [[nodiscard]] CacheStorageSettings &cacheStorage() const;
+ [[nodiscard]] UpdatesSettings &updates() const;
+ [[nodiscard]] PersonalSettings &personal() const;
+ [[nodiscard]] CardsDisplaySettings &cardsDisplay() const;
+ [[nodiscard]] InterfaceSettings &userInterface() const;
+ [[nodiscard]] DeckEditorSettings &deckEditor() const;
+ [[nodiscard]] PathsSettings &paths() const;
+ [[nodiscard]] VisualDeckStorageSettings &visualDeckStorage() const;
+ [[nodiscard]] AppearanceSettings &appearance() const;
+ [[nodiscard]] NetworkSettings &network() const;
+ [[nodiscard]] CommanderBracketSettings &commanderBrackets() const
+ {
+ return *commanderBracketSettings;
+ }
[[nodiscard]] bool getIsPortableBuild() const
{
return isPortableBuild;
}
- [[nodiscard]] bool getDownloadSpoilersStatus() const
- {
- return mbDownloadSpoilers;
- }
- [[nodiscard]] bool getRoundCardCorners() const
- {
- return roundCardCorners;
- }
static SettingsCache &instance();
void resetPaths();
public slots:
- void setDownloadSpoilerStatus(bool _spoilerStatus);
-
- void setLang(const QString &_lang);
- void setShowTipsOnStartup(bool _showTipsOnStartup);
- void setSeenTips(const QList &_seenTips);
- void setDeckPath(const QString &_deckPath);
- void setFiltersPath(const QString &_filtersPath);
- void setReplaysPath(const QString &_replaysPath);
- void setThemesPath(const QString &_themesPath);
- void setCustomCardDatabasePath(const QString &_customCardDatabasePath);
- void setPicsPath(const QString &_picsPath);
- void setCardDatabasePath(const QString &_cardDatabasePath);
- void setSpoilerDatabasePath(const QString &_spoilerDatabasePath);
- void setTokenDatabasePath(const QString &_tokenDatabasePath);
void setThemeName(const QString &_themeName);
- void setHomeTabBackgroundSource(const QString &_backgroundSource);
- void setHomeTabBackgroundShuffleFrequency(int _frequency);
- void setHomeTabDisplayCardName(QT_STATE_CHANGED_T _displayCardName);
- void setTabVisualDeckStorageOpen(bool value);
- void setTabServerOpen(bool value);
- void setTabAccountOpen(bool value);
- void setTabDeckStorageOpen(bool value);
- void setTabReplaysOpen(bool value);
- void setTabAdminOpen(bool value);
- void setTabLogOpen(bool value);
- void setChatMentionColor(const QString &_chatMentionColor);
- void setChatHighlightColor(const QString &_chatHighlightColor);
- void setPicDownload(QT_STATE_CHANGED_T _picDownload);
- void setShowStatusBar(bool value);
- void setNotificationsEnabled(QT_STATE_CHANGED_T _notificationsEnabled);
- void setSpectatorNotificationsEnabled(QT_STATE_CHANGED_T _spectatorNotificationsEnabled);
- void setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T _buddyConnectNotificationsEnabled);
- void setDoubleClickToPlay(QT_STATE_CHANGED_T _doubleClickToPlay);
- void setClickPlaysAllSelected(QT_STATE_CHANGED_T _clickPlaysAllSelected);
- void setPlayToStack(QT_STATE_CHANGED_T _playToStack);
- void setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases);
- void setStartingHandSize(int _startingHandSize);
- void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens);
- void setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts);
- void setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar);
- void setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames);
- void setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt);
- void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop);
- void setPrintingSelectorSortOrder(int _printingSelectorSortOrder);
- void setPrintingSelectorCardSize(int _printingSelectorCardSize);
- void setIncludeRebalancedCards(bool _includeRebalancedCards);
- void setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible);
- void setDeckEditorBannerCardComboBoxVisible(QT_STATE_CHANGED_T _deckEditorBannerCardComboBoxVisible);
- void setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T _deckEditorTagsWidgetVisible);
- void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder);
- void setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value);
- void setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags);
- void setVisualDeckStorageDefaultTagsList(QStringList _defaultTagsList);
- void setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T value);
- void setVisualDeckStorageShowColorIdentity(QT_STATE_CHANGED_T value);
- void setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox);
- void setVisualDeckStorageShowTagsOnDeckPreviews(QT_STATE_CHANGED_T _showTags);
- void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize);
- void setVisualDeckStorageDrawUnusedColorIdentities(QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities);
- void setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity);
- void setVisualDeckStorageTooltipType(int value);
- void setVisualDeckStoragePromptForConversion(bool _visualDeckStoragePromptForConversion);
- void setVisualDeckStorageAlwaysConvert(bool _visualDeckStorageAlwaysConvert);
- void setVisualDeckStorageInGame(QT_STATE_CHANGED_T value);
- void setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T value);
- void setVisualDeckEditorCardSize(int _visualDeckEditorCardSize);
- void setVisualDatabaseDisplayCardSize(int _visualDatabaseDisplayCardSize);
- void setEDHRecCardSize(int _EDHRecCardSize);
- void setArchidektPreviewCardSize(int _archidektPreviewCardSize);
- void setDefaultDeckEditorType(int value);
- void setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T _enabled);
- void setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int _amount);
- void setVisualDeckEditorSampleHandSize(int _amount);
- void setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand);
- void setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate);
- void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout);
- void setTapAnimation(QT_STATE_CHANGED_T _tapAnimation);
- void setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T _autoRotateSidewaysLayoutCards);
- void setOpenDeckInNewTab(QT_STATE_CHANGED_T _openDeckInNewTab);
- void setRewindBufferingMs(int _rewindBufferingMs);
- void setStyleUserList(QT_STATE_CHANGED_T _styleUserList);
- void setChatMention(QT_STATE_CHANGED_T _chatMention);
- void setChatMentionCompleter(QT_STATE_CHANGED_T _chatMentionCompleter);
- void setChatMentionForeground(QT_STATE_CHANGED_T _chatMentionForeground);
- void setChatHighlightForeground(QT_STATE_CHANGED_T _chatHighlightForeground);
- void setZoneViewGroupByIndex(const int _zoneViewGroupByIndex);
- void setZoneViewSortByIndex(const int _zoneViewSortByIndex);
- void setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView);
- void setSoundEnabled(QT_STATE_CHANGED_T _soundEnabled);
- void setSoundThemeName(const QString &_soundThemeName);
- void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers);
- void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages);
- void setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages);
- void setPixmapCacheSize(const int _pixmapCacheSize);
- void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod);
- void setNetworkCacheSizeInMB(const int _networkCacheSize);
- void setNetworkRedirectCacheTtl(const int _redirectCacheTtl);
- void setLocalCardImageStorageNamingScheme(
- const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme);
- void setCardScaling(const QT_STATE_CHANGED_T _scaleCards);
- void setStackCardOverlapPercent(const int _verticalCardOverlapPercent);
- void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups);
- void setShowMentionPopups(const QT_STATE_CHANGED_T _showMentionPopups);
- void setRoomHistory(const QT_STATE_CHANGED_T _roomHistory);
- void setLeftJustified(const QT_STATE_CHANGED_T _leftJustified);
- void setMasterVolume(const int _masterVolume);
- void setCardInfoViewMode(const int _viewMode);
- void setHighlightWords(const QString &_highlightWords);
- void setGameDescription(const QString _gameDescription);
- void setMaxPlayers(const int _maxPlayers);
- void setGameTypes(const QString _gameTypes);
- void setOnlyBuddies(const bool _onlyBuddies);
- void setOnlyRegistered(const bool _onlyRegistered);
- void setSpectatorsAllowed(const bool _spectatorsAllowed);
- void setSpectatorsNeedPassword(const bool _spectatorsNeedPassword);
- void setSpectatorsCanTalk(const bool _spectatorsCanTalk);
- void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything);
- void setCreateGameAsSpectator(const bool _createGameAsSpectator);
- void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal);
- void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad);
- void setRememberGameSettings(const bool _rememberGameSettings);
- void setLocalGameRememberSettings(bool value);
- void setLocalGameMaxPlayers(int value);
- void setLocalGameStartingLifeTotal(int value);
- void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value);
- void setStartupCardUpdateCheckPromptForUpdate(bool value);
- void setStartupCardUpdateCheckAlwaysUpdate(bool value);
- void setCardUpdateCheckInterval(int value);
- void setLastCardUpdateCheck(QDate value);
- void setAlwaysEnableNewSets(bool value);
- void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate);
- void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion);
- void setUpdateReleaseChannelIndex(int value);
- void setMaxFontSize(int _max);
- void setRoundCardCorners(bool _roundCardCorners);
- void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount);
- void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount);
- void setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally);
};
#endif
diff --git a/cockatrice/src/client/settings/card_counter_settings.cpp b/cockatrice/src/client/settings/card_counter_settings.cpp
index 662ae0c7d..d4030c174 100644
--- a/cockatrice/src/client/settings/card_counter_settings.cpp
+++ b/cockatrice/src/client/settings/card_counter_settings.cpp
@@ -5,7 +5,7 @@
#include
CardCounterSettings::CardCounterSettings(const QString &settingsPath, QObject *parent)
- : SettingsManager(settingsPath + "global.ini", "cards", "counters", parent)
+ : SettingsManager(settingsPath + "card_counters.ini", "cards", "counters", parent)
{
}
diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h
index 95155b8d1..f4ebc204e 100644
--- a/cockatrice/src/client/settings/shortcuts_settings.h
+++ b/cockatrice/src/client/settings/shortcuts_settings.h
@@ -786,6 +786,10 @@ private:
ShortcutGroup::Tabs)},
{"Tabs/aTabLogs",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Logs"), parseSequenceString(""), ShortcutGroup::Tabs)},
+ {"Tabs/aTabReport",
+ ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Report Queue"), parseSequenceString(""), ShortcutGroup::Tabs)},
+ {"Tabs/aTabModeration",
+ ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Moderation"), parseSequenceString(""), ShortcutGroup::Tabs)},
};
};
diff --git a/cockatrice/src/client/sound_engine.cpp b/cockatrice/src/client/sound_engine.cpp
index e592b5ea0..96cafa3d3 100644
--- a/cockatrice/src/client/sound_engine.cpp
+++ b/cockatrice/src/client/sound_engine.cpp
@@ -2,12 +2,11 @@
#include "settings/cache_settings.h"
+#include
+#include
#include
#include
-
-#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
-#include
-#endif
+#include
#define DEFAULT_THEME_NAME "Default"
#define TEST_SOUND_FILENAME "player_join"
@@ -15,8 +14,10 @@
SoundEngine::SoundEngine(QObject *parent) : QObject(parent), audioOutput(nullptr), player(nullptr)
{
ensureThemeDirectoryExists();
- connect(&SettingsCache::instance(), &SettingsCache::soundThemeChanged, this, &SoundEngine::themeChangedSlot);
- connect(&SettingsCache::instance(), &SettingsCache::soundEnabledChanged, this, &SoundEngine::soundEnabledChanged);
+ connect(&SettingsCache::instance().sound(), &SoundSettings::soundThemeChanged, this,
+ &SoundEngine::themeChangedSlot);
+ connect(&SettingsCache::instance().sound(), &SoundSettings::soundEnabledChanged, this,
+ &SoundEngine::soundEnabledChanged);
soundEnabledChanged();
themeChangedSlot();
@@ -36,14 +37,12 @@ SoundEngine::~SoundEngine()
void SoundEngine::soundEnabledChanged()
{
- if (SettingsCache::instance().getSoundEnabled()) {
+ if (SettingsCache::instance().sound().getSoundEnabled()) {
qCInfo(SoundEngineLog) << "SoundEngine: enabling sound with" << audioData.size() << "sounds";
if (!player) {
player = new QMediaPlayer;
-#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
audioOutput = new QAudioOutput(player);
player->setAudioOutput(audioOutput);
-#endif
}
} else {
qCInfo(SoundEngineLog) << "SoundEngine: disabling sound";
@@ -70,14 +69,9 @@ void SoundEngine::playSound(const QString &fileName)
}
player->stop();
- int volumeSliderValue = SettingsCache::instance().getMasterVolume();
-#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ int volumeSliderValue = SettingsCache::instance().sound().getMasterVolume();
player->audioOutput()->setVolume(qreal(volumeSliderValue) / 100);
player->setSource(QUrl::fromLocalFile(audioData[fileName]));
-#else
- player->setVolume(volumeSliderValue);
- player->setMedia(QUrl::fromLocalFile(audioData[fileName]));
-#endif
player->play();
}
@@ -88,10 +82,10 @@ void SoundEngine::testSound()
void SoundEngine::ensureThemeDirectoryExists()
{
- if (SettingsCache::instance().getSoundThemeName().isEmpty() ||
- !getAvailableThemes().contains(SettingsCache::instance().getSoundThemeName())) {
+ if (SettingsCache::instance().sound().getSoundThemeName().isEmpty() ||
+ !getAvailableThemes().contains(SettingsCache::instance().sound().getSoundThemeName())) {
qCInfo(SoundEngineLog) << "Sounds theme name not set, setting default value";
- SettingsCache::instance().setSoundThemeName(DEFAULT_THEME_NAME);
+ SettingsCache::instance().sound().setSoundThemeName(DEFAULT_THEME_NAME);
}
}
@@ -100,7 +94,7 @@ QStringMap &SoundEngine::getAvailableThemes()
QDir dir;
availableThemes.clear();
- // load themes from user profile dir
+ // Load themes from user profile dir
dir.setPath(SettingsCache::instance().getDataPath() + "/sounds");
@@ -110,7 +104,7 @@ QStringMap &SoundEngine::getAvailableThemes()
}
}
- // load themes from cockatrice system dir
+ // Load themes from Cockatrice system dir
dir.setPath(qApp->applicationDirPath() +
#ifdef Q_OS_MAC
"/../Resources/sounds"
@@ -132,7 +126,7 @@ QStringMap &SoundEngine::getAvailableThemes()
void SoundEngine::themeChangedSlot()
{
- QString themeName = SettingsCache::instance().getSoundThemeName();
+ QString themeName = SettingsCache::instance().sound().getSoundThemeName();
qCInfo(SoundEngineLog) << "Sound theme changed:" << themeName;
QDir dir = getAvailableThemes().value(themeName);
diff --git a/cockatrice/src/client/url_scheme_event_filter.h b/cockatrice/src/client/url_scheme_event_filter.h
new file mode 100644
index 000000000..9e96502ca
--- /dev/null
+++ b/cockatrice/src/client/url_scheme_event_filter.h
@@ -0,0 +1,69 @@
+#ifndef COCKATRICE_URL_SCHEME_EVENT_FILTER_H
+#define COCKATRICE_URL_SCHEME_EVENT_FILTER_H
+
+#include
+#include
+#include
+#include
+#include
+
+/**
+ * @brief Event filter that catches QFileOpenEvent URLs matching a scheme and
+ * re-emits them as urlReceived().
+ *
+ * On macOS, when the application is registered as a URL scheme handler, the
+ * OS delivers incoming URLs via QFileOpenEvent on the QApplication object.
+ * Install this filter on QApplication to intercept them:
+ *
+ * @code
+ * UrlSchemeEventFilter filter(QStringList{QStringLiteral("cockatrice")});
+ * QObject::connect(&filter, &UrlSchemeEventFilter::urlReceived,
+ * &mainWindow, &MainWindow::handleUrl);
+ * app.installEventFilter(&filter);
+ * @endcode
+ *
+ * Note: the strings are compared against QUrl::scheme(), so they must be
+ * written without the "://" suffix (e.g. "cockatrice", not "cockatrice://").
+ */
+class UrlSchemeEventFilter : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit UrlSchemeEventFilter(const QStringList &schemes, QObject *parent = nullptr)
+ : QObject(parent), prefixes(schemes)
+ {
+ }
+
+signals:
+ void urlReceived(const QString &url);
+
+public:
+ bool eventFilter(QObject *watched, QEvent *event) override
+ {
+ if (event->type() == QEvent::FileOpen) {
+ auto *fileEvent = static_cast(event);
+
+ const QUrl url = fileEvent->url();
+
+ for (const auto &prefix : prefixes) {
+ if (url.scheme() == prefix) {
+ emit urlReceived(url.toString());
+ return true;
+ }
+ }
+
+ if (url.isLocalFile()) {
+ emit urlReceived(url.toLocalFile());
+ return true;
+ }
+ }
+
+ return QObject::eventFilter(watched, event);
+ }
+
+private:
+ QStringList prefixes;
+};
+
+#endif // COCKATRICE_URL_SCHEME_EVENT_FILTER_H
diff --git a/cockatrice/src/database/interface/settings_card_database_path_provider.h b/cockatrice/src/database/interface/settings_card_database_path_provider.h
new file mode 100644
index 000000000..1c7d02460
--- /dev/null
+++ b/cockatrice/src/database/interface/settings_card_database_path_provider.h
@@ -0,0 +1,41 @@
+#ifndef SETTINGS_CARD_DATABASE_PATH_PROVIDER_H
+#define SETTINGS_CARD_DATABASE_PATH_PROVIDER_H
+
+#include "../../client/settings/cache_settings.h"
+
+#include
+#include
+
+class SettingsCardDatabasePathProvider : public ICardDatabasePathProvider
+{
+ Q_OBJECT
+
+public:
+ explicit SettingsCardDatabasePathProvider(QObject *parent = nullptr) : ICardDatabasePathProvider(parent)
+ {
+ connect(&SettingsCache::instance().paths(), &PathsSettings::cardDatabasePathChanged, this,
+ &ICardDatabasePathProvider::cardDatabasePathChanged);
+ }
+
+ [[nodiscard]] QString getCardDatabasePath() const override
+ {
+ return SettingsCache::instance().paths().getCardDatabasePath();
+ }
+
+ [[nodiscard]] QString getCustomCardDatabasePath() const override
+ {
+ return SettingsCache::instance().paths().getCustomCardDatabasePath();
+ }
+
+ [[nodiscard]] QString getTokenDatabasePath() const override
+ {
+ return SettingsCache::instance().paths().getTokenDatabasePath();
+ }
+
+ [[nodiscard]] virtual QString getSpoilerCardDatabasePath() const override
+ {
+ return SettingsCache::instance().paths().getSpoilerCardDatabasePath();
+ }
+};
+
+#endif // SETTINGS_CARD_DATABASE_PATH_PROVIDER_H
diff --git a/cockatrice/src/database/interface/settings_card_preference_provider.h b/cockatrice/src/database/interface/settings_card_preference_provider.h
index 6c31b9d6d..64502e3a7 100644
--- a/cockatrice/src/database/interface/settings_card_preference_provider.h
+++ b/cockatrice/src/database/interface/settings_card_preference_provider.h
@@ -3,6 +3,8 @@
#include "../../client/settings/cache_settings.h"
#include
+#include
+#include
class SettingsCardPreferenceProvider : public ICardPreferenceProvider
{
@@ -14,7 +16,7 @@ public:
[[nodiscard]] bool getIncludeRebalancedCards() const override
{
- return SettingsCache::instance().getIncludeRebalancedCards();
+ return SettingsCache::instance().cardsDisplay().getIncludeRebalancedCards();
}
};
diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp
index dd873cfa5..4abb8210c 100644
--- a/cockatrice/src/filters/deck_filter_string.cpp
+++ b/cockatrice/src/filters/deck_filter_string.cpp
@@ -52,18 +52,14 @@ static void setupParserRules()
search["Start"] = passthru;
search["QueryPartList"] = [](const peg::SemanticValues &sv) -> DeckFilter {
- return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) {
- auto matchesFilter = [&deck, &info](const std::any &query) {
- return std::any_cast(query)(deck, info);
- };
+ return [=](const DeckSearchData &data) {
+ auto matchesFilter = [&data](const std::any &query) { return std::any_cast(query)(data); };
return std::all_of(sv.begin(), sv.end(), matchesFilter);
};
};
search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> DeckFilter {
- return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) {
- auto matchesFilter = [&deck, &info](const std::any &query) {
- return std::any_cast(query)(deck, info);
- };
+ return [=](const DeckSearchData &data) {
+ auto matchesFilter = [&data](const std::any &query) { return std::any_cast(query)(data); };
return std::any_of(sv.begin(), sv.end(), matchesFilter);
};
};
@@ -71,9 +67,7 @@ static void setupParserRules()
search["QueryPart"] = passthru;
search["NotQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
const auto dependent = std::any_cast(sv[0]);
- return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) -> bool {
- return !dependent(deck, info);
- };
+ return [=](const DeckSearchData &data) -> bool { return !dependent(data); };
};
search["String"] = [](const peg::SemanticValues &sv) -> QString {
@@ -125,9 +119,9 @@ static void setupParserRules()
auto cardFilter = FilterString(std::any_cast(sv[0]));
auto numberMatcher = sv.size() > 1 ? std::any_cast(sv[1]) : [](int count) { return count > 0; };
- return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool {
+ return [=](const DeckSearchData &data) -> bool {
int count = 0;
- auto cardNodes = deck->deckLoader->getDeck().deckList.getCardNodes();
+ auto cardNodes = data.deck->deckList.getCardNodes();
for (auto node : cardNodes) {
auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName());
if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) {
@@ -146,53 +140,49 @@ static void setupParserRules()
search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast(sv[0]);
- return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
- return deck->deckLoader->getDeck().deckList.getName().contains(name, Qt::CaseInsensitive);
+ return [=](const DeckSearchData &data) {
+ return data.deck->deckList.getName().contains(name, Qt::CaseInsensitive);
};
};
search["FileNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast(sv[0]);
- return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
- auto filename = QFileInfo(deck->filePath).fileName();
+ return [=](const DeckSearchData &data) {
+ auto filename = QFileInfo(data.filePath).fileName();
return filename.contains(name, Qt::CaseInsensitive);
};
};
search["PathQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast(sv[0]);
- return [=](const DeckPreviewWidget *, const ExtraDeckSearchInfo &info) {
- return info.relativeFilePath.contains(name, Qt::CaseInsensitive);
- };
+ return [=](const DeckSearchData &data) { return data.relativeFilePath.contains(name, Qt::CaseInsensitive); };
};
search["FormatQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto format = std::any_cast(sv[0]);
- return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
- auto gameFormat = deck->deckLoader->getDeck().deckList.getGameFormat();
+ return [=](const DeckSearchData &data) {
+ auto gameFormat = data.deck->deckList.getGameFormat();
return QString::compare(format, gameFormat, Qt::CaseInsensitive) == 0;
};
};
search["CommentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto value = std::any_cast(sv[0]);
- return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
- auto comments = deck->deckLoader->getDeck().deckList.getComments();
+ return [=](const DeckSearchData &data) {
+ auto comments = data.deck->deckList.getComments();
return comments.contains(value, Qt::CaseInsensitive);
};
};
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast(sv[0]);
- return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
- return deck->getDisplayName().contains(name, Qt::CaseInsensitive);
- };
+ return [=](const DeckSearchData &data) { return data.displayName.contains(name, Qt::CaseInsensitive); };
};
}
DeckFilterString::DeckFilterString()
{
- filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; };
+ filter = [](const DeckSearchData &) { return false; };
_error = "Not initialized";
}
@@ -205,7 +195,7 @@ DeckFilterString::DeckFilterString(const QString &expr)
_error = QString();
if (ba.isEmpty()) {
- filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return true; };
+ filter = [](const DeckSearchData &) { return true; };
return;
}
@@ -215,6 +205,6 @@ DeckFilterString::DeckFilterString(const QString &expr)
if (!search.parse(ba.data(), filter)) {
qCInfo(DeckFilterStringLog).nospace() << "DeckFilterString error for " << expr << "; " << qPrintable(_error);
- filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; };
+ filter = [](const DeckSearchData &) { return false; };
}
-}
\ No newline at end of file
+}
diff --git a/cockatrice/src/filters/deck_filter_string.h b/cockatrice/src/filters/deck_filter_string.h
index 916b629ee..90a6a17eb 100644
--- a/cockatrice/src/filters/deck_filter_string.h
+++ b/cockatrice/src/filters/deck_filter_string.h
@@ -7,7 +7,7 @@
#ifndef DECK_FILTER_STRING_H
#define DECK_FILTER_STRING_H
-#include "../interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h"
+#include "../interface/deck_loader/loaded_deck.h"
#include
#include
@@ -16,26 +16,29 @@
inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string");
/**
- * Extra info relevant to filtering that isn't present in the DeckPreviewWidget
+ * The data a deck search expression is evaluated against.
+ *
+ * This is a data view rather than a widget pointer, so the same filter
+ * expression can be evaluated against a model or a live widget.
*/
-struct ExtraDeckSearchInfo
+struct DeckSearchData
{
- /**
- * The relative filepath starting from the deck folder
- */
- QString relativeFilePath;
+ const LoadedDeck *deck = nullptr; ///< The loaded deck. Must not be null.
+ QString filePath; ///< Absolute path of the deck file.
+ QString displayName; ///< Deck name, or the file name if the deck has no name.
+ QString relativeFilePath; ///< File path relative to the deck folder.
};
-typedef std::function DeckFilter;
+typedef std::function DeckFilter;
class DeckFilterString
{
public:
DeckFilterString();
explicit DeckFilterString(const QString &expr);
- bool check(const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) const
+ bool check(const DeckSearchData &data) const
{
- return filter(deck, info);
+ return filter(data);
}
[[nodiscard]] bool valid() const
diff --git a/cockatrice/src/filters/filter_builder.cpp b/cockatrice/src/filters/filter_builder.cpp
index 785f753e7..f109fbcd3 100644
--- a/cockatrice/src/filters/filter_builder.cpp
+++ b/cockatrice/src/filters/filter_builder.cpp
@@ -1,5 +1,6 @@
#include "filter_builder.h"
+#include "../interface/pixel_map_generator.h"
#include "../interface/widgets/utility/custom_line_edit.h"
#include
@@ -21,7 +22,7 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
typeCombo->addItem(CardFilter::typeName(static_cast(i)), QVariant(i));
}
- QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString());
+ QPushButton *ok = new QPushButton(themePixmap(QStringLiteral("icons/increment")), QString());
ok->setObjectName("ok");
ok->setMaximumSize(20, 20);
diff --git a/cockatrice/src/game/abstract_game.cpp b/cockatrice/src/game/abstract_game.cpp
index c20003ece..6aa2ab28f 100644
--- a/cockatrice/src/game/abstract_game.cpp
+++ b/cockatrice/src/game/abstract_game.cpp
@@ -31,7 +31,7 @@ AbstractClient *AbstractGame::getClientForPlayer(int playerId) const
}
}
-void AbstractGame::loadReplay(GameReplay *replay)
+void AbstractGame::loadReplay(const GameReplay *replay)
{
gameMetaInfo->setFromProto(replay->game_info());
gameMetaInfo->setSpectatorsOmniscient(true);
diff --git a/cockatrice/src/game/abstract_game.h b/cockatrice/src/game/abstract_game.h
index 5115ed5ca..fcf764492 100644
--- a/cockatrice/src/game/abstract_game.h
+++ b/cockatrice/src/game/abstract_game.h
@@ -53,7 +53,7 @@ public:
AbstractClient *getClientForPlayer(int playerId) const;
- void loadReplay(GameReplay *replay);
+ void loadReplay(const GameReplay *replay);
CardItem *getCard(int playerId, const QString &zoneName, int cardId) const;
diff --git a/cockatrice/src/game/board/counter_state.cpp b/cockatrice/src/game/board/counter_state.cpp
index 6da18b662..0970e4272 100644
--- a/cockatrice/src/game/board/counter_state.cpp
+++ b/cockatrice/src/game/board/counter_state.cpp
@@ -13,12 +13,12 @@ CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject
convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent);
}
-void CounterState::setValue(int newValue)
+void CounterState::setValue(int newValue, bool skipDamageAnimation)
{
if (newValue == value) {
return;
}
int old = value;
value = newValue;
- emit valueChanged(old, newValue);
+ emit valueChanged(old, newValue, skipDamageAnimation);
}
\ No newline at end of file
diff --git a/cockatrice/src/game/board/counter_state.h b/cockatrice/src/game/board/counter_state.h
index 0f2f16b55..4c7b34473 100644
--- a/cockatrice/src/game/board/counter_state.h
+++ b/cockatrice/src/game/board/counter_state.h
@@ -35,10 +35,23 @@ public:
return value;
}
- void setValue(int newValue);
+ /**
+ * @brief Set the counter value.
+ * @param newValue The new value.
+ * @param skipDamageAnimation When true, valueChanged is emitted with skipDamageAnimation=true, letting views
+ * suppress damage-related feedback (e.g. battlefield shimmer, life counter flash) for values set during replay
+ * rewinds.
+ */
+ void setValue(int newValue, bool skipDamageAnimation = false);
signals:
- void valueChanged(int oldValue, int newValue);
+ /**
+ * @brief Emitted whenever the value changes.
+ * @param oldValue The previous value.
+ * @param newValue The new value.
+ * @param skipDamageAnimation True when the change should not trigger damage/life-change feedback in views.
+ */
+ void valueChanged(int oldValue, int newValue, bool skipDamageAnimation);
private:
int id;
diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp
index 4a96eebdb..f146cdbb4 100644
--- a/cockatrice/src/game/game_event_handler.cpp
+++ b/cockatrice/src/game/game_event_handler.cpp
@@ -229,7 +229,11 @@ void GameEventHandler::handleArrowDeletion(int creatorId, int arrowId)
void GameEventHandler::handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId)
{
- if (response.response_code() == Response::RespNameNotFound) {
+ // The server confirms the arrow no longer exists whether it deleted it itself
+ // (RespOk, followed by an Event_DeleteArrow broadcast) or never had it
+ // (RespNameNotFound). In both cases the local copy has to go. deleteArrow is
+ // a no-op if the arrow was already removed by the event broadcast.
+ if (response.response_code() == Response::RespOk || response.response_code() == Response::RespNameNotFound) {
emit arrowDeleted(creatorId, arrowId);
}
}
@@ -281,12 +285,19 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
emit playerJoined(prop);
}
player->processPlayerInfo(playerInfo);
+ // Extract playmat from player properties for opponent display
+ if (prop.has_playmat_params()) {
+ player->setPlaymatFromProperties(prop);
+ }
if (player->getPlayerInfo()->getLocal()) {
emit localPlayerDeckSelected(player, playerId, playerInfo);
} else {
if (!game->getGameMetaInfo()->proto().share_decklists_on_load()) {
continue;
}
+ if (!playerInfo.has_deck_list()) {
+ continue;
+ }
opponentDecksToDisplay.append(
qMakePair(playerId, qMakePair(playerName, QString::fromStdString(playerInfo.deck_list()))));
@@ -344,6 +355,11 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
const ServerInfo_PlayerProperties &prop = event.player_properties();
emit playerPropertiesChanged(prop, eventPlayerId);
+ // Update playmat from player properties
+ if (prop.has_playmat_params()) {
+ player->setPlaymatFromProperties(prop);
+ }
+
const auto contextType = static_cast(getPbExtension(context));
switch (contextType) {
case GameEventContext::READY_START: {
@@ -414,12 +430,13 @@ void GameEventHandler::eventJoin(const Event_Join &event, int /*eventPlayerId*/,
QString playerName = QString::fromStdString(playerInfo.user_info().name());
emit addPlayerToAutoCompleteList(playerName);
- if (game->getPlayerManager()->getPlayers().contains(playerId)) {
+ PlayerManager *playerManager = game->getPlayerManager();
+ if (playerManager->getPlayers().contains(playerId) || playerManager->getSpectators().contains(playerId)) {
return;
}
if (playerInfo.spectator()) {
- game->getPlayerManager()->addSpectator(playerId, playerInfo);
+ playerManager->addSpectator(playerId, playerInfo);
emit logJoinSpectator(playerName);
emit spectatorJoined(playerInfo);
} else {
diff --git a/cockatrice/src/game/game_event_handler.h b/cockatrice/src/game/game_event_handler.h
index f47116949..99277abb7 100644
--- a/cockatrice/src/game/game_event_handler.h
+++ b/cockatrice/src/game/game_event_handler.h
@@ -1,8 +1,16 @@
/**
* @file game_event_handler.h
* @ingroup GameLogic
+ * @brief Game-level command sender and event dispatcher.
+ *
+ * GameEventHandler sends commands initiated by the local client to the server
+ * and processes incoming game-wide events. It bridges the networking layer
+ * (protobuf events received via AbstractClient) with the game model and UI
+ * (GameState, PlayerManager, logging, widgets).
+ *
+ * Player-scoped events are forwarded to PlayerEventHandler instances, while
+ * spectator and global game events are handled directly here.
*/
-//! \todo Document this file.
#ifndef COCKATRICE_GAME_EVENT_HANDLER_H
#define COCKATRICE_GAME_EVENT_HANDLER_H
@@ -15,92 +23,310 @@
#include
class AbstractClient;
-class Response;
+class AbstractGame;
+class CommandContainer;
+class GameCommand;
class GameEventContainer;
class GameEventContext;
-class GameCommand;
-class GameState;
-class MessageLogWidget;
-class CommandContainer;
-class Event_GameJoined;
+class PendingCommand;
+class PlayerLogic;
+class Response;
+
class Event_GameStateChanged;
class Event_PlayerPropertiesChanged;
class Event_Join;
class Event_Leave;
class Event_GameHostChanged;
class Event_GameClosed;
-class Event_GameStart;
class Event_SetActivePlayer;
class Event_SetActivePhase;
-class Event_Ping;
class Event_GameSay;
class Event_Kicked;
class Event_ReverseTurn;
-class AbstractGame;
-class PendingCommand;
-class PlayerLogic;
+class Event_Ping;
inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler");
+/**
+ * @class GameEventHandler
+ * @brief Central dispatcher for game-wide commands and events.
+ *
+ * This class owns no game state itself. Instead, it:
+ * - Sends commands to the server on behalf of local players
+ * - Receives and dispatches server-side game events
+ * - Updates the game model indirectly via Player, GameState, and PlayerManager
+ * - Emits high-level signals for UI updates and logging
+ */
class GameEventHandler : public QObject
{
Q_OBJECT
private:
+ /** Pointer to the owning game instance. */
AbstractGame *game;
public:
+ /** @name Construction
+ * Lifecycle and ownership.
+ * @{
+ */
+
+ /**
+ * @brief Construct a GameEventHandler.
+ *
+ * The handler is owned by the AbstractGame instance and uses it to
+ * access the game state, players, and network clients.
+ *
+ * @param _game Owning game instance (also used as QObject parent).
+ */
explicit GameEventHandler(AbstractGame *_game);
+ /** @} */
+
+ /** @name Outgoing game commands
+ * Commands initiated locally and sent to the server.
+ *
+ * These methods construct and send protobuf commands corresponding
+ * to user actions in the UI.
+ * @{
+ */
+
+ /** @brief Request advancing the game to the next turn. */
void handleNextTurn();
+
+ /** @brief Request reversing the current turn order. */
void handleReverseTurn();
+ /** @brief Concede the game for the currently active local player. */
void handleActiveLocalPlayerConceded();
+
+ /** @brief Undo a previous concede for the active local player. */
void handleActiveLocalPlayerUnconceded();
+
+ /**
+ * @brief Set the active phase of the game.
+ *
+ * Typically triggered by the active player selecting a new phase.
+ *
+ * @param phase Phase identifier.
+ */
void handleActivePhaseChanged(int phase);
+
+ /** @brief Leave the current game session. */
void handleGameLeft();
+
+ /**
+ * @brief Send a chat message to all players and spectators.
+ *
+ * @param chatMessage Message text.
+ */
void handleChatMessageSent(const QString &chatMessage);
+
+ /**
+ * @brief Delete an existing arrow.
+ *
+ * @param arrowId Unique identifier of the arrow to delete.
+ */
void handleArrowDeletion(int creatorId, int arrowId);
void handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId);
+ /** @} */
+
+ /** @name Incoming event processing
+ * Entry points for server-sent events.
+ * @{
+ */
+
+ /**
+ * @brief Process a container of game events received from the server.
+ *
+ * This is the main dispatch function for incoming game events.
+ * Events are routed to spectator handlers, game-level handlers,
+ * or forwarded to PlayerEventHandler instances as appropriate.
+ *
+ * @param cont Game event container from the server.
+ * @param client Client that received the container.
+ * @param options Processing flags (e.g. silent, replay).
+ */
+ void
+ processGameEventContainer(const GameEventContainer &cont, AbstractClient *client, EventProcessingOptions options);
+
+ /** @} */
+
+ /** @name Command preparation helpers
+ * Internal helpers for building command containers.
+ * @{
+ */
+
+ /**
+ * @brief Wrap a single protobuf command in a PendingCommand.
+ *
+ * @param cmd Protobuf command message.
+ * @return Newly allocated PendingCommand (caller takes ownership).
+ */
+ PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
+
+ /**
+ * @brief Wrap multiple protobuf commands in a single PendingCommand.
+ *
+ * Ownership of the messages in cmdList is transferred to the handler.
+ *
+ * @param cmdList List of protobuf command messages.
+ * @return Newly allocated PendingCommand.
+ */
+ PendingCommand *prepareGameCommand(const QList &cmdList);
+
+ /** @} */
+
+ /** @name Spectator event handlers
+ * Events originating from spectators.
+ * @{
+ */
+
+ /**
+ * @brief Handle a spectator chat message.
+ */
void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context);
+
+ /**
+ * @brief Handle a spectator leaving the game.
+ */
void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
+ /** @} */
+
+ /** @name Game state event handlers
+ * Events that affect global game state.
+ * @{
+ */
+
+ /**
+ * @brief Handle a full game state update from the server.
+ *
+ * Used during game startup, reconnection, and resynchronization.
+ */
void eventGameStateChanged(const Event_GameStateChanged &event, int eventPlayerId, const GameEventContext &context);
+
+ /**
+ * @brief Update card attachment relationships for all players.
+ *
+ * Called after a game state update to ensure attachments are resolved
+ * consistently across all zones.
+ */
void processCardAttachmentsForPlayers(const Event_GameStateChanged &event);
+
+ /** @brief Handle a change in game host. */
+ void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context);
+
+ /** @brief Handle the game being closed by the server. */
+ void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context);
+
+ /** @brief Handle a change of the active player. */
+ void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context);
+
+ /** @brief Handle a change of the active phase. */
+ void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context);
+
+ /** @brief Handle a turn reversal event. */
+ void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext &context);
+
+ /** @brief Handle ping / latency updates. */
+ void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context);
+
+ /** @} */
+
+ /** @name Player lifecycle and property handlers
+ * Events related to players joining, leaving, or changing state.
+ * @{
+ */
+
+ /**
+ * @brief Handle updates to a player's properties.
+ *
+ * Includes readiness, concede state, deck selection, sideboard lock,
+ * and connection state changes.
+ */
void eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event,
int eventPlayerId,
const GameEventContext &context);
+
+ /** @brief Handle a player or spectator joining the game. */
void eventJoin(const Event_Join &event, int eventPlayerId, const GameEventContext &context);
+
+ /** @brief Handle a player leaving the game. */
void eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
- QString getLeaveReason(Event_Leave::LeaveReason reason);
+
+ /** @brief Handle the local player being kicked from the game. */
void eventKicked(const Event_Kicked &event, int eventPlayerId, const GameEventContext &context);
- void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context);
- void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context);
- void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context);
- void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context);
- void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context);
- void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext & /*context*/);
+ /**
+ * @brief Convert a leave reason enum to a human-readable string.
+ */
+ QString getLeaveReason(Event_Leave::LeaveReason reason);
- void commandFinished(const Response &response);
+ /** @} */
- void
- processGameEventContainer(const GameEventContainer &cont, AbstractClient *client, EventProcessingOptions options);
- PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
- PendingCommand *prepareGameCommand(const QList &cmdList);
public slots:
+ /** @name Command dispatch slots
+ * Low-level command transmission.
+ * @{
+ */
+
+ /**
+ * @brief Send a prepared PendingCommand.
+ *
+ * @param pend Pending command to send.
+ * @param playerId Player whose client should send the command.
+ */
void sendGameCommand(PendingCommand *pend, int playerId = -1);
+
+ /**
+ * @brief Send a single protobuf command.
+ *
+ * @param command Protobuf command message.
+ * @param playerId Player whose client should send the command.
+ */
void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1);
+ /**
+ * @brief Called when a PendingCommand finishes execution.
+ *
+ * Used to detect server-side errors such as chat flood protection.
+ */
+ void commandFinished(const Response &response);
+
+ /** @} */
+
signals:
+ /** @name Core state signals
+ * @{
+ */
+
void emitUserEvent();
+ void containerProcessingStarted(GameEventContext context);
+ void containerProcessingDone();
+ void gameFlooded();
+ void setContextJudgeName(QString judgeName);
+
+ /** @} */
+
+ /** @name Player and spectator signals
+ * @{
+ */
+
void addPlayerToAutoCompleteList(QString playerName);
void localPlayerDeckSelected(PlayerLogic *localPlayer, int playerId, ServerInfo_Player playerInfo);
void remotePlayerDeckSelected(QString deckList, int playerId, QString playerName);
void remotePlayersDecksSelected(QVector>> opponentDecks);
void localPlayerSideboardLocked(int playerId, bool sideboardLocked);
void localPlayerReadyStateChanged(int playerId, bool ready);
+
+ /** @} */
+
+ /** @name Game flow signals
+ * @{
+ */
+
void gameStopped();
void gameClosed();
void playerPropertiesChanged(const ServerInfo_PlayerProperties &prop, int playerId);
@@ -109,11 +335,15 @@ signals:
void playerKicked();
void spectatorJoined(const ServerInfo_PlayerProperties &spectatorInfo);
void spectatorLeft(int leavingSpectatorId);
- void gameFlooded();
- void containerProcessingStarted(GameEventContext context);
- void setContextJudgeName(QString judgeName);
- void containerProcessingDone();
void arrowDeleted(int creatorId, int arrowId);
+
+ /** @} */
+
+ /** @name Logging signals
+ * Signals consumed by MessageLogWidget.
+ * @{
+ */
+
void logSpectatorSay(ServerInfo_User userInfo, QString message);
void logSpectatorLeave(QString name, QString reason);
void logGameStart();
@@ -132,6 +362,8 @@ signals:
void logActivePhaseChanged(int activePhase);
void logConcede(int playerId);
void logUnconcede(int playerId);
+
+ /** @} */
};
#endif // COCKATRICE_GAME_EVENT_HANDLER_H
diff --git a/cockatrice/src/game/player/event_processing_options.h b/cockatrice/src/game/player/event_processing_options.h
index 4c7663789..06238d77e 100644
--- a/cockatrice/src/game/player/event_processing_options.h
+++ b/cockatrice/src/game/player/event_processing_options.h
@@ -13,7 +13,8 @@
enum EventProcessingOption
{
SKIP_REVEAL_WINDOW = 0x0001,
- SKIP_TAP_ANIMATION = 0x0002
+ SKIP_TAP_ANIMATION = 0x0002,
+ SKIP_DAMAGE_ANIMATION = 0x0004
};
// Wrap it in a QFlags typedef
diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp
index df9eb8a1d..67c3295d6 100644
--- a/cockatrice/src/game/player/player_actions.cpp
+++ b/cockatrice/src/game/player/player_actions.cpp
@@ -27,6 +27,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -67,7 +69,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown)
const CardInfo &info = exactCard.getInfo();
int tableRow = info.getUiAttributes().tableRow;
- bool playToStack = SettingsCache::instance().getPlayToStack();
+ bool playToStack = SettingsCache::instance().userInterface().getPlayToStack();
QString currentZone = card->getZone()->getName();
if (!faceDown && currentZone == ZoneNames::STACK && tableRow == 3) {
cmd.set_target_zone(ZoneNames::GRAVE);
@@ -310,7 +312,7 @@ void PlayerActions::actDrawCard()
void PlayerActions::actRequestMulliganDialog()
{
- int startSize = SettingsCache::instance().getStartingHandSize();
+ int startSize = SettingsCache::instance().userInterface().getStartingHandSize();
int handSize = player->getHandZone()->getCards().size();
int deckSize = player->getDeckZone()->getCards().size() + handSize;
@@ -326,7 +328,7 @@ void PlayerActions::actMulligan(int number)
}
doMulligan(number);
- SettingsCache::instance().setStartingHandSize(number);
+ SettingsCache::instance().userInterface().setStartingHandSize(number);
}
void PlayerActions::actMulliganSameSize()
@@ -930,13 +932,13 @@ void PlayerActions::setLastTokenInfo(CardInfoPtr cardInfo)
return;
}
- lastTokenInfo = {.name = cardInfo->getName(),
- .color = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().left(1).toLower(),
- .pt = cardInfo->getPowTough(),
- .annotation = SettingsCache::instance().getAnnotateTokens() ? cardInfo->getText() : "",
- .destroy = true,
- .providerId =
- SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())};
+ lastTokenInfo = {
+ .name = cardInfo->getName(),
+ .color = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().left(1).toLower(),
+ .pt = cardInfo->getPowTough(),
+ .annotation = SettingsCache::instance().userInterface().getAnnotateTokens() ? cardInfo->getText() : "",
+ .destroy = true,
+ .providerId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())};
lastTokenTableRow = TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow);
@@ -1169,7 +1171,7 @@ void PlayerActions::createCard(const CardItem *sourceCard,
}
cmd.set_pt(cardInfo->getPowTough().toStdString());
- if (SettingsCache::instance().getAnnotateTokens()) {
+ if (SettingsCache::instance().userInterface().getAnnotateTokens()) {
cmd.set_annotation(cardInfo->getText().toStdString());
} else {
cmd.set_annotation("");
@@ -1352,11 +1354,7 @@ void PlayerActions::actSetPT(QList selectedCards, const QString &pt)
const auto oldpt = CardItem::parsePT(card->getPT());
int ptIter = 0;
for (const auto &_item : ptList) {
-#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
if (_item.typeId() == QMetaType::Type::Int) {
-#else
- if (_item.type() == QVariant::Int) {
-#endif
int oldItem = ptIter < oldpt.size() ? oldpt.at(ptIter).toInt() : 0;
newpt += '/' + QString::number(oldItem + _item.toInt());
} else {
diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp
index bc48298f7..277b8b1d4 100644
--- a/cockatrice/src/game/player/player_event_handler.cpp
+++ b/cockatrice/src/game/player/player_event_handler.cpp
@@ -262,14 +262,15 @@ void PlayerEventHandler::eventCreateCounter(const Event_CreateCounter &event)
player->addCounter(event.counter_info());
}
-void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event)
+void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options)
{
CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr);
if (!ctr) {
return;
}
int oldValue = ctr->getValue();
- ctr->setValue(event.value());
+ const bool skipDamageAnimation = options.testFlag(SKIP_DAMAGE_ANIMATION);
+ ctr->setValue(event.value(), skipDamageAnimation);
emit logSetCounter(player, ctr->getName(), event.value(), oldValue);
}
@@ -625,7 +626,7 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
eventCreateCounter(event.GetExtension(Event_CreateCounter::ext));
break;
case GameEvent::SET_COUNTER:
- eventSetCounter(event.GetExtension(Event_SetCounter::ext));
+ eventSetCounter(event.GetExtension(Event_SetCounter::ext), options);
break;
case GameEvent::DEL_COUNTER:
eventDelCounter(event.GetExtension(Event_DelCounter::ext));
diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h
index cfd82933f..300cacd08 100644
--- a/cockatrice/src/game/player/player_event_handler.h
+++ b/cockatrice/src/game/player/player_event_handler.h
@@ -1,11 +1,24 @@
/**
* @file player_event_handler.h
* @ingroup GameLogicPlayers
+ * @brief Player-scoped game event handler.
+ *
+ * PlayerEventHandler applies game events that affect a single Player’s
+ * board state, zones, cards, counters, arrows, and related UI/log output.
+ *
+ * It is invoked by GameEventHandler after basic routing and validation.
+ * Each instance is bound 1:1 to a Player and must never mutate state
+ * belonging to other players except where explicitly required by events
+ * (e.g. moving cards between players, attaching cards, arrows).
+ *
+ * This class is intentionally stateful and tightly coupled to Player,
+ * PlayerActions, and the board/zones implementation. It performs both
+ * model mutation and UI-side bookkeeping (zone views, arrows, menus).
*/
-//! \todo Document this file.
#ifndef COCKATRICE_PLAYER_EVENT_HANDLER_H
#define COCKATRICE_PLAYER_EVENT_HANDLER_H
+
#include "event_processing_options.h"
#include
@@ -16,6 +29,7 @@
class CardItem;
class CardZoneLogic;
class PlayerLogic;
+
class Event_AttachCard;
class Event_ChangeZoneProperties;
class Event_CreateArrow;
@@ -37,11 +51,176 @@ class Event_SetCounter;
class Event_Shuffle;
class Event_GameLogNotice;
+/**
+ * @class PlayerEventHandler
+ * @brief Applies player-specific game events and emits corresponding log signals.
+ *
+ * Design notes:
+ * - All event handlers assume events are authoritative and already validated
+ * by the server.
+ * - Most handlers mutate both logical state (CardItem, CardZoneLogic, counters)
+ * and visual/UI state (views, arrows, menus).
+ * - Logging signals are emitted *after* or *during* state mutation, depending
+ * on whether later mutations would invalidate log data.
+ */
class PlayerEventHandler : public QObject
{
-
Q_OBJECT
+
+public:
+ /**
+ * @brief Construct a PlayerEventHandler bound to a Player.
+ * @param player Owning player instance.
+ */
+ explicit PlayerEventHandler(PlayerLogic *player);
+
+ /** @name Event dispatch
+ * @{
+ */
+
+ /**
+ * @brief Dispatch a generic GameEvent to the appropriate handler.
+ *
+ * This is the single entry point used by GameEventHandler. It extracts
+ * the correct protobuf extension and forwards the event to a typed
+ * handler method.
+ *
+ * @param type Game event type enum.
+ * @param event Generic protobuf container.
+ * @param context Additional context (undo, judge, etc.).
+ * @param options Processing options (UI suppression, reveal behavior).
+ */
+ void processGameEvent(GameEvent::GameEventType type,
+ const GameEvent &event,
+ const GameEventContext &context,
+ EventProcessingOptions options);
+
+ /** @} */
+
+ /** @name Chat and randomization events
+ * @{
+ */
+
+ /// Handle in-game chat messages from this player.
+ void eventGameSay(const Event_GameSay &event);
+
+ /// Handle zone shuffle events (typically libraries).
+ void eventShuffle(const Event_Shuffle &event);
+
+ /// Handle die roll events.
+ void eventRollDie(const Event_RollDie &event);
+
+ /** @} */
+
+ /** @name Arrow and targeting events
+ * @{
+ */
+
+ /// Create a visual arrow between cards or players.
+ void eventCreateArrow(const Event_CreateArrow &event);
+
+ /// Delete an existing arrow.
+ void eventDeleteArrow(const Event_DeleteArrow &event);
+
+ /** @} */
+
+ /** @name Token and card creation
+ * @{
+ */
+
+ /// Create a token card in a target zone.
+ void eventCreateToken(const Event_CreateToken &event);
+
+ /** @} */
+
+ /** @name Card attribute and counter updates
+ * @{
+ */
+
+ /**
+ * @brief Set a card attribute (tapped, PT, annotation, etc.).
+ *
+ * May apply to a single card or all cards in a zone if no card ID
+ * is provided by the event.
+ */
+ void
+ eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context, EventProcessingOptions options);
+
+ /// Update a counter attached to a card.
+ void eventSetCardCounter(const Event_SetCardCounter &event);
+
+ /// Create a player-level counter.
+ void eventCreateCounter(const Event_CreateCounter &event);
+
+ /// Set a player-level counter value.
+ void eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options);
+
+ /// Delete a player-level counter.
+ void eventDelCounter(const Event_DelCounter &event);
+
+ /** @} */
+
+ /** @name Zone-level operations
+ * @{
+ */
+
+ /// Log a zone dump (e.g. reveal graveyard/library contents).
+ void eventDumpZone(const Event_DumpZone &event);
+
+ /**
+ * @brief Move a card between zones and/or players.
+ *
+ * This is one of the most complex handlers:
+ * - Removes the card from the start zone
+ * - Updates card identity and ownership if needed
+ * - Handles attachments and arrows
+ * - Emits appropriate move or undo-draw logs
+ * - Inserts the card into the target zone
+ */
+ void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context);
+
+ /// Flip a card face up or face down.
+ void eventFlipCard(const Event_FlipCard &event);
+
+ /// Destroy a card and clean up attachments.
+ void eventDestroyCard(const Event_DestroyCard &event);
+
+ /// Attach or detach a card to/from another card.
+ void eventAttachCard(const Event_AttachCard &event);
+
+ /** @} */
+
+ /** @name Draw and reveal operations
+ * @{
+ */
+
+ /// Draw one or more cards from the deck.
+ void eventDrawCards(const Event_DrawCards &event);
+
+ /**
+ * @brief Reveal cards from a zone.
+ *
+ * Handles peeking, in-place top-card reveals, full reveal windows,
+ * and write-access granting.
+ */
+ void eventRevealCards(const Event_RevealCards &event, EventProcessingOptions options);
+
+ /** @} */
+
+ /** @name Zone configuration
+ * @{
+ */
+
+ /// Update zone visibility and reveal behavior.
+ void eventChangeZoneProperties(const Event_ChangeZoneProperties &event);
+
+ /** @} */
+
+ void eventGameLogNotice(const Event_GameLogNotice &event);
signals:
+ /** @name Logging signals
+ * @{
+ */
void logSay(PlayerLogic *player, QString message);
void logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end);
void logRollDie(PlayerLogic *player, int sides, const QList &rolls);
@@ -82,40 +261,13 @@ signals:
bool isLentToAnotherPlayer = false);
void logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal);
void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal);
+ /** @} */
+
void cardZoneChanged(CardItem *card, bool sameZone);
void requestCardMenuUpdate(const CardItem *card);
-public:
- PlayerEventHandler(PlayerLogic *player);
-
- void processGameEvent(GameEvent::GameEventType type,
- const GameEvent &event,
- const GameEventContext &context,
- EventProcessingOptions options);
-
- void eventGameSay(const Event_GameSay &event);
- void eventShuffle(const Event_Shuffle &event);
- void eventRollDie(const Event_RollDie &event);
- void eventCreateArrow(const Event_CreateArrow &event);
- void eventDeleteArrow(const Event_DeleteArrow &event);
- void eventCreateToken(const Event_CreateToken &event);
- void
- eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context, EventProcessingOptions options);
- void eventSetCardCounter(const Event_SetCardCounter &event);
- void eventCreateCounter(const Event_CreateCounter &event);
- void eventSetCounter(const Event_SetCounter &event);
- void eventDelCounter(const Event_DelCounter &event);
- void eventDumpZone(const Event_DumpZone &event);
- void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context);
- void eventFlipCard(const Event_FlipCard &event);
- void eventDestroyCard(const Event_DestroyCard &event);
- void eventAttachCard(const Event_AttachCard &event);
- void eventDrawCards(const Event_DrawCards &event);
- void eventRevealCards(const Event_RevealCards &event, EventProcessingOptions options);
- void eventChangeZoneProperties(const Event_ChangeZoneProperties &event);
- void eventGameLogNotice(const Event_GameLogNotice &event);
-
private:
+ /** Owning player instance. */
PlayerLogic *player;
void setCardAttrHelper(const GameEventContext &context,
diff --git a/cockatrice/src/game/player/player_logic.cpp b/cockatrice/src/game/player/player_logic.cpp
index 485e2fc5c..143df5c57 100644
--- a/cockatrice/src/game/player/player_logic.cpp
+++ b/cockatrice/src/game/player/player_logic.cpp
@@ -175,7 +175,15 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
const ServerInfo_Card &cardInfo = zoneInfo.card_list(j);
auto *card = new CardItem(this);
card->processCardInfo(cardInfo);
- zone->addCard(card, false, cardInfo.x(), cardInfo.y());
+ // Zones without coordinates (hand, piles, stack) preserve the order
+ // they arrive in on the server in the positions of their cards list.
+ // The x coordinate of such cards is always 0, so inserting at it
+ // would reverse the list on reconnect. Append instead.
+ if (zoneInfo.with_coords()) {
+ zone->addCard(card, false, cardInfo.x(), cardInfo.y());
+ } else {
+ zone->addCard(card, false, -1);
+ }
}
}
if (zoneInfo.has_always_reveal_top_card()) {
@@ -250,6 +258,22 @@ void PlayerLogic::setDeck(const DeckList &_deck)
emit deckChanged();
}
+void PlayerLogic::setPlaymatFromProperties(const ServerInfo_PlayerProperties &props)
+{
+ if (props.has_playmat_params() && !props.playmat_params().card_name().empty()) {
+ const auto &pp = props.playmat_params();
+ remotePlaymatCard = {QString::fromStdString(pp.card_name()), QString::fromStdString(pp.card_provider_id())};
+ remotePlaymatParams = {qBound(0.0, pp.margin_pct_l(), 0.95), qBound(0.0, pp.margin_pct_r(), 0.95),
+ qBound(0.0, pp.vertical_offset(), 1.0), qBound(0.1, pp.zoom(), 4.0)};
+ hasRemotePlaymat = true;
+ } else {
+ remotePlaymatCard = CardRef{};
+ remotePlaymatParams = PlaymatParams{};
+ hasRemotePlaymat = false;
+ }
+ emit playmatChanged();
+}
+
CounterState *PlayerLogic::addCounter(const ServerInfo_Counter &counter)
{
return addCounter(counter.id(), QString::fromStdString(counter.name()),
diff --git a/cockatrice/src/game/player/player_logic.h b/cockatrice/src/game/player/player_logic.h
index a89cb6eed..6923b3afe 100644
--- a/cockatrice/src/game/player/player_logic.h
+++ b/cockatrice/src/game/player/player_logic.h
@@ -17,6 +17,7 @@
#include "../zones/table_zone_logic.h"
#include "player_event_handler.h"
#include "player_info.h"
+#include "player_manager.h"
#include
#include
@@ -72,6 +73,8 @@ signals:
const QList &cardList,
bool withWritePermission);
void deckChanged();
+ /** @brief Emitted when the remote playmat (card/params) is updated from player properties. */
+ void playmatChanged();
void newCardAdded(AbstractCardItem *card);
void requestCardMenuUpdate(const CardItem *card);
void counterAdded(CounterState *state);
@@ -226,6 +229,20 @@ public:
void setZoneId(int _zoneId);
+ void setPlaymatFromProperties(const ServerInfo_PlayerProperties &props);
+ const CardRef &getRemotePlaymatCard() const
+ {
+ return remotePlaymatCard;
+ }
+ const PlaymatParams &getRemotePlaymatParams() const
+ {
+ return remotePlaymatParams;
+ }
+ bool getHasRemotePlaymat() const
+ {
+ return hasRemotePlaymat;
+ }
+
private:
AbstractGame *game;
PlayerInfo *playerInfo;
@@ -243,6 +260,11 @@ private:
bool dialogSemaphore;
QList cardsToDelete;
+
+ // Playmat from player properties (for opponent display)
+ CardRef remotePlaymatCard;
+ PlaymatParams remotePlaymatParams;
+ bool hasRemotePlaymat = false;
};
class AnnotationDialog : public QInputDialog
diff --git a/cockatrice/src/game/player/player_manager.cpp b/cockatrice/src/game/player/player_manager.cpp
index 6772d3ff1..8486efbeb 100644
--- a/cockatrice/src/game/player/player_manager.cpp
+++ b/cockatrice/src/game/player/player_manager.cpp
@@ -75,6 +75,14 @@ PlayerLogic *PlayerManager::getPlayer(int playerId) const
return player;
}
+void PlayerManager::clearSpectators()
+{
+ const QList spectatorIds = spectators.keys();
+ for (int spectatorId : spectatorIds) {
+ removeSpectator(spectatorId);
+ }
+}
+
void PlayerManager::onPlayerConceded(int playerId, bool conceded)
{
// Everything else cares about this
diff --git a/cockatrice/src/game/player/player_manager.h b/cockatrice/src/game/player/player_manager.h
index 2f8b87af8..504e65396 100644
--- a/cockatrice/src/game/player/player_manager.h
+++ b/cockatrice/src/game/player/player_manager.h
@@ -100,6 +100,9 @@ public:
emit spectatorRemoved(spectatorId, spectatorInfo);
}
+ /** @brief Remove all spectators, emitting the removal signal for each. */
+ void clearSpectators();
+
[[nodiscard]] AbstractGame *getGame() const
{
return game;
diff --git a/cockatrice/src/game/replay.cpp b/cockatrice/src/game/replay.cpp
index 69f9d8b20..dcf3e9b9b 100644
--- a/cockatrice/src/game/replay.cpp
+++ b/cockatrice/src/game/replay.cpp
@@ -2,7 +2,7 @@
#include "../interface/widgets/tabs/tab_game.h"
-Replay::Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent)
+Replay::Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent)
{
gameState = new GameState(this, 0, -1, isLocalGame, {}, false, false, -1, false);
connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged);
diff --git a/cockatrice/src/game/replay.h b/cockatrice/src/game/replay.h
index ecb3a10d0..1c269b273 100644
--- a/cockatrice/src/game/replay.h
+++ b/cockatrice/src/game/replay.h
@@ -15,7 +15,7 @@ class Replay : public AbstractGame
Q_OBJECT
public:
- explicit Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame);
+ explicit Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame);
};
#endif // COCKATRICE_REPLAY_H
diff --git a/cockatrice/src/game/zones/view_zone_logic.cpp b/cockatrice/src/game/zones/view_zone_logic.cpp
index 8782a1762..2ef04284a 100644
--- a/cockatrice/src/game/zones/view_zone_logic.cpp
+++ b/cockatrice/src/game/zones/view_zone_logic.cpp
@@ -3,6 +3,7 @@
#include "../../client/settings/cache_settings.h"
#include "../../game_graphics/board/card_item.h"
+#include
/**
* @param _player the player that the cards are revealed to.
* @param _origZone the zone the cards were revealed from.
@@ -57,7 +58,7 @@ bool ZoneViewZoneLogic::prepareAddCard(int x)
// autoclose check is done both here and in removeCard
- if (cards.isEmpty() && !doInsert && SettingsCache::instance().getCloseEmptyCardView()) {
+ if (cards.isEmpty() && !doInsert && SettingsCache::instance().userInterface().getCloseEmptyCardView()) {
emit closeView();
}
@@ -144,7 +145,7 @@ void ZoneViewZoneLogic::removeCard(int position, bool toNewZone)
// card gets dragged within the view.
// Another autoclose check is done in prepareAddCard so that the view autocloses if the last card was moved to an
// unrevealed portion of the same zone.
- if (cards.isEmpty() && SettingsCache::instance().getCloseEmptyCardView() && toNewZone) {
+ if (cards.isEmpty() && SettingsCache::instance().userInterface().getCloseEmptyCardView() && toNewZone) {
emit closeView();
return;
}
diff --git a/cockatrice/src/game_graphics/animated_item.h b/cockatrice/src/game_graphics/animated_item.h
new file mode 100644
index 000000000..700e0f62d
--- /dev/null
+++ b/cockatrice/src/game_graphics/animated_item.h
@@ -0,0 +1,26 @@
+#ifndef ANIMATED_ITEM_H
+#define ANIMATED_ITEM_H
+
+/**
+ * @file animated_item.h
+ * @ingroup GameGraphics
+ * @brief Interface for scene items driven by GameScene's shared animation timer.
+ *
+ * Items that want per-tick animation while a single QBasicTimer runs (instead of
+ * owning their own QTimer) implement this interface and register with the scene
+ * via GameScene::registerAnimationItem.
+ */
+
+class IAnimatedItem
+{
+public:
+ virtual ~IAnimatedItem() = default;
+
+ /**
+ * @brief Advances the item's animation by one timer tick.
+ * @return true while the animation is still running, false once it has finished.
+ */
+ virtual bool animationEvent() = 0;
+};
+
+#endif
diff --git a/cockatrice/src/game_graphics/board/abstract_card_drag_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_drag_item.cpp
index 026efd60d..2f43591ac 100644
--- a/cockatrice/src/game_graphics/board/abstract_card_drag_item.cpp
+++ b/cockatrice/src/game_graphics/board/abstract_card_drag_item.cpp
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
const QColor GHOST_MASK = QColor(255, 255, 255, 50);
@@ -34,12 +35,13 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
setCacheMode(DeviceCoordinateCache);
- connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
- Q_UNUSED(_roundCardCorners);
+ connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::roundCardCornersChanged, this,
+ [this](bool _roundCardCorners) {
+ Q_UNUSED(_roundCardCorners);
- prepareGeometryChange();
- update();
- });
+ prepareGeometryChange();
+ update();
+ });
connect(item, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater);
}
@@ -47,7 +49,8 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
QPainterPath AbstractCardDragItem::shape() const
{
QPainterPath shape;
- qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
+ qreal cardCornerRadius =
+ SettingsCache::instance().cardsDisplay().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp
index 86b3e27c8..3969b7d03 100644
--- a/cockatrice/src/game_graphics/board/abstract_card_item.cpp
+++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp
@@ -1,6 +1,7 @@
#include "abstract_card_item.h"
#include "../../client/settings/cache_settings.h"
+#include "../../interface/card_localization.h"
#include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../game_scene.h"
#include "../z_values.h"
@@ -12,6 +13,9 @@
#include
#include
#include
+#include
+#include
+#include
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id)
: ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0),
@@ -21,15 +25,19 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef
setFlag(ItemIsSelectable);
setCacheMode(DeviceCoordinateCache);
- connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); });
+ connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::displayCardNamesChanged, this,
+ [this] { update(); });
+ connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
+ [this] { update(); });
refreshCardInfo();
- connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
- Q_UNUSED(_roundCardCorners);
+ connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::roundCardCornersChanged, this,
+ [this](bool _roundCardCorners) {
+ Q_UNUSED(_roundCardCorners);
- prepareGeometryChange();
- update();
- });
+ prepareGeometryChange();
+ update();
+ });
}
AbstractCardItem::~AbstractCardItem()
@@ -45,7 +53,8 @@ QRectF AbstractCardItem::boundingRect() const
QPainterPath AbstractCardItem::shape() const
{
QPainterPath shape;
- qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
+ qreal cardCornerRadius =
+ SettingsCache::instance().cardsDisplay().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
@@ -101,7 +110,7 @@ QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle)
{
- const int MAX_FONT_SIZE = SettingsCache::instance().getMaxFontSize();
+ const int MAX_FONT_SIZE = SettingsCache::instance().appearance().getMaxFontSize();
const int fontSize = std::max(9, MAX_FONT_SIZE);
QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect());
@@ -151,7 +160,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
painter->drawPath(shape());
}
- if (translatedPixmap.isNull() || SettingsCache::instance().getDisplayCardNames() || facedown) {
+ if (translatedPixmap.isNull() || SettingsCache::instance().cardsDisplay().getDisplayCardNames() || facedown) {
painter->save();
transformPainter(painter, translatedSize, angle);
painter->setPen(Qt::white);
@@ -165,7 +174,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
if (SettingsCache::instance().debug().getShowCardId()) {
prefix = "#" + QString::number(id) + " ";
}
- nameStr = prefix + cardRef.name;
+ nameStr = prefix + CardLocalization::displayName(getCardInfo());
}
painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor,
translatedSize.height() - 6 * scaleFactor),
@@ -234,7 +243,7 @@ void AbstractCardItem::setHovered(bool _hovered)
isHovered = _hovered;
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
- setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
+ setScale(_hovered && SettingsCache::instance().cardsDisplay().getScaleCards() ? 1.1 : 1);
setTransformOriginPoint(_hovered ? CardDimensions::WIDTH_HALF_F : 0, _hovered ? CardDimensions::HEIGHT_HALF_F : 0);
update();
}
@@ -287,7 +296,7 @@ void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
}
tapped = _tapped;
- if (SettingsCache::instance().getTapAnimation() && canAnimate) {
+ if (SettingsCache::instance().cardsDisplay().getTapAnimation() && canAnimate) {
static_cast