diff --git a/.ci/Arch/Dockerfile b/.ci/Arch/Dockerfile
index b08e568f3..f37315262 100644
--- a/.ci/Arch/Dockerfile
+++ b/.ci/Arch/Dockerfile
@@ -8,7 +8,6 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \
gtest \
mariadb-libs \
ninja \
- openssl \
protobuf \
qt6-base \
qt6-declarative \
diff --git a/.ci/Debian12/Dockerfile b/.ci/Debian12/Dockerfile
index fc756aac2..0fa227d6f 100644
--- a/.ci/Debian12/Dockerfile
+++ b/.ci/Debian12/Dockerfile
@@ -15,15 +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-declarative-dev \
qt6-svg-dev \
+ qt6-shadertools-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
diff --git a/.ci/Debian13/Dockerfile b/.ci/Debian13/Dockerfile
index bdecb56df..13e8b35c7 100644
--- a/.ci/Debian13/Dockerfile
+++ b/.ci/Debian13/Dockerfile
@@ -16,15 +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-declarative-dev \
qt6-svg-dev \
+ qt6-shadertools-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
diff --git a/.ci/Fedora43/Dockerfile b/.ci/Fedora43/Dockerfile
index 463da5a51..68e894543 100644
--- a/.ci/Fedora43/Dockerfile
+++ b/.ci/Fedora43/Dockerfile
@@ -7,9 +7,8 @@ RUN dnf install -y \
git \
mariadb-devel \
ninja-build \
- openssl-devel \
protobuf-devel \
- qt6-{qtdeclarative,qtmultimedia,qtshadertools,qtsvg,qttools,qtwebsockets}-devel \
+ qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \
diff --git a/.ci/Fedora44/Dockerfile b/.ci/Fedora44/Dockerfile
index 62238e760..ffd7c1b9b 100644
--- a/.ci/Fedora44/Dockerfile
+++ b/.ci/Fedora44/Dockerfile
@@ -7,9 +7,8 @@ RUN dnf install -y \
git \
mariadb-devel \
ninja-build \
- openssl-devel \
protobuf-devel \
- qt6-{qtdeclarative,qtmultimedia,qtshadertools,qtsvg,qttools,qtwebsockets}-devel \
+ qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \
diff --git a/.ci/Servatrice_Debian12/Dockerfile b/.ci/Servatrice_Debian12/Dockerfile
index 321aa7c0f..21f6a036e 100644
--- a/.ci/Servatrice_Debian12/Dockerfile
+++ b/.ci/Servatrice_Debian12/Dockerfile
@@ -12,7 +12,6 @@ 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 715997474..12320c276 100644
--- a/.ci/Ubuntu24.04/Dockerfile
+++ b/.ci/Ubuntu24.04/Dockerfile
@@ -15,15 +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-declarative-dev \
qt6-svg-dev \
+ qt6-shadertools-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
diff --git a/.ci/Ubuntu26.04/Dockerfile b/.ci/Ubuntu26.04/Dockerfile
index 96dd10763..ce3d9cd6c 100644
--- a/.ci/Ubuntu26.04/Dockerfile
+++ b/.ci/Ubuntu26.04/Dockerfile
@@ -16,15 +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-declarative-dev \
qt6-svg-dev \
+ qt6-shadertools-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
diff --git a/.ci/compile.sh b/.ci/compile.sh
index f20432893..8a16d3243 100755
--- a/.ci/compile.sh
+++ b/.ci/compile.sh
@@ -149,9 +149,6 @@ if [[ $MAKE_TEST ]]; then
fi
if [[ $USE_CCACHE ]]; 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 [[ $CCACHE_SIZE ]]; then
# note, this setting persists after running the script
ccache --max-size "$CCACHE_SIZE"
@@ -327,32 +324,4 @@ if [[ $MAKE_PACKAGE ]]; then
BUILD_DIR="$BUILD_DIR" .ci/name_build.sh "$PACKAGE_SUFFIX"
echo "::endgroup::"
fi
-
- if [[ $RUNNER_OS == Windows ]]; then
- echo "::group::Check installer for build-tree artifacts"
- cd "$BUILD_DIR"
- package="$(find . -maxdepth 1 -type f -name 'Cockatrice-*.exe' -print -quit)"
- if [[ ! $package ]]; then
- echo "::error file=$0::Could not find installer to inspect"
- exit 1
- fi
- seven_zip="$(command -v 7z || true)"
- if [[ ! $seven_zip ]]; then
- seven_zip="/c/Program Files/7-Zip/7z.exe"
- fi
- if [[ ! -f $seven_zip ]]; then
- echo "::warning file=$0::7-Zip not found, skipping installer content check"
- else
- echo "Inspecting $package"
- # Fail the build if the installer contains any path left behind by the MSBuild or
- # Qt AUTOMOC tooling (build-tree artifacts must live in the build dir, not the install)
- if "$seven_zip" l "$package" |
- grep -E "_autogen|\.dir[\\/]|\.tlog|(^|[\\/])x64[\\/]|(^|[\\/])\.qt[\\/]|(^|[\\/])\.qsb[\\/]|(^|[\\/])\.lupdate[\\/]|CMakeFiles"; then
- echo "::error file=$0::Installer contains build-tree artifacts"
- exit 1
- fi
- echo "Installer content is clean"
- fi
- echo "::endgroup::"
- fi
fi
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 75fbc59f1..e895e2220 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -40,7 +40,7 @@ jobs:
steps:
- name: "Checkout repository"
- uses: actions/checkout@v7
+ uses: actions/checkout@v6
- name: "Initialize CodeQL"
uses: github/codeql-action/init@v4
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index bd528f245..04037a74e 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -152,7 +152,7 @@ jobs:
env:
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
CCACHE_EVICTION_AGE: 7d
- 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
+ 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
CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
@@ -176,12 +176,8 @@ jobs:
shell: bash
run: |
source .ci/docker.sh
- 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[@]}"
+ RUN --server --debug --test --ccache "$CCACHE_SIZE" \
+ --cmake-generator "$CMAKE_GENERATOR"
- name: "Build release package"
id: build
@@ -272,8 +268,8 @@ jobs:
make_package: 1
override_target: 13
package_suffix: "-macOS13_Intel"
- qt_version: 6.11.*
- qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
+ qt_version: 6.11.1
+ qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
soc: Intel
type: Release
use_ccache: 1
@@ -288,8 +284,8 @@ jobs:
make_package: 1
override_target: 14
package_suffix: "-macOS14"
- qt_version: 6.11.*
- qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
+ qt_version: 6.11.1
+ qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
soc: Apple
type: Release
use_ccache: 1
@@ -304,8 +300,8 @@ jobs:
make_package: 1
override_target: 15
package_suffix: "-macOS15"
- qt_version: 6.11.*
- qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
+ qt_version: 6.11.1
+ qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
soc: Apple
type: Release
use_ccache: 1
@@ -317,8 +313,8 @@ jobs:
ccache_eviction_age: 7d
cmake_generator: Ninja
- qt_version: 6.11.*
- qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
+ qt_version: 6.11.1
+ qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
soc: Apple
type: Debug
use_ccache: 1
@@ -332,8 +328,8 @@ jobs:
cmake_generator_platform: x64
make_package: 1
package_suffix: "-Win10"
- qt_version: 6.11.*
- qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
+ qt_version: 6.11.1
+ qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
type: Release
name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
@@ -342,7 +338,7 @@ jobs:
timeout-minutes: 100
env:
CCACHE_DIR: ${{ github.workspace }}/.cache/
- 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
+ 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
steps:
- name: "Checkout"
@@ -368,20 +364,18 @@ jobs:
key: ccache-${{ matrix.runner }}_${{ matrix.override_target }}-Xcode${{ matrix.xcode }}
path: ${{ env.CCACHE_DIR }}
- - name: "[macOS] Install aqtinstall"
- if: matrix.os == 'macOS'
+ - name: "Install aqtinstall"
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: "[macOS] Resolve latest Qt from ${{ matrix.qt_version }} input"
- if: matrix.os == 'macOS'
+ - name: "Resolve latest Qt patch version"
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 }}"
+ - name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
if: matrix.os == 'macOS'
id: restore_qt
uses: actions/cache/restore@v6
@@ -391,22 +385,21 @@ jobs:
# 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 ${{ matrix.qt_version }}"
+ - name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
cache: false
- # cache-key-prefix: Qt
- dir: ${{ github.workspace }} # thinning script depends on this location
+ dir: ${{ github.workspace }}
modules: ${{ matrix.qt_modules }}
- version: ${{ matrix.qt_version }}
+ version: ${{ steps.resolve_qt_version.outputs.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' && github.ref == 'refs/heads/master'
+ if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v6
with:
key: ${{ steps.restore_qt.outputs.cache-primary-key }}
@@ -418,10 +411,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: ${{ github.ref == 'refs/heads/master' }}
+ cache: true
cache-key-prefix: Qt
modules: ${{ matrix.qt_modules }}
- version: ${{ matrix.qt_version }}
+ version: ${{ steps.resolve_qt_version.outputs.version }}
- name: "[Windows] Install NSIS"
if: matrix.os == 'Windows'
@@ -453,7 +446,7 @@ 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 }},${{ case(github.ref == 'refs/heads/master', 'readwrite', 'read') }}"
+ VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
VCPKG_DISABLE_METRICS: 1
VCPKG_FEATURE_FLAGS: dependencygraph
run: .ci/compile.sh --server --test --vcpkg
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 967d94c58..df4fe233c 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -76,7 +76,7 @@ jobs:
uses: docker/build-push-action@v7
with:
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), '') }}
+ cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
context: .
platforms: ${{ matrix.platform }}
push: false
@@ -127,7 +127,7 @@ jobs:
steps:
- name: "Download digests"
- uses: actions/download-artifact@v8
+ uses: actions/download-artifact@v7
with:
path: ${{ runner.temp }}/digests
pattern: digest-*
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 293e25dd9..27fecc979 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..
-# 3.16 required for Qt6 and target_precompile_headers()
-cmake_minimum_required(VERSION 3.16)
+# cmake 3.16 is required if using qt6
+cmake_minimum_required(VERSION 3.10)
-# Use compiler cache (ccache)
+# Early detect 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 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 servatrice
+option(WITH_SERVER "build servatrice" OFF)
+# Compile cockatrice
+option(WITH_CLIENT "build cockatrice" ON)
+# Compile oracle
+option(WITH_ORACLE "build oracle" ON)
# 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)
@@ -39,24 +39,13 @@ else()
)
endif()
-# 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)
+if(USE_CCACHE)
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)
@@ -195,9 +184,6 @@ 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")
@@ -206,9 +192,6 @@ 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
@@ -256,6 +239,11 @@ 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()
@@ -293,7 +281,7 @@ if(UNIX)
if(CPACK_GENERATOR STREQUAL "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_MAIN_COMPONENT "cockatrice")
- set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qtimageformats, qt6-qtmultimedia, qt6-qtsvg, qt6-qttools")
+ set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia, qt6-qtimageformats")
set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games")
set(CPACK_RPM_PACKAGE_URL "http://github.com/Cockatrice/Cockatrice")
# stop directories from making package conflicts
@@ -311,7 +299,7 @@ if(UNIX)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
- set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-image-formats-plugins, qt6-qpa-plugins")
+ 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
endif()
endif()
diff --git a/Dockerfile b/Dockerfile
index 7d3deb5fb..382309d47 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,7 +14,6 @@ RUN apt-get update \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6sql6-mysql \
- libssl-dev \
qt6-websockets-dev \
protobuf-compiler \
qt6-tools-dev \
@@ -43,7 +42,6 @@ RUN apt-get update \
libprotobuf32t64 \
libqt6sql6-mysql \
libqt6websockets6 \
- libssl3 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
diff --git a/README.md b/README.md
index 5935bb540..f22df461f 100644
--- a/README.md
+++ b/README.md
@@ -149,15 +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 |
+| 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 971c9094d..0259d12e1 100644
--- a/cmake/FindQtRuntime.cmake
+++ b/cmake/FindQtRuntime.cmake
@@ -28,7 +28,7 @@ if(WITH_CLIENT)
)
endif()
if(WITH_ORACLE)
- set(_ORACLE_NEEDED Concurrent Network Svg Widgets Xml)
+ set(_ORACLE_NEEDED Concurrent Network Svg Widgets)
endif()
if(TEST)
# Union of Qt modules required across all test targets (independent of application targets).
diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in
index b3cbcece8..5af116470 100644
--- a/cmake/NSIS.template.in
+++ b/cmake/NSIS.template.in
@@ -387,14 +387,19 @@ SectionEnd
Section "un.Application" UnSecApplication
SetShellVarContext all
+ RMDir /r "$INSTDIR\plugins"
+ RMDir /r "$INSTDIR\sounds"
+ RMDir /r "$INSTDIR\themes"
+ RMDir /r "$INSTDIR\translations"
+ Delete "$INSTDIR\*.exe"
+ Delete "$INSTDIR\*.dll"
+ Delete "$INSTDIR\qt.conf"
+ Delete "$INSTDIR\qdebug.txt"
+ Delete "$INSTDIR\servatrice.sql"
+ Delete "$INSTDIR\servatrice.ini.example"
+ RMDir "$INSTDIR"
- ; Remove the entire application directory so any file that is not part of
- ; the installed payload (e.g. build-tree artifacts such as *.dir folders,
- ; *_autogen and *.tlog files from a build) cannot survive between an
- ; uninstall and a fresh reinstall.
- RMDir /r "$INSTDIR"
-
- RMDir /r "$SMPROGRAMS\Cockatrice"
+ RMDir "$SMPROGRAMS\Cockatrice"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice"
SectionEnd
diff --git a/cmake/pch/qtcore_pch.h b/cmake/pch/qtcore_pch.h
deleted file mode 100644
index cc3dd12ee..000000000
--- a/cmake/pch/qtcore_pch.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/** @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
deleted file mode 100644
index 2c63f450e..000000000
--- a/cmake/pch/qtwidgets_pch.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/** @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 9b31310e6..c00f1b9ce 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -45,14 +45,11 @@ set(cockatrice_SOURCES
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_login_prompt.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_share_deck.cpp
- src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp
src/interface/widgets/dialogs/dlg_settings.cpp
src/interface/widgets/dialogs/dlg_startup_card_check.cpp
src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp
@@ -60,9 +57,6 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_view_log.cpp
src/interface/widgets/dialogs/override_printing_warning.cpp
src/interface/widgets/dialogs/tip_of_the_day.cpp
- src/interface/widgets/deck_share/deck_share_utils.cpp
- src/interface/widgets/deck_share/shared_deck_preview_widget.cpp
- src/interface/widgets/deck_share/share_bar_widget.cpp
src/filters/deck_filter_string.cpp
src/filters/filter_builder.cpp
src/filters/filter_tree_model.cpp
@@ -169,11 +163,9 @@ set(cockatrice_SOURCES
src/interface/palette_editor/palette_grid_widget.cpp
src/interface/palette_editor/palette_editor_dialog.cpp
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
- src/interface/widgets/cards/additional_info/deck_color_identity.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
@@ -222,7 +214,6 @@ 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
@@ -280,7 +271,6 @@ 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
@@ -324,8 +314,6 @@ set(cockatrice_SOURCES
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp
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/deck_preview/public_deck_preview_widget.cpp
- src/interface/widgets/visual_deck_storage/remote_public_decks_model.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
@@ -391,13 +379,11 @@ 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_public_decks.cpp
src/interface/widgets/tabs/tab_report.cpp
src/interface/widgets/tabs/tab_replays.cpp
src/interface/widgets/tabs/tab_room.cpp
@@ -413,7 +399,6 @@ set(cockatrice_SOURCES
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
@@ -454,8 +439,6 @@ set(cockatrice_SOURCES
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/intent_open_shared_deck.cpp
- src/interface/intents/intent_open_shared_deck.h
src/interface/intents/url_parser.cpp
src/interface/intents/url_parser.h
src/interface/widgets/server/user/user_info_popup.cpp
@@ -532,8 +515,6 @@ qt6_add_executable(
MANUAL_FINALIZATION
)
-target_precompile_headers(cockatrice PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtwidgets_pch.h")
-
qt6_add_shaders(
cockatrice
"onboarding_shaders"
@@ -543,7 +524,6 @@ qt6_add_shaders(
"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(
@@ -668,35 +648,18 @@ if(WIN32)
set(qtconf_dest_dir .)
install(
- DIRECTORY "$/"
+ DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE}/"
DESTINATION ./
FILES_MATCHING
PATTERN "*.dll"
- PATTERN "*.pdb" EXCLUDE
- PATTERN "*.dir*" EXCLUDE
- PATTERN "*_autogen*" EXCLUDE
- PATTERN "*.tlog*" EXCLUDE
- PATTERN "CMakeFiles*" EXCLUDE
- PATTERN "x64*" EXCLUDE
- PATTERN ".qt*" EXCLUDE
- PATTERN ".qsb*" EXCLUDE
- PATTERN ".lupdate*" EXCLUDE
)
install(
DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/"
DESTINATION ./
FILES_MATCHING
+ PATTERN "CMakeFiles" EXCLUDE
PATTERN "*.ini"
- PATTERN "CMakeFiles*" EXCLUDE
- PATTERN "*.dir*" EXCLUDE
- PATTERN "*_autogen*" EXCLUDE
- PATTERN "*.tlog*" EXCLUDE
- PATTERN "*.pdb" EXCLUDE
- PATTERN "x64*" EXCLUDE
- PATTERN ".qt*" EXCLUDE
- PATTERN ".qsb*" EXCLUDE
- PATTERN ".lupdate*" EXCLUDE
)
# Qt plugins: audio, iconengines, imageformats, multimedia, platforms, printsupport, styles, tls
@@ -749,6 +712,10 @@ Data = Resources\")
"
COMPONENT Runtime
)
+
+ if(OPENSSL_FOUND)
+ install(FILES ${OPENSSL_INCLUDE_DIRS} DESTINATION ./)
+ endif()
endif()
if(Qt6LinguistTools_FOUND)
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index 14cf15b2f..e21bdb0be 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -63,8 +63,6 @@
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
@@ -365,8 +363,6 @@
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
deleted file mode 100644
index 68f48e2c2..000000000
Binary files a/cockatrice/resources/backgrounds/home-dark.png and /dev/null differ
diff --git a/cockatrice/resources/backgrounds/home-light.png b/cockatrice/resources/backgrounds/home-light.png
deleted file mode 100644
index eaaaba932..000000000
Binary files a/cockatrice/resources/backgrounds/home-light.png and /dev/null differ
diff --git a/cockatrice/resources/backgrounds/home.png b/cockatrice/resources/backgrounds/home.png
index eaaaba932..68f48e2c2 100644
Binary files a/cockatrice/resources/backgrounds/home.png and b/cockatrice/resources/backgrounds/home.png differ
diff --git a/cockatrice/resources/help/search.md b/cockatrice/resources/help/search.md
index fd0a12507..0c8bdb450 100644
--- a/cockatrice/resources/help/search.md
+++ b/cockatrice/resources/help/search.md
@@ -52,7 +52,6 @@ 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
deleted file mode 100644
index 57ed5c2da..000000000
--- a/cockatrice/resources/usericons/pawn_dev_double.svg
+++ /dev/null
@@ -1,343 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/usericons/pawn_dev_single.svg b/cockatrice/resources/usericons/pawn_dev_single.svg
deleted file mode 100644
index f7c4e7018..000000000
--- a/cockatrice/resources/usericons/pawn_dev_single.svg
+++ /dev/null
@@ -1,211 +0,0 @@
-
-
-
-
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 c1598bd25..890a621c8 100644
--- a/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
+++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
@@ -1,6 +1,5 @@
#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"
@@ -181,7 +180,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(themePixmap(QStringLiteral("cockatrice")).scaled(64, 64));
+ serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64));
serverShutdownMessageBox.setText(tr("Scheduled server shutdown"));
serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal);
serverShutdownMessageBox.setVisible(true);
diff --git a/cockatrice/src/client/sound_engine.cpp b/cockatrice/src/client/sound_engine.cpp
index 96cafa3d3..18de2264d 100644
--- a/cockatrice/src/client/sound_engine.cpp
+++ b/cockatrice/src/client/sound_engine.cpp
@@ -94,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");
@@ -104,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"
diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp
index a2b7519a8..4abb8210c 100644
--- a/cockatrice/src/filters/deck_filter_string.cpp
+++ b/cockatrice/src/filters/deck_filter_string.cpp
@@ -43,12 +43,6 @@ NumericValue <- [0-9]+
static std::once_flag init;
-// The peglib parser is a single permanent object, so the rule actions below cannot see
-// per-instance state. The card language that the nested [[card name]] search matches
-// against is passed through this thread-local context, which is live only while a
-// DeckFilterString is being parsed, and copied into the nested FilterString closures.
-thread_local CardSearchLanguage deckSearchLanguageContext;
-
static void setupParserRules()
{
// plumbing
@@ -122,7 +116,7 @@ static void setupParserRules()
// actual functionality
search["DeckContentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
- auto cardFilter = FilterString(std::any_cast(sv[0]), deckSearchLanguageContext);
+ 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 DeckSearchData &data) -> bool {
@@ -192,7 +186,7 @@ DeckFilterString::DeckFilterString()
_error = "Not initialized";
}
-DeckFilterString::DeckFilterString(const QString &expr, const CardSearchLanguage &searchLanguage)
+DeckFilterString::DeckFilterString(const QString &expr)
{
QByteArray ba = expr.simplified().toUtf8();
@@ -205,8 +199,6 @@ DeckFilterString::DeckFilterString(const QString &expr, const CardSearchLanguage
return;
}
- deckSearchLanguageContext = searchLanguage;
-
search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) {
_error = QString("Error at position %1: %2").arg(col).arg(QString::fromStdString(msg));
});
diff --git a/cockatrice/src/filters/deck_filter_string.h b/cockatrice/src/filters/deck_filter_string.h
index 5b1419004..90a6a17eb 100644
--- a/cockatrice/src/filters/deck_filter_string.h
+++ b/cockatrice/src/filters/deck_filter_string.h
@@ -12,7 +12,6 @@
#include
#include
#include
-#include
inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string");
@@ -36,7 +35,7 @@ class DeckFilterString
{
public:
DeckFilterString();
- explicit DeckFilterString(const QString &expr, const CardSearchLanguage &searchLanguage = {});
+ explicit DeckFilterString(const QString &expr);
bool check(const DeckSearchData &data) const
{
return filter(data);
diff --git a/cockatrice/src/filters/filter_builder.cpp b/cockatrice/src/filters/filter_builder.cpp
index f109fbcd3..785f753e7 100644
--- a/cockatrice/src/filters/filter_builder.cpp
+++ b/cockatrice/src/filters/filter_builder.cpp
@@ -1,6 +1,5 @@
#include "filter_builder.h"
-#include "../interface/pixel_map_generator.h"
#include "../interface/widgets/utility/custom_line_edit.h"
#include
@@ -22,7 +21,7 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
typeCombo->addItem(CardFilter::typeName(static_cast(i)), QVariant(i));
}
- QPushButton *ok = new QPushButton(themePixmap(QStringLiteral("icons/increment")), QString());
+ QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString());
ok->setObjectName("ok");
ok->setMaximumSize(20, 20);
diff --git a/cockatrice/src/game/board/counter_state.cpp b/cockatrice/src/game/board/counter_state.cpp
index 0970e4272..6da18b662 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, bool skipDamageAnimation)
+void CounterState::setValue(int newValue)
{
if (newValue == value) {
return;
}
int old = value;
value = newValue;
- emit valueChanged(old, newValue, skipDamageAnimation);
+ emit valueChanged(old, newValue);
}
\ 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 4c7b34473..0f2f16b55 100644
--- a/cockatrice/src/game/board/counter_state.h
+++ b/cockatrice/src/game/board/counter_state.h
@@ -35,23 +35,10 @@ public:
return value;
}
- /**
- * @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);
+ void setValue(int newValue);
signals:
- /**
- * @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);
+ void valueChanged(int oldValue, int newValue);
private:
int id;
diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp
index f146cdbb4..bc68d4d7c 100644
--- a/cockatrice/src/game/game_event_handler.cpp
+++ b/cockatrice/src/game/game_event_handler.cpp
@@ -430,13 +430,12 @@ void GameEventHandler::eventJoin(const Event_Join &event, int /*eventPlayerId*/,
QString playerName = QString::fromStdString(playerInfo.user_info().name());
emit addPlayerToAutoCompleteList(playerName);
- PlayerManager *playerManager = game->getPlayerManager();
- if (playerManager->getPlayers().contains(playerId) || playerManager->getSpectators().contains(playerId)) {
+ if (game->getPlayerManager()->getPlayers().contains(playerId)) {
return;
}
if (playerInfo.spectator()) {
- playerManager->addSpectator(playerId, playerInfo);
+ game->getPlayerManager()->addSpectator(playerId, playerInfo);
emit logJoinSpectator(playerName);
emit spectatorJoined(playerInfo);
} else {
diff --git a/cockatrice/src/game/player/event_processing_options.h b/cockatrice/src/game/player/event_processing_options.h
index 06238d77e..4c7663789 100644
--- a/cockatrice/src/game/player/event_processing_options.h
+++ b/cockatrice/src/game/player/event_processing_options.h
@@ -13,8 +13,7 @@
enum EventProcessingOption
{
SKIP_REVEAL_WINDOW = 0x0001,
- SKIP_TAP_ANIMATION = 0x0002,
- SKIP_DAMAGE_ANIMATION = 0x0004
+ SKIP_TAP_ANIMATION = 0x0002
};
// Wrap it in a QFlags typedef
diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp
index 277b8b1d4..bc48298f7 100644
--- a/cockatrice/src/game/player/player_event_handler.cpp
+++ b/cockatrice/src/game/player/player_event_handler.cpp
@@ -262,15 +262,14 @@ void PlayerEventHandler::eventCreateCounter(const Event_CreateCounter &event)
player->addCounter(event.counter_info());
}
-void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options)
+void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event)
{
CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr);
if (!ctr) {
return;
}
int oldValue = ctr->getValue();
- const bool skipDamageAnimation = options.testFlag(SKIP_DAMAGE_ANIMATION);
- ctr->setValue(event.value(), skipDamageAnimation);
+ ctr->setValue(event.value());
emit logSetCounter(player, ctr->getName(), event.value(), oldValue);
}
@@ -626,7 +625,7 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
eventCreateCounter(event.GetExtension(Event_CreateCounter::ext));
break;
case GameEvent::SET_COUNTER:
- eventSetCounter(event.GetExtension(Event_SetCounter::ext), options);
+ eventSetCounter(event.GetExtension(Event_SetCounter::ext));
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 300cacd08..48ad85e88 100644
--- a/cockatrice/src/game/player/player_event_handler.h
+++ b/cockatrice/src/game/player/player_event_handler.h
@@ -153,7 +153,7 @@ public:
void eventCreateCounter(const Event_CreateCounter &event);
/// Set a player-level counter value.
- void eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options);
+ void eventSetCounter(const Event_SetCounter &event);
/// Delete a player-level counter.
void eventDelCounter(const Event_DelCounter &event);
diff --git a/cockatrice/src/game/player/player_logic.cpp b/cockatrice/src/game/player/player_logic.cpp
index 143df5c57..45ba09aac 100644
--- a/cockatrice/src/game/player/player_logic.cpp
+++ b/cockatrice/src/game/player/player_logic.cpp
@@ -175,15 +175,7 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
const ServerInfo_Card &cardInfo = zoneInfo.card_list(j);
auto *card = new CardItem(this);
card->processCardInfo(cardInfo);
- // 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);
- }
+ zone->addCard(card, false, cardInfo.x(), cardInfo.y());
}
}
if (zoneInfo.has_always_reveal_top_card()) {
diff --git a/cockatrice/src/game/player/player_manager.cpp b/cockatrice/src/game/player/player_manager.cpp
index 8486efbeb..6772d3ff1 100644
--- a/cockatrice/src/game/player/player_manager.cpp
+++ b/cockatrice/src/game/player/player_manager.cpp
@@ -75,14 +75,6 @@ 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 504e65396..2f8b87af8 100644
--- a/cockatrice/src/game/player/player_manager.h
+++ b/cockatrice/src/game/player/player_manager.h
@@ -100,9 +100,6 @@ 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_graphics/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp
index 3969b7d03..1410d0c80 100644
--- a/cockatrice/src/game_graphics/board/abstract_card_item.cpp
+++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp
@@ -1,7 +1,6 @@
#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"
@@ -27,8 +26,6 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::displayCardNamesChanged, this,
[this] { update(); });
- connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
- [this] { update(); });
refreshCardInfo();
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::roundCardCornersChanged, this,
@@ -174,7 +171,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
if (SettingsCache::instance().debug().getShowCardId()) {
prefix = "#" + QString::number(id) + " ";
}
- nameStr = prefix + CardLocalization::displayName(getCardInfo());
+ nameStr = prefix + cardRef.name;
}
painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor,
translatedSize.height() - 6 * scaleFactor),
diff --git a/cockatrice/src/game_graphics/board/abstract_counter.cpp b/cockatrice/src/game_graphics/board/abstract_counter.cpp
index 4ba04804f..e63117e13 100644
--- a/cockatrice/src/game_graphics/board/abstract_counter.cpp
+++ b/cockatrice/src/game_graphics/board/abstract_counter.cpp
@@ -29,9 +29,9 @@ AbstractCounter::AbstractCounter(CounterState *state,
{
setAcceptHoverEvents(true);
- connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue, bool skipDamageAnimation) {
+ connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) {
value = newValue;
- onValueChanged(oldValue, newValue, skipDamageAnimation);
+ onValueChanged(oldValue, newValue);
update();
});
@@ -230,7 +230,7 @@ void AbstractCounterDialog::changeValue(int diff)
setTextValue(QString::number(curValue));
}
-void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/, bool /*skipDamageAnimation*/)
+void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/)
{
// Default: no feedback. Subclasses such as PlayerCounter override this to
// flash the counter on meaningful changes (life gain/loss).
diff --git a/cockatrice/src/game_graphics/board/abstract_counter.h b/cockatrice/src/game_graphics/board/abstract_counter.h
index 67b5b4074..9ddcc6d58 100644
--- a/cockatrice/src/game_graphics/board/abstract_counter.h
+++ b/cockatrice/src/game_graphics/board/abstract_counter.h
@@ -39,9 +39,8 @@ protected:
* @brief Hook for subclasses that need per-value-change feedback (e.g. life-total flash).
*
* Called whenever the counter's value changes, before the item repaints.
- * @param skipDamageAnimation True when damage-related feedback should be suppressed (replay rewinds).
*/
- virtual void onValueChanged(int oldValue, int newValue, bool skipDamageAnimation);
+ virtual void onValueChanged(int oldValue, int newValue);
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
diff --git a/cockatrice/src/game_graphics/board/card_item.cpp b/cockatrice/src/game_graphics/board/card_item.cpp
index c2dc455cc..c40c8c214 100644
--- a/cockatrice/src/game_graphics/board/card_item.cpp
+++ b/cockatrice/src/game_graphics/board/card_item.cpp
@@ -316,7 +316,7 @@ void CardItem::drawAttachArrow()
for (const auto &item : scene()->selectedItems()) {
CardItem *card = qgraphicsitem_cast(item);
- if (card == nullptr || card == this) {
+ if (card == nullptr) {
continue;
}
if (card->getZone() != state->getZone()) {
diff --git a/cockatrice/src/game_graphics/deckview/deck_view.cpp b/cockatrice/src/game_graphics/deckview/deck_view.cpp
index 1acd02a75..1278737a0 100644
--- a/cockatrice/src/game_graphics/deckview/deck_view.cpp
+++ b/cockatrice/src/game_graphics/deckview/deck_view.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item,
@@ -380,10 +381,12 @@ void DeckViewScene::rebuildTree()
addItem(container);
}
- // Cards in custom zones nested under a board are regular board cards in-game.
- // They are collected recursively (like every other consumer) and reported with
- // the top-level board zone as their origin, so that sideboard plans keep working.
- for (auto *currentCard : deck->getCardNodes({currentZone->getName()})) {
+ for (int j = 0; j < currentZone->size(); j++) {
+ auto *currentCard = dynamic_cast(currentZone->at(j));
+ if (!currentCard) {
+ continue;
+ }
+
for (int k = 0; k < currentCard->getNumber(); ++k) {
auto *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName());
container->addCard(newCard);
diff --git a/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp b/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp
index 6d8ad0534..b311d2ebd 100644
--- a/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp
+++ b/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp
@@ -16,13 +16,11 @@
#include
#include
#include
-#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
@@ -90,17 +88,6 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa
cardDatabaseDisplayModel = new TokenDisplayModel(this);
cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel);
- const auto applyCardSearchLanguage = [this]() {
- const CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay();
- cardDatabaseDisplayModel->setSearchLanguage(CardSearchLanguage{
- cardsDisplay.getCardLang(), static_cast(cardsDisplay.getCardSearchLanguage())});
- };
- applyCardSearchLanguage();
- connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
- applyCardSearchLanguage);
- connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardSearchLanguageChanged, this,
- applyCardSearchLanguage);
-
chooseTokenFromAllRadioButton = new QRadioButton(tr("Show &all tokens"));
connect(chooseTokenFromAllRadioButton, &QRadioButton::toggled, this, &DlgCreateToken::actChooseTokenFromAll);
chooseTokenFromDeckRadioButton = new QRadioButton(tr("Show tokens from this &deck"));
diff --git a/cockatrice/src/game_graphics/game_scene.cpp b/cockatrice/src/game_graphics/game_scene.cpp
index 17af7618b..cd2b12828 100644
--- a/cockatrice/src/game_graphics/game_scene.cpp
+++ b/cockatrice/src/game_graphics/game_scene.cpp
@@ -44,16 +44,11 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
GameScene::~GameScene()
{
- // Sever all destroyed->removeAnimatedItem connections before the members below
- // are destroyed: the base QGraphicsScene destructor destroys the remaining items,
- // and their destroyed() signals must not reach slots that reference members that
- // no longer exist. The connection handle overload is used because the string-based
- // disconnect(nullptr, nullptr, this, nullptr) is invalid (the sender must never be
- // nullptr) and would otherwise fail to sever these pointer-to-member connections.
- for (auto it = animationItemConnections.constBegin(); it != animationItemConnections.constEnd(); ++it) {
- QObject::disconnect(*it);
- }
- animationItemConnections.clear();
+ // Sever all incoming connections (animated item destroy-tracking) before the
+ // members below are destroyed: the base QGraphicsScene destructor destroys the
+ // remaining items, and their destroyed() signals must not reach slots that
+ // reference members that no longer exist.
+ disconnect(this);
delete animationTimer;
animationTimer = nullptr;
@@ -221,12 +216,7 @@ void GameScene::removePlayer(PlayerLogic *player)
clearArrowsForPlayer(player->getPlayerInfo()->getId());
- // Closing a view removes it from zoneViews synchronously, so iterate over a
- // copy: otherwise a player with several open views (e.g. library and hand)
- // only has the first one closed here and the remaining views are left
- // pointing at a player that is about to be deleted.
- const QList zoneViewCopy = zoneViews;
- for (ZoneViewWidget *zone : zoneViewCopy) {
+ for (ZoneViewWidget *zone : zoneViews) {
if (zone->getPlayer() == player) {
zone->close();
}
@@ -669,10 +659,7 @@ CardItem *GameScene::findTopmostCardInZone(const QList &items,
*/
void GameScene::toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed)
{
- // Closing a view removes it from zoneViews synchronously, so iterate over a
- // copy to make sure every already-open matching view is closed.
- const QList zoneViewCopy = zoneViews;
- for (auto *view : zoneViewCopy) {
+ for (auto &view : zoneViews) {
ZoneViewZone *temp = view->getZone();
if (temp->getLogic()->getName() == zoneName && temp->getLogic()->getPlayer() == player &&
qobject_cast(temp->getLogic())->getNumberCards() == numberCards) {
@@ -790,15 +777,8 @@ void GameScene::registerAnimationItem(IAnimatedItem *item)
if (!object) {
return;
}
- // Guard against duplicate connections using the connection map, not
- // animatedItems: the animation timer removes entries from animatedItems when an
- // animation completes, but the destroyed->removeAnimatedItem connection must
- // persist until the object is destroyed. Relying on animatedItems here would let
- // a re-registered item (e.g. a life counter that flashes repeatedly) accumulate
- // duplicate destroyed connections, the older ones of which would survive teardown.
- if (!animationItemConnections.contains(object)) {
- animationItemConnections.insert(object,
- connect(object, &QObject::destroyed, this, &GameScene::removeAnimatedItem));
+ if (!animatedItems.contains(object)) {
+ connect(object, &QObject::destroyed, this, &GameScene::removeAnimatedItem);
}
animatedItems.insert(object, item);
if (animationTimer && !animationTimer->isActive()) {
@@ -817,7 +797,6 @@ void GameScene::unregisterAnimationItem(IAnimatedItem *item)
void GameScene::removeAnimatedItem(QObject *item)
{
animatedItems.remove(item);
- animationItemConnections.remove(item);
if (animationTimer && animatedItems.isEmpty()) {
animationTimer->stop();
}
diff --git a/cockatrice/src/game_graphics/game_scene.h b/cockatrice/src/game_graphics/game_scene.h
index 859d7a6eb..c12696189 100644
--- a/cockatrice/src/game_graphics/game_scene.h
+++ b/cockatrice/src/game_graphics/game_scene.h
@@ -54,11 +54,9 @@ private:
QPointer hoveredCard; ///< Currently hovered card
QBasicTimer *animationTimer; ///< Timer for scene animations
QHash animatedItems; ///< Items currently animating
- QHash
- animationItemConnections; ///< destroyed->removeAnimatedItem handles per animated item
- int playerRotation; ///< Rotation offset for player layout
- bool rearranging = false; ///< Guard against re-entrant rearrange
- bool needsReArrange = false; ///< Pending rearrange requested during a pass
+ int playerRotation; ///< Rotation offset for player layout
+ bool rearranging = false; ///< Guard against re-entrant rearrange
+ bool needsReArrange = false; ///< Pending rearrange requested during a pass
/**
* @brief Updates which card is currently hovered based on scene coordinates.
diff --git a/cockatrice/src/game_graphics/hand_counter.cpp b/cockatrice/src/game_graphics/hand_counter.cpp
index 8dcbcfdaa..35989ff38 100644
--- a/cockatrice/src/game_graphics/hand_counter.cpp
+++ b/cockatrice/src/game_graphics/hand_counter.cpp
@@ -1,6 +1,5 @@
#include "hand_counter.h"
-#include "../interface/pixel_map_generator.h"
#include "zones/card_zone.h"
#include
@@ -33,8 +32,7 @@ void HandCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*op
QSize translatedSize = painter->combinedTransform().mapRect(boundingRect()).size().toSize();
QPixmap cachedPixmap;
if (!QPixmapCache::find("handCounter" + QString::number(translatedSize.width()), &cachedPixmap)) {
- cachedPixmap =
- themePixmap(QStringLiteral("hand")).scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ cachedPixmap = QPixmap("theme:hand").scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPixmapCache::insert("handCounter" + QString::number(translatedSize.width()), cachedPixmap);
}
resetPainterTransform(painter);
diff --git a/cockatrice/src/game_graphics/player/menu/tally_menu.cpp b/cockatrice/src/game_graphics/player/menu/tally_menu.cpp
index 08cb6cac9..7eb3945b3 100644
--- a/cockatrice/src/game_graphics/player/menu/tally_menu.cpp
+++ b/cockatrice/src/game_graphics/player/menu/tally_menu.cpp
@@ -12,13 +12,11 @@ TallyMenu::TallyMenu()
aTallyNone = createTallyAction(TallyType::None);
aTallySubtypes = createTallyAction(TallyType::Subtypes);
aTallyTotalPower = createTallyAction(TallyType::TotalPower);
- aTallyTotalToughness = createTallyAction(TallyType::TotalToughness);
addAction(aTallyNone);
addSeparator();
addAction(aTallySubtypes);
addAction(aTallyTotalPower);
- addAction(aTallyTotalToughness);
retranslateUi();
}
@@ -56,5 +54,4 @@ void TallyMenu::retranslateUi()
aTallyNone->setText(tr("None"));
aTallySubtypes->setText(tr("Subtypes"));
aTallyTotalPower->setText(tr("Total Power"));
- aTallyTotalToughness->setText(tr("Total Toughness"));
}
diff --git a/cockatrice/src/game_graphics/player/menu/tally_menu.h b/cockatrice/src/game_graphics/player/menu/tally_menu.h
index 11802fd20..acd1daf67 100644
--- a/cockatrice/src/game_graphics/player/menu/tally_menu.h
+++ b/cockatrice/src/game_graphics/player/menu/tally_menu.h
@@ -24,7 +24,6 @@ private:
QAction *aTallyNone = nullptr;
QAction *aTallySubtypes = nullptr;
QAction *aTallyTotalPower = nullptr;
- QAction *aTallyTotalToughness = nullptr;
QAction *createTallyAction(TallyType tallyType);
};
diff --git a/cockatrice/src/game_graphics/player/player_graphics_item.cpp b/cockatrice/src/game_graphics/player/player_graphics_item.cpp
index 122ab83be..8bf2703e1 100644
--- a/cockatrice/src/game_graphics/player/player_graphics_item.cpp
+++ b/cockatrice/src/game_graphics/player/player_graphics_item.cpp
@@ -3,7 +3,6 @@
#include "../../game/player/player_actions.h"
#include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../../interface/widgets/cards/art_crop_attribution.h"
-#include "../../interface/widgets/cards/card_art_utils.h"
#include "../../interface/widgets/playmat/playmat_utils.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../board/abstract_card_item.h"
@@ -252,8 +251,8 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
AbstractCounter *widget;
if (state->getName() == "life") {
widget = playerTarget->addCounter(state);
- connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue, bool skipDamageAnimation) {
- if (newValue < oldValue && !skipDamageAnimation) {
+ connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) {
+ if (newValue < oldValue) {
tableZoneGraphicsItem->triggerDamageShimmer();
}
});
@@ -443,7 +442,7 @@ void PlayerGraphicsItem::updatePlaymat()
hasPlaymat = true;
emit playmatChanged(true);
}
- playmatPixmap = CardArtUtils::rotateSidewaysLayoutArt(fullRes, card);
+ playmatPixmap = fullRes;
update();
}
diff --git a/cockatrice/src/game_graphics/player/player_list_widget.cpp b/cockatrice/src/game_graphics/player/player_list_widget.cpp
index a7ec2e4a9..4268e1019 100644
--- a/cockatrice/src/game_graphics/player/player_list_widget.cpp
+++ b/cockatrice/src/game_graphics/player/player_list_widget.cpp
@@ -53,13 +53,13 @@ PlayerListWidget::PlayerListWidget(TabSupervisor *_tabSupervisor,
QWidget *parent)
: QTreeWidget(parent), tabSupervisor(_tabSupervisor), client(_client), game(_game), gameStarted(false)
{
- readyIcon = themePixmap(QStringLiteral("icons/ready_start"));
- notReadyIcon = themePixmap(QStringLiteral("icons/not_ready_start"));
- concededIcon = themePixmap(QStringLiteral("icons/conceded"));
+ readyIcon = QPixmap("theme:icons/ready_start");
+ notReadyIcon = QPixmap("theme:icons/not_ready_start");
+ concededIcon = QPixmap("theme:icons/conceded");
playerIcon = loadColorAdjustedPixmap("theme:icons/player");
judgeIcon = loadColorAdjustedPixmap("theme:icons/scales");
spectatorIcon = loadColorAdjustedPixmap("theme:icons/spectator");
- lockIcon = themePixmap(QStringLiteral("icons/lock"));
+ lockIcon = QPixmap("theme:icons/lock");
if (tabSupervisor) {
itemDelegate = new PlayerListItemDelegate(this);
@@ -92,11 +92,6 @@ void PlayerListWidget::retranslateUi()
void PlayerListWidget::addPlayer(const ServerInfo_PlayerProperties &player)
{
- if (players.contains(player.player_id())) {
- updatePlayerProperties(player);
- return;
- }
-
QTreeWidgetItem *newPlayer = new PlayerListTWI;
players.insert(player.player_id(), newPlayer);
updatePlayerProperties(player);
@@ -181,17 +176,6 @@ void PlayerListWidget::removePlayer(int playerId)
delete takeTopLevelItem(indexOfTopLevelItem(player));
}
-void PlayerListWidget::clearSpectators()
-{
- const QList playerIds = players.keys();
- for (int playerId : playerIds) {
- QTreeWidgetItem *player = players.value(playerId, 0);
- if (player && !player->data(1, Qt::UserRole).toBool()) {
- removePlayer(playerId);
- }
- }
-}
-
void PlayerListWidget::setActivePlayer(int playerId)
{
QMapIterator i(players);
diff --git a/cockatrice/src/game_graphics/player/player_list_widget.h b/cockatrice/src/game_graphics/player/player_list_widget.h
index f2f0be5fd..a53cfa989 100644
--- a/cockatrice/src/game_graphics/player/player_list_widget.h
+++ b/cockatrice/src/game_graphics/player/player_list_widget.h
@@ -66,7 +66,6 @@ public slots:
void addPlayer(const ServerInfo_PlayerProperties &player);
void removePlayer(int playerId);
void updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId = -1);
- void clearSpectators();
};
#endif
diff --git a/cockatrice/src/game_graphics/player/player_target.cpp b/cockatrice/src/game_graphics/player/player_target.cpp
index d6c28370d..910ee9c17 100644
--- a/cockatrice/src/game_graphics/player/player_target.cpp
+++ b/cockatrice/src/game_graphics/player/player_target.cpp
@@ -69,7 +69,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*
}
}
-void PlayerCounter::onValueChanged(int oldValue, int newValue, bool skipDamageAnimation)
+void PlayerCounter::onValueChanged(int oldValue, int newValue)
{
flashDelta = newValue - oldValue;
if (flashDelta == 0) {
@@ -81,11 +81,6 @@ void PlayerCounter::onValueChanged(int oldValue, int newValue, bool skipDamageAn
return;
}
- if (skipDamageAnimation) {
- flashAlpha = 0.0;
- return;
- }
-
flashAlpha = 1.0;
flashClock.start();
if (scene()) {
@@ -137,18 +132,8 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o
QRectF translatedRect = painter->combinedTransform().mapRect(avatarBoundingRect);
QSize translatedSize = translatedRect.size().toSize();
QPixmap cachedPixmap;
- // The key must cover everything the generated pawn depends on: the rendered
- // size, the user level, and the pixmap being drawn. fullPixmap.cacheKey() is
- // 0 for every null pixmap, so the default-pawn branch additionally needs the
- // pawn's privlevel (lowercased, matching UserLevelPixmapGenerator) and colors
- // in the key — otherwise two players without a custom avatar (and the same
- // user level) would share one cached pawn.
const QString cacheKey = "avatar" + QString::number(translatedSize.width()) + "_" +
- QString::number(translatedSize.height()) + "_" + QString::number(info->user_level()) +
- "_" + QString::number(fullPixmap.cacheKey()) + "_" +
- QString::fromStdString(info->privlevel()).toLower() + "_" +
- QString::fromStdString(info->pawn_colors().left_side()) + "_" +
- QString::fromStdString(info->pawn_colors().right_side());
+ QString::number(info->user_level()) + "_" + QString::number(fullPixmap.cacheKey());
if (!QPixmapCache::find(cacheKey, &cachedPixmap)) {
cachedPixmap = QPixmap(translatedSize.width(), translatedSize.height());
diff --git a/cockatrice/src/game_graphics/player/player_target.h b/cockatrice/src/game_graphics/player/player_target.h
index 1d06c6274..af0e9c8b7 100644
--- a/cockatrice/src/game_graphics/player/player_target.h
+++ b/cockatrice/src/game_graphics/player/player_target.h
@@ -21,7 +21,7 @@ class PlayerCounter : public AbstractCounter, public IAnimatedItem
{
Q_OBJECT
protected:
- void onValueChanged(int oldValue, int newValue, bool skipDamageAnimation) override;
+ void onValueChanged(int oldValue, int newValue) override;
private:
static constexpr qreal flashDurationMs = 450.0;
diff --git a/cockatrice/src/game_graphics/tally/stats_tally.cpp b/cockatrice/src/game_graphics/tally/stats_tally.cpp
index 7e05c3fb1..e7a6621fa 100644
--- a/cockatrice/src/game_graphics/tally/stats_tally.cpp
+++ b/cockatrice/src/game_graphics/tally/stats_tally.cpp
@@ -34,31 +34,3 @@ QList StatsTally::computeTotalPower(const QList &cards)
QString name = QCoreApplication::translate("StatsTally", "Total Power");
return {TallyRow{name, QString::number(total)}};
}
-
-static int sumToughness(const QList &cards)
-{
- int total = 0;
- for (auto card : cards) {
- QVariantList parsed = CardItem::parsePT(card->getPT());
- if (parsed.size() == 2) {
- int toughness = parsed.at(1).toInt(); // toInt will default to 0 if it's not an int
- total += qMax(toughness, 0);
- }
- }
- return total;
-}
-
-QList StatsTally::computeTotalToughness(const QList &cards)
-{
- // don't bother if none of the cards have pt
- bool hasPT =
- std::any_of(cards.cbegin(), cards.cend(), [](const CardItem *card) { return !card->getPT().isEmpty(); });
- if (!hasPT) {
- return {};
- }
-
- int total = sumToughness(cards);
-
- QString name = QCoreApplication::translate("StatsTally", "Total Toughness");
- return {TallyRow{name, QString::number(total)}};
-}
diff --git a/cockatrice/src/game_graphics/tally/stats_tally.h b/cockatrice/src/game_graphics/tally/stats_tally.h
index e499587eb..4c3d93b56 100644
--- a/cockatrice/src/game_graphics/tally/stats_tally.h
+++ b/cockatrice/src/game_graphics/tally/stats_tally.h
@@ -16,14 +16,6 @@ namespace StatsTally
*/
QList computeTotalPower(const QList &cards);
-/**
- * @brief Sums the toughness of all selected cards
- *
- * @param cards The list of selected card items to analyze.
- * @return A single row containing the total, or an empty list if none of the cards have pt
- */
-QList computeTotalToughness(const QList &cards);
-
} // namespace StatsTally
#endif // COCKATRICE_STATS_TALLY_H
diff --git a/cockatrice/src/game_graphics/tally/tally.cpp b/cockatrice/src/game_graphics/tally/tally.cpp
index 21806ee84..aa2cae024 100644
--- a/cockatrice/src/game_graphics/tally/tally.cpp
+++ b/cockatrice/src/game_graphics/tally/tally.cpp
@@ -21,8 +21,6 @@ QList Tally::compute(const QList &cards, const TallyType t
return SubtypeTally::countSubtypes(cards);
case TallyType::TotalPower:
return StatsTally::computeTotalPower(cards);
- case TallyType::TotalToughness:
- return StatsTally::computeTotalToughness(cards);
}
return {};
}
diff --git a/cockatrice/src/game_graphics/tally/tally.h b/cockatrice/src/game_graphics/tally/tally.h
index 84c54918f..97406cddb 100644
--- a/cockatrice/src/game_graphics/tally/tally.h
+++ b/cockatrice/src/game_graphics/tally/tally.h
@@ -21,8 +21,7 @@ enum class TallyType
None,
Subtypes,
TotalPower,
- TotalToughness,
- MaxValue = TotalToughness // sentinel value
+ MaxValue = TotalPower // sentinel value
};
namespace Tally
diff --git a/cockatrice/src/game_graphics/zones/hand_zone.cpp b/cockatrice/src/game_graphics/zones/hand_zone.cpp
index 1a8f7a910..b52a4955a 100644
--- a/cockatrice/src/game_graphics/zones/hand_zone.cpp
+++ b/cockatrice/src/game_graphics/zones/hand_zone.cpp
@@ -41,8 +41,7 @@ void HandZone::handleDropEvent(const QList &dragItems,
}
}
} else {
- bool sameZone = startZone == getLogic();
- x = calcDropIndexFromY(dropPoint.y(), !sameZone);
+ x = calcDropIndexFromY(dropPoint.y());
}
Command_MoveCard cmd;
diff --git a/cockatrice/src/game_graphics/zones/select_zone.cpp b/cockatrice/src/game_graphics/zones/select_zone.cpp
index 470c70fcf..c58c41b92 100644
--- a/cockatrice/src/game_graphics/zones/select_zone.cpp
+++ b/cockatrice/src/game_graphics/zones/select_zone.cpp
@@ -83,7 +83,7 @@ SelectZone::StackLayoutParams SelectZone::buildStackParams(qreal minOffset) cons
return {cardCount, boundingRect().height(), cardHeight, offset, minOffset};
}
-int SelectZone::calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal minOffset) const
+int SelectZone::calcDropIndexFromY(qreal dropY, qreal minOffset) const
{
const auto &cards = getLogic()->getCards();
if (cards.isEmpty()) {
@@ -94,8 +94,7 @@ int SelectZone::calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal min
if (effectiveOffset <= 0.0) {
return 0;
}
- int max = allowCountExpand ? params.cardCount : params.cardCount - 1;
- return qBound(0, qRound((dropY - start) / effectiveOffset), max);
+ return qBound(0, qRound((dropY - start) / effectiveOffset), params.cardCount - 1);
}
void SelectZone::restoreStaleEscapedCards()
diff --git a/cockatrice/src/game_graphics/zones/select_zone.h b/cockatrice/src/game_graphics/zones/select_zone.h
index b5d3ca37a..7408f29b6 100644
--- a/cockatrice/src/game_graphics/zones/select_zone.h
+++ b/cockatrice/src/game_graphics/zones/select_zone.h
@@ -104,12 +104,8 @@ protected:
/**
* @brief Computes the card index at a given y-coordinate within the zone's vertical layout.
* Returns 0 if the zone has no cards or the offset is zero.
- *
- * @param dropY The y-coordinate that the card was dropped at
- * @param allowCountExpand If false, clamps the index at the number of cards minus 1
- * @param minOffset Minimum offset to preserve
*/
- int calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal minOffset = 0.0) const;
+ int calcDropIndexFromY(qreal dropY, qreal minOffset = 0.0) const;
/**
* @brief Positions cards vertically with alternating left/right x-offsets.
diff --git a/cockatrice/src/game_graphics/zones/stack_zone.cpp b/cockatrice/src/game_graphics/zones/stack_zone.cpp
index ff62097c7..e9b14f13d 100644
--- a/cockatrice/src/game_graphics/zones/stack_zone.cpp
+++ b/cockatrice/src/game_graphics/zones/stack_zone.cpp
@@ -57,14 +57,18 @@ void StackZone::handleDropEvent(const QList &dragItems,
return;
}
- bool sameZone = startZone == getLogic();
- int index = calcDropIndexFromY(dropPoint.y(), !sameZone, MIN_CARD_VISIBLE);
- if (sameZone) {
+ const auto &cards = getLogic()->getCards();
+ int index;
+ if (startZone == getLogic()) {
+ // Reordering within the zone: use drop position
+ index = calcDropIndexFromY(dropPoint.y(), MIN_CARD_VISIBLE);
// Same-zone no-op: don't move a card onto itself
- const auto &cards = getLogic()->getCards();
if (!cards.isEmpty() && cards.at(index)->getId() == dragItems.at(0)->getId()) {
return;
}
+ } else {
+ // Coming from another zone: append at end (top of stack, rendered on top)
+ index = static_cast(cards.size());
}
Command_MoveCard cmd;
diff --git a/cockatrice/src/game_graphics/zones/view_zone.cpp b/cockatrice/src/game_graphics/zones/view_zone.cpp
index 5bd5d262f..baf7b8b30 100644
--- a/cockatrice/src/game_graphics/zones/view_zone.cpp
+++ b/cockatrice/src/game_graphics/zones/view_zone.cpp
@@ -1,6 +1,5 @@
#include "view_zone.h"
-#include "../../client/settings/cache_settings.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game/zones/view_zone_logic.h"
@@ -12,13 +11,11 @@
#include
#include
#include
-#include
#include
#include
#include
#include
#include
-#include
/**
* @param parent the parent QGraphicsWidget containing the reveal zone
@@ -256,10 +253,7 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca
void ZoneViewZone::setFilterString(const QString &_filterString)
{
- const CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay();
- filterString = FilterString(
- _filterString, CardSearchLanguage{cardsDisplay.getCardLang(),
- static_cast(cardsDisplay.getCardSearchLanguage())});
+ filterString = FilterString(_filterString);
reorganizeCards();
}
diff --git a/cockatrice/src/game_graphics/zones/view_zone_widget.cpp b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp
index fa6733413..17118e80d 100644
--- a/cockatrice/src/game_graphics/zones/view_zone_widget.cpp
+++ b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp
@@ -21,7 +21,6 @@
#include
#include
#include
-#include
#include
namespace
@@ -63,7 +62,7 @@ ZoneViewWidget::ZoneViewWidget(PlayerLogic *_player,
searchEdit.setPlaceholderText(tr("Search by card name (or search expressions)"));
searchEdit.setClearButtonEnabled(true);
searchEdit.addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
- auto help = searchEdit.addAction(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
+ auto help = searchEdit.addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(&searchEdit); });
@@ -169,12 +168,6 @@ ZoneViewWidget::ZoneViewWidget(PlayerLogic *_player,
}
connect(&searchEdit, &QLineEdit::textChanged, zone, &ZoneViewZone::setFilterString);
-
- const auto applyCardSearchLanguage = [this] { zone->setFilterString(searchEdit.text()); };
- connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
- applyCardSearchLanguage);
- connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardSearchLanguageChanged, this,
- applyCardSearchLanguage);
}
setLayout(vbox);
@@ -556,7 +549,7 @@ void ZoneViewWidget::initStyleOption(QStyleOption *option) const
{
QStyleOptionTitleBar *titleBar = qstyleoption_cast(option);
if (titleBar) {
- titleBar->icon = themePixmap(QStringLiteral("cockatrice"));
+ titleBar->icon = QPixmap("theme:cockatrice");
}
}
diff --git a/cockatrice/src/interface/card_localization.h b/cockatrice/src/interface/card_localization.h
deleted file mode 100644
index 0bfe4a764..000000000
--- a/cockatrice/src/interface/card_localization.h
+++ /dev/null
@@ -1,59 +0,0 @@
-#ifndef COCKATRICE_CARD_LOCALIZATION_H
-#define COCKATRICE_CARD_LOCALIZATION_H
-
-#include "../client/settings/cache_settings.h"
-
-#include
-#include
-#include
-
-namespace CardLocalization
-{
-/**
- * @brief The language code selected for localized card text and images.
- */
-inline QString displayLang()
-{
- return SettingsCache::instance().cardsDisplay().getCardLang();
-}
-
-/**
- * @brief Card name in the configured display language, falling back to English.
- * @param card The card to display.
- * @return The localized name, or an empty string for a null card.
- */
-inline QString displayName(const CardInfoPtr &card)
-{
- return card.isNull() ? QString() : card->getLocalizedName(displayLang());
-}
-
-/**
- * @brief Card rules text in the configured display language, falling back to English.
- * @param card The card to display.
- * @return The localized text, or an empty string for a null card.
- */
-inline QString displayText(const CardInfoPtr &card)
-{
- return card.isNull() ? QString() : card->getLocalizedText(displayLang());
-}
-
-/**
- * @brief Card name in the configured display language, falling back to English.
- * @param card The card to display.
- */
-inline QString displayName(const CardInfo &card)
-{
- return card.getLocalizedName(displayLang());
-}
-
-/**
- * @brief Card rules text in the configured display language, falling back to English.
- * @param card The card to display.
- */
-inline QString displayText(const CardInfo &card)
-{
- return card.getLocalizedText(displayLang());
-}
-} // namespace CardLocalization
-
-#endif // COCKATRICE_CARD_LOCALIZATION_H
\ No newline at end of file
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp
index b8a54761a..2f46e7941 100644
--- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp
@@ -1,14 +1,13 @@
#include "card_picture_loader.h"
#include "../../client/settings/cache_settings.h"
-#include "../pixel_map_generator.h"
#include "card_picture_loader_cache_method.h"
#include "card_picture_loader_local_schemes.h"
#include
#include
#include
-#include
+#include
#include
#include
#include
@@ -20,7 +19,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -38,10 +36,8 @@ CardPictureLoader::CardPictureLoader() : QObject(nullptr)
&CardPictureLoader::picsPathChanged);
connect(&SettingsCache::instance().downloads(), &DownloadSettings::picDownloadChanged, this,
&CardPictureLoader::picDownloadChanged);
- connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
- &CardPictureLoader::cardLangChanged);
- qRegisterMetaType("ExactCard");
+ qRegisterMetaType();
connect(worker, &CardPictureLoaderWorker::imageLoaded, this, &CardPictureLoader::imageLoaded);
statusBar = new CardPictureLoaderStatusBar(nullptr);
@@ -66,7 +62,7 @@ void CardPictureLoader::getCardBackPixmap(QPixmap &pixmap, QSize size)
QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + "x" + QString::number(size.height());
if (!QPixmapCache::find(backCacheKey, &pixmap)) {
qCDebug(CardPictureLoaderLog) << "PictureLoader: cache miss for" << backCacheKey;
- QPixmap tmpPixmap = themePixmap(QStringLiteral("cardback"));
+ QPixmap tmpPixmap("theme:cardback");
if (tmpPixmap.isNull()) {
qCWarning(CardPictureLoaderLog) << "Failed to load 'theme:cardback'! Using fallback pixmap.";
@@ -87,7 +83,7 @@ void CardPictureLoader::getCardBackLoadingInProgressPixmap(QPixmap &pixmap, QSiz
"_trice_card_back_inprogress_" + QString::number(size.width()) + "x" + QString::number(size.height());
if (!QPixmapCache::find(backCacheKey, &pixmap)) {
qCDebug(CardPictureLoaderCardBackCacheFailLog) << "PictureLoader: cache miss for" << backCacheKey;
- QPixmap tmpPixmap = themePixmap(QStringLiteral("cardback"));
+ QPixmap tmpPixmap("theme:cardback");
if (tmpPixmap.isNull()) {
qCWarning(CardPictureLoaderLog) << "Failed to load 'theme:cardback' for in-progress state! Using fallback.";
@@ -109,7 +105,7 @@ void CardPictureLoader::getCardBackLoadingFailedPixmap(QPixmap &pixmap, QSize si
"_trice_card_back_failed_" + QString::number(size.width()) + "x" + QString::number(size.height());
if (!QPixmapCache::find(backCacheKey, &pixmap)) {
qCDebug(CardPictureLoaderCardBackCacheFailLog) << "PictureLoader: cache miss for" << backCacheKey;
- QPixmap tmpPixmap = themePixmap(QStringLiteral("cardback"));
+ QPixmap tmpPixmap("theme:cardback");
if (tmpPixmap.isNull()) {
qCWarning(CardPictureLoaderLog) << "Failed to load 'theme:cardback' for failed state! Using fallback.";
@@ -142,8 +138,7 @@ void CardPictureLoader::getPixmap(QPixmap &pixmap, const ExactCard &card, QSize
QPixmap bigPixmap;
if (QPixmapCache::find(key, &bigPixmap)) {
if (bigPixmap.isNull()) {
- // Leave the pixmap null so callers fall back to a solid color
- // instead of showing the card back.
+ getCardBackLoadingFailedPixmap(pixmap, size);
QDateTime failedAtTime = getInstance().failedAt.value(key);
if (!failedAtTime.isValid() ||
failedAtTime.addSecs(RETRY_FAILED_CARDS_SECS) < QDateTime::currentDateTime()) {
@@ -209,49 +204,7 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
card.emitPixmapUpdated();
}
-void CardPictureLoader::deleteAllLocalOverrides(const ExactCard &card)
-{
- const QString picsRoot = SettingsCache::instance().paths().getPicsPath();
- if (picsRoot.isEmpty() || !card) {
- return;
- }
-
- QDir baseDir(picsRoot);
- if (!baseDir.cd("downloadedPics")) {
- return;
- }
-
- const QString name = card.getInfo().getCorrectedName();
-
- QString set, collector, uuid;
- auto printing = card.getPrinting();
- if (printing.getSet()) {
- set = printing.getSet()->getCorrectedShortName();
- collector = printing.getProperty("num");
- uuid = printing.getUuid();
- }
-
- for (const auto &scheme : CardPictureLoaderLocalSchemes::exportSchemes()) {
- QString rel = CardPictureLoaderLocalSchemes::expandPattern(scheme.pattern, name, set, collector, uuid);
-
- if (rel.isEmpty()) {
- continue;
- }
-
- rel += ".png";
- rel = QDir::cleanPath(rel);
-
- QString fullPath = baseDir.filePath(rel);
-
- if (QFile::exists(fullPath)) {
- QFile::remove(fullPath);
- }
- }
-}
-
-void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card,
- const QPixmap &pixmap,
- const bool allowOverwrite)
+void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap)
{
if (pixmap.isNull() || !card) {
return;
@@ -311,9 +264,8 @@ void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card,
QFileInfo outInfo(baseDir.filePath(relativePath));
- // Automatic cache writes (FILESYSTEM_CACHE) must never clobber an explicit user override.
- // Only the explicit override paths pass allowOverwrite == true.
- if (!allowOverwrite && outInfo.exists()) {
+ // Do not overwrite existing files
+ if (outInfo.exists()) {
return;
}
@@ -334,122 +286,6 @@ void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card,
}
}
-void CardPictureLoader::installPrintingOverrideOnLoad(const ExactCard &originalCard, const ExactCard &overrideCard)
-{
- // Overriding a card with itself is the reset case, not a real override: every code path below
- // would re-enter itself through emitPixmapUpdated(). Reject it outright.
- if (originalCard == overrideCard) {
- return;
- }
-
- CardInfoPtr cardPtr = overrideCard.getCardPtr();
- if (!cardPtr) {
- return;
- }
-
- // Heap-allocate so the lambda can capture it before the connection is made
- auto *connectionHandle = new QMetaObject::Connection;
-
- *connectionHandle =
- connect(cardPtr.data(), &CardInfo::pixmapUpdated, cardPtr.data(),
- [originalCard, overrideCard, connectionHandle, this](const PrintingInfo &printing) {
- // All printings share the same CardInfo, so ignore updates triggered by any
- // other printing (e.g., the original card re-loading from disk).
- if (printing != overrideCard.getPrinting()) {
- return;
- }
-
- QPixmap pixmap;
- if (QPixmapCache::find(overrideCard.getPixmapCacheKey(), &pixmap) && !pixmap.isNull()) {
- // The override art has resolved — persist it and reflect it immediately.
- // Retire the connection before emitting so the refresh can't re-enter.
- saveCardImageToLocalStorage(originalCard, pixmap, /*allowOverwrite=*/true);
-
- QObject::disconnect(*connectionHandle);
- delete connectionHandle;
-
- QPixmapCache::clear();
- originalCard.emitPixmapUpdated();
- return;
- }
-
- // The art could not be resolved. Keep the connection armed so a late resolution
- // still lands, and surface a visible refusal instead of a silent no-op. An
- // override already on disk is left untouched and simply re-displayed.
- QPixmapCache::clear();
- if (!hasLocalOverrides(originalCard)) {
- QPixmap refusedPixmap;
- getCardBackLoadingFailedPixmap(refusedPixmap, QSize(480, 672));
- QPixmapCache::insert(originalCard.getPixmapCacheKey(), refusedPixmap);
- }
- originalCard.emitPixmapUpdated();
- });
-
- // Now enqueue; if the image is already loading (deduplicated in the worker),
- // the signal will still fire when it completes
- CardPictureLoader::getInstance().worker->enqueueImageLoad(overrideCard);
-}
-
-void CardPictureLoader::installPrintingOverride(const ExactCard &originalCard, const ExactCard &overrideCard)
-{
- // Same guard as installPrintingOverrideOnLoad: self-override is the reset case.
- if (originalCard == overrideCard) {
- return;
- }
-
- QPixmap pixmap;
- const QString key = overrideCard.getPixmapCacheKey();
-
- if (QPixmapCache::find(key, &pixmap) && !pixmap.isNull()) {
- // Already cached — save immediately; the caller refreshes the card.
- saveCardImageToLocalStorage(originalCard, pixmap, /*allowOverwrite=*/true);
- return;
- }
-
- // Cache miss or previously failed load — enqueue load and wait for the signal.
- installPrintingOverrideOnLoad(originalCard, overrideCard);
-}
-
-bool CardPictureLoader::hasLocalOverrides(const ExactCard &card)
-{
- const QString picsRoot = SettingsCache::instance().paths().getPicsPath();
- if (picsRoot.isEmpty() || !card) {
- return false;
- }
-
- QDir baseDir(picsRoot);
- if (!baseDir.cd("downloadedPics")) {
- return false;
- }
-
- const QString name = card.getInfo().getCorrectedName();
-
- QString set, collector, uuid;
- const PrintingInfo printing = card.getPrinting();
- if (printing.getSet()) {
- set = printing.getSet()->getCorrectedShortName();
- collector = printing.getProperty("num");
- uuid = printing.getUuid();
- }
-
- for (const auto &scheme : CardPictureLoaderLocalSchemes::exportSchemes()) {
- QString rel = CardPictureLoaderLocalSchemes::expandPattern(scheme.pattern, name, set, collector, uuid);
-
- if (rel.isEmpty()) {
- continue;
- }
-
- rel += ".png";
- rel = QDir::cleanPath(rel);
-
- if (QFile::exists(baseDir.filePath(rel))) {
- return true;
- }
- }
-
- return false;
-}
-
void CardPictureLoader::clearPixmapCache()
{
QPixmapCache::clear();
@@ -489,11 +325,31 @@ void CardPictureLoader::picsPathChanged()
QPixmapCache::clear();
}
-void CardPictureLoader::cardLangChanged()
+bool CardPictureLoader::hasCustomArt()
{
- // Localized images are fetched via a different URL, but the in-memory
- // pixmap cache is keyed by card name/uuid, so drop everything cached
- // (including failure timestamps) to force a reload in the new language.
- QPixmapCache::clear();
- failedAt.clear();
+ auto picsPath = SettingsCache::instance().paths().getPicsPath();
+ QDirIterator it(picsPath, QDir::Dirs | QDir::NoDotAndDotDot);
+
+ // Check if there is at least one non-directory file in the pics path, other
+ // than in the "downloadedPics" subdirectory.
+ while (it.hasNext()) {
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
+ QFileInfo dir(it.nextFileInfo());
+#else
+ // nextFileInfo() is only available in Qt 6.3+, for previous versions, we build
+ // the QFileInfo from a QString which requires more system calls.
+ QFileInfo dir(it.next());
+#endif
+
+ if (it.fileName() == "downloadedPics") {
+ continue;
+ }
+
+ QDirIterator subIt(it.filePath(), QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
+ if (subIt.hasNext()) {
+ return true;
+ }
+ }
+
+ return false;
}
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h
index 0a4934e6d..5c3ac84a3 100644
--- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h
@@ -97,17 +97,10 @@ public:
static void cacheCardPixmaps(const QList &cards);
/**
- * @brief Check if a local override image already exists for the card.
- * @param card The card to check.
- * @return True if the card has at least one locally stored override image.
+ * @brief Check if the user has custom card art in the picsPath directory.
+ * @return True if any custom art exists.
*/
- static bool hasLocalOverrides(const ExactCard &card);
-
- /**
- * @brief Removes all locally stored override images for the card.
- * @param card The card to remove the override images of.
- */
- static void deleteAllLocalOverrides(const ExactCard &card);
+ static bool hasCustomArt();
/**
* @brief Clears the in-memory QPixmap cache for all cards.
@@ -127,9 +120,7 @@ public slots:
* @param image Loaded QImage.
*/
void imageLoaded(const ExactCard &card, const QImage &image);
- void saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap, bool allowOverwrite = false);
- void installPrintingOverride(const ExactCard &originalCard, const ExactCard &overrideCard);
- void installPrintingOverrideOnLoad(const ExactCard &originalCard, const ExactCard &overrideCard);
+ void saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap);
private slots:
/**
@@ -143,12 +134,6 @@ private slots:
* Clears the QPixmap cache to reload images.
*/
void picsPathChanged();
-
- /**
- * @brief Triggered when the card language setting changes.
- * Clears the in-memory picture caches so images reload in the new language.
- */
- void cardLangChanged();
};
#endif
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp
index c82fca403..39621839a 100644
--- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp
@@ -94,10 +94,6 @@ QImage CardPictureLoaderLocal::tryLoadCardImageFromDisk(const QString &setName,
candidatePaths << picsPath + "/downloadedPics/" + setName + "/" + nameVariant;
}
- // Non-set-folder export schemes (e.g., Name_Set_Collector) write straight into
- // downloadedPics/; check there as a fallback so local overrides round-trip.
- candidatePaths << picsPath + "/downloadedPics/" + nameVariant;
-
for (const QString &path : candidatePaths) {
QFileInfo fileInfo(path);
QDir dir = fileInfo.dir();
@@ -109,8 +105,7 @@ QImage CardPictureLoaderLocal::tryLoadCardImageFromDisk(const QString &setName,
QStringList files = dir.entryList(QDir::Files);
for (const QString &file : files) {
- QFileInfo fi(file);
- if (fi.completeBaseName() != baseName) {
+ if (!file.startsWith(baseName)) {
continue;
}
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp
index 34092f361..d288236d2 100644
--- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp
@@ -84,8 +84,8 @@ void CardPictureLoaderWorker::queueRequest(const QUrl &url, CardPictureLoaderWor
SettingsCache::instance().cacheStorage().getCardPictureLoaderCacheMethod()) ==
CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE &&
cache->metaData(url).isValid()) {
- // A request that will be served from the disk cache never touches the network and therefore
- // doesn't use up any of the rate limit, so it gets to skip the queue.
+ // If we hit a cached url, we get to make the request for free, since it won't contribute towards the
+ // rate-limit
makeRequest(url, worker);
return;
}
@@ -107,13 +107,10 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
req.setRawHeader("Accept", "image/avif,image/webp,image/apng,image/,/*;q=0.8");
- // Cached entries are served straight from the disk cache even when picture downloads are
- // enabled: re-fetching an already-cached image would burn the rate limit for nothing. Only a
- // genuine cache miss goes to the network, and only when downloads are enabled.
- bool useNetworkCache = static_cast(
- SettingsCache::instance().cacheStorage().getCardPictureLoaderCacheMethod()) ==
- CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE &&
- (cache->metaData(url).isValid() || !picDownload);
+ bool useNetworkCache =
+ !picDownload && static_cast(
+ SettingsCache::instance().cacheStorage().getCardPictureLoaderCacheMethod()) ==
+ CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE;
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
useNetworkCache ? QNetworkRequest::AlwaysCache : QNetworkRequest::AlwaysNetwork);
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp
index 4f933207e..5f4ff0bbd 100644
--- a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp
@@ -282,15 +282,8 @@ QString CardPictureToLoad::transformUrl(const QString &urlTemplate) const
}
// language setting
- const QString cardLang = SettingsCache::instance().cardsDisplay().getCardLang();
- transformMap["!sflang!"] = cardLang;
-
- // The localized printing's own id is unknown, so Scryfall must resolve it by
- // its translated name (see populateSetUrls); expose that name for the
- // `/cards/named` template.
- if (cardLang != "en") {
- transformMap["!localizedName!"] = card.getInfo().getLocalizedName(cardLang);
- }
+ transformMap["!sflang!"] = QString(QCoreApplication::translate(
+ "PictureLoader", "en", "code for scryfall's language property, not available for all languages"));
QString transformedUrl = urlTemplate;
for (const QString &prop : transformMap.keys()) {
diff --git a/cockatrice/src/interface/deck_loader/deck_file_format.h b/cockatrice/src/interface/deck_loader/deck_file_format.h
index 3a25797ec..995de32c0 100644
--- a/cockatrice/src/interface/deck_loader/deck_file_format.h
+++ b/cockatrice/src/interface/deck_loader/deck_file_format.h
@@ -17,7 +17,7 @@ enum Format
PlainText,
/**
- * This is Cockatrice's native deck file format, and supports deck metadata such as banner cards and tags.
+ * This is cockatrice's native deck file format, and supports deck metadata such as banner cards and tags.
* Stored as .cod files.
*/
Cockatrice
diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp
index f29b4eed2..39a0c1071 100644
--- a/cockatrice/src/interface/deck_loader/deck_loader.cpp
+++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp
@@ -50,7 +50,7 @@ DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bo
result = deckList.loadFromFile_Native(&file);
if (!result) {
qCInfo(DeckLoaderLog) << "Failed to load " << fileName
- << "as Cockatrice format; retrying as plain format";
+ << "as cockatrice format; retrying as plain format";
file.seek(0);
result = deckList.loadFromFile_Plain(&file, CardNameNormalizer());
fmt = DeckFileFormat::PlainText;
@@ -375,32 +375,15 @@ void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList &deckL
void DeckLoader::saveToStream_DeckZone(QTextStream &out,
const InnerDecklistNode *zoneNode,
bool addComments,
- bool addSetNameAndNumber,
- const QString &boardZoneName)
+ bool addSetNameAndNumber)
{
- // Nested sub-zones keep their owning board's identity: the top-level call
- // passes no board, so the zone's own name is used; recursive calls carry the
- // owning board down so the sideboard marker survives sub-zone nesting.
- const QString owningBoardZoneName = boardZoneName.isEmpty() ? zoneNode->getName() : boardZoneName;
-
// group cards by card type and count the subtotals
QMultiMap cardsByType;
QMap cardTotalByType;
int cardTotal = 0;
- QList subZones;
for (int j = 0; j < zoneNode->size(); j++) {
auto *card = dynamic_cast(zoneNode->at(j));
- if (!card) {
- // Cards collected in nested sub-zones are exported by recursion so
- // they don't end up invisible in the plain text output. They are
- // deferred until after this zone's own header and cards so they read
- // as part of this zone's block.
- if (auto *subZone = dynamic_cast(zoneNode->at(j))) {
- subZones.append(subZone);
- }
- continue;
- }
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(card->getName());
QString cardType = info ? info->getMainCardType() : "unknown";
@@ -428,30 +411,25 @@ void DeckLoader::saveToStream_DeckZone(QTextStream &out,
QList cards = cardsByType.values(cardType);
- saveToStream_DeckZoneCards(out, cards, addComments, addSetNameAndNumber, owningBoardZoneName);
+ saveToStream_DeckZoneCards(out, zoneNode, cards, addComments, addSetNameAndNumber);
if (addComments) {
out << "\n";
}
}
-
- // Nested sub-zones come last, after the parent's own header and cards.
- for (const auto *subZone : subZones) {
- saveToStream_DeckZone(out, subZone, addComments, addSetNameAndNumber, owningBoardZoneName);
- }
}
void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
+ const InnerDecklistNode *zoneNode,
QList cards,
bool addComments,
- bool addSetNameAndNumber,
- const QString &boardZoneName)
+ bool addSetNameAndNumber)
{
// QMultiMap sorts values in reverse order
for (int i = cards.size() - 1; i >= 0; --i) {
DecklistCardNode *card = cards[i];
- if (boardZoneName == DECK_ZONE_SIDE && addComments) {
+ if (zoneNode->getName() == DECK_ZONE_SIDE && addComments) {
out << "SB: ";
}
@@ -532,26 +510,9 @@ bool DeckLoader::convertToCockatriceFormat(LoadedDeck &deck)
void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node)
{
- if (!node || node->isEmpty()) {
- return;
- }
-
const int totalColumns = 2;
- // Dispatch children by type instead of trusting a whole-node height: a deck
- // node may hold direct cards and nested zones side by side (custom zones),
- // and an empty node would previously crash on at(0).
- QVector cards;
- QVector subZones;
- for (int i = 0; i < node->size(); i++) {
- if (auto *card = dynamic_cast(node->at(i))) {
- cards.append(card);
- } else if (auto *zone = dynamic_cast(node->at(i))) {
- subZones.append(zone);
- }
- }
-
- if (!cards.isEmpty()) {
+ if (node->height() == 1) {
QTextBlockFormat blockFormat;
QTextCharFormat charFormat;
charFormat.setFontPointSize(11);
@@ -562,9 +523,9 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
tableFormat.setCellPadding(0);
tableFormat.setCellSpacing(0);
tableFormat.setBorder(0);
- QTextTable *table = cursor->insertTable(cards.size() + 1, totalColumns, tableFormat);
- for (int i = 0; i < cards.size(); i++) {
- const AbstractDecklistCardNode *card = cards[i];
+ QTextTable *table = cursor->insertTable(node->size() + 1, totalColumns, tableFormat);
+ for (int i = 0; i < node->size(); i++) {
+ auto *card = dynamic_cast(node->at(i));
QTextCharFormat cellCharFormat;
cellCharFormat.setFontPointSize(9);
@@ -579,13 +540,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
cellCursor = cell.firstCursorPosition();
cellCursor.insertText(card->getName());
}
- }
-
- for (const InnerDecklistNode *subZone : subZones) {
- if (subZone->isEmpty()) {
- continue;
- }
-
+ } else if (node->height() == 2) {
QTextBlockFormat blockFormat;
QTextCharFormat charFormat;
charFormat.setFontPointSize(14);
@@ -604,8 +559,10 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
tableFormat.setColumnWidthConstraints(constraints);
QTextTable *table = cursor->insertTable(1, totalColumns, tableFormat);
- QTextCursor cellCursor = table->cellAt(0, 0).firstCursorPosition();
- printDeckListNode(&cellCursor, subZone);
+ for (int i = 0; i < node->size(); i++) {
+ QTextCursor cellCursor = table->cellAt(0, (i * totalColumns) / node->size()).lastCursorPosition();
+ printDeckListNode(&cellCursor, dynamic_cast(node->at(i)));
+ }
}
cursor->movePosition(QTextCursor::End);
diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h
index be0df311d..ac23e1ee0 100644
--- a/cockatrice/src/interface/deck_loader/deck_loader.h
+++ b/cockatrice/src/interface/deck_loader/deck_loader.h
@@ -131,7 +131,7 @@ public:
static void printDeckList(QPrinter *printer, const DeckList &deckList);
/**
- * Converts the given deck's file to the Cockatrice file format.
+ * Converts the given deck's file to the cockatrice file format.
* Uses the lastLoadInfo in the LoadedDeck to determine the current name of the file and where to save to.
* @param deck The deck to convert. Should have valid lastLoadInfo. Will update the lastLoadInfo.
* @return Whether the conversion succeeded.
@@ -159,13 +159,12 @@ private:
static void saveToStream_DeckZone(QTextStream &out,
const InnerDecklistNode *zoneNode,
bool addComments = true,
- bool addSetNameAndNumber = true,
- const QString &boardZoneName = QString());
+ bool addSetNameAndNumber = true);
static void saveToStream_DeckZoneCards(QTextStream &out,
+ const InnerDecklistNode *zoneNode,
QList cards,
bool addComments = true,
- bool addSetNameAndNumber = true,
- const QString &boardZoneName = QString());
+ bool addSetNameAndNumber = true);
};
#endif
diff --git a/cockatrice/src/interface/intents/contexts/context_open_deck.h b/cockatrice/src/interface/intents/contexts/context_open_deck.h
deleted file mode 100644
index 03dca088e..000000000
--- a/cockatrice/src/interface/intents/contexts/context_open_deck.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#ifndef COCKATRICE_CONTEXT_OPEN_DECK_H
-#define COCKATRICE_CONTEXT_OPEN_DECK_H
-
-#include "context_connect_to_server.h"
-
-#include
-
-struct ContextOpenDeck
-{
- ContextConnectToServer serverContext;
- QString shareToken;
-};
-
-#endif // COCKATRICE_CONTEXT_OPEN_DECK_H
diff --git a/cockatrice/src/interface/intents/intent.cpp b/cockatrice/src/interface/intents/intent.cpp
index db0d13b2c..c02a89f35 100644
--- a/cockatrice/src/interface/intents/intent.cpp
+++ b/cockatrice/src/interface/intents/intent.cpp
@@ -2,11 +2,10 @@
Intent::Intent(QObject *parent) : QObject(parent)
{
- // An intent is done as soon as it reports success, failure, or cancellation.
- // Deleting it also tears down its dependency chain and disconnects any signal wiring.
+ // An intent is done as soon as it reports success or failure. Deleting it
+ // also tears down its dependency chain and disconnects any signal wiring.
connect(this, &Intent::finished, this, &QObject::deleteLater);
connect(this, &Intent::failed, this, &QObject::deleteLater);
- connect(this, &Intent::cancelled, this, &QObject::deleteLater);
}
Intent::~Intent() = default;
@@ -28,7 +27,6 @@ void Intent::runDependency(Intent *dependency)
this->execute();
});
connect(dependency, &Intent::failed, this, &Intent::failed);
- connect(dependency, &Intent::cancelled, this, &Intent::cancelled);
dependency->execute();
}
@@ -48,11 +46,3 @@ void Intent::emitFailed(const QString &reason)
emit failed(reason);
}
}
-
-void Intent::emitCancelled()
-{
- if (!completed) {
- completed = true;
- emit cancelled();
- }
-}
diff --git a/cockatrice/src/interface/intents/intent.h b/cockatrice/src/interface/intents/intent.h
index 5d9fdd3d6..125900ecd 100644
--- a/cockatrice/src/interface/intents/intent.h
+++ b/cockatrice/src/interface/intents/intent.h
@@ -16,7 +16,6 @@ public:
signals:
void finished();
void failed(QString reason);
- void cancelled();
protected:
// --- Subclasses must implement these ---
@@ -30,7 +29,6 @@ protected:
// Emit the outcome exactly once; ignore late signals after the intent is done.
void emitFinished();
void emitFailed(const QString &reason);
- void emitCancelled();
private:
bool completed = false;
diff --git a/cockatrice/src/interface/intents/intent_join_server_game.cpp b/cockatrice/src/interface/intents/intent_join_server_game.cpp
index b22b200ed..205c4dc70 100644
--- a/cockatrice/src/interface/intents/intent_join_server_game.cpp
+++ b/cockatrice/src/interface/intents/intent_join_server_game.cpp
@@ -19,15 +19,13 @@ bool IntentJoinServerGame::checkPrecondition() const
if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) {
return false;
}
- // serverName()/serverPort() reflect the server the client was configured
- // to connect to, which may differ from the actual TCP peer (e.g. when
- // connecting through a proxy), so compare those configured values. A link
- // naming the same host on another port is a different server and must not
- // reuse the session there.
- if (remoteClient->serverName().compare(context->roomContext.serverContext.hostname, Qt::CaseInsensitive) != 0) {
+ // peerPort() reflects the actual TCP peer, which may differ from the
+ // configured server port (e.g. when connecting through a proxy), so only
+ // the hostname is compared here.
+ if (remoteClient->peerName() != context->roomContext.serverContext.hostname) {
return false;
}
- if (QString::number(remoteClient->serverPort()) != context->roomContext.serverContext.port) {
+ if (QString::number(remoteClient->peerPort()) != context->roomContext.serverContext.port) {
return false;
}
diff --git a/cockatrice/src/interface/intents/intent_login.cpp b/cockatrice/src/interface/intents/intent_login.cpp
index 7beb63e1d..ff871fd03 100644
--- a/cockatrice/src/interface/intents/intent_login.cpp
+++ b/cockatrice/src/interface/intents/intent_login.cpp
@@ -1,14 +1,9 @@
#include "intent_login.h"
#include "../../client/settings/cache_settings.h"
-#include "../widgets/dialogs/dlg_login_prompt.h"
#include "libcockatrice/settings/servers_settings.h"
-#include
-
-IntentGetLoginCredentials::IntentGetLoginCredentials(ContextConnectToServer *_context,
- bool _promptForMissingCredentials)
- : Intent(), context(_context), promptForMissingCredentials(_promptForMissingCredentials)
+IntentGetLoginCredentials::IntentGetLoginCredentials(ContextConnectToServer *_context) : Intent(), context(_context)
{
}
@@ -34,46 +29,5 @@ void IntentGetLoginCredentials::onPreconditionSatisfied()
void IntentGetLoginCredentials::onPreconditionNotSatisfied()
{
- // MainWindow::applyStartupDestination runs this intent on every launch for
- // users whose startup tab is Server / Server Room; keep that path quiet, as
- // it was before the link-driven sign-in dialog existed.
- if (!promptForMissingCredentials) {
- emitFailed(tr("No saved credentials for this server"));
- return;
- }
-
- // No credentials saved for the target server: ask the user for them. They
- // opt into saving them so later links to the same server connect directly.
- const QString serverText = context->hostname + ":" + context->port;
- DlgLoginPrompt dialog(serverText);
- // ApplicationModal: the dialog has no parent (the intent is not a widget),
- // so WindowModal would not actually block any other window.
- dialog.setWindowModality(Qt::ApplicationModal);
-
- if (dialog.exec() != QDialog::Accepted) {
- emitCancelled();
- return;
- }
-
- context->username = dialog.username();
- context->password = dialog.password();
-
- if (dialog.savePassword() && !context->username.isEmpty()) {
- ServersSettings &servers = SettingsCache::instance().servers();
- // The host may already be saved under a friendly name (e.g. a public-server
- // list entry) with no credentials; reuse that name instead of overwriting
- // it with the raw hostname when addNewServer updates the entry in place.
- QString saveName = context->hostname;
- const int existingIndex = servers.findServerIndex(context->hostname, context->port);
- if (existingIndex >= 0) {
- saveName =
- servers.getValue(QString("saveName%1").arg(existingIndex), "server", "server_details").toString();
- if (saveName.isEmpty()) {
- saveName = context->hostname;
- }
- }
- servers.addNewServer(saveName, context->hostname, context->port, context->username, context->password, true);
- }
-
- emitFinished();
+ emitFailed(tr("No saved credentials for this server"));
}
diff --git a/cockatrice/src/interface/intents/intent_login.h b/cockatrice/src/interface/intents/intent_login.h
index 8ffd91a0a..c7fec92b7 100644
--- a/cockatrice/src/interface/intents/intent_login.h
+++ b/cockatrice/src/interface/intents/intent_login.h
@@ -9,10 +9,7 @@ class IntentGetLoginCredentials : public Intent
Q_OBJECT
public:
- // When promptForMissingCredentials is false (the default) a server without
- // saved credentials fails silently; only intent chains from cockatrice://
- // links opt into the interactive sign-in dialog.
- explicit IntentGetLoginCredentials(ContextConnectToServer *_context, bool _promptForMissingCredentials = false);
+ IntentGetLoginCredentials(ContextConnectToServer *_context);
protected:
bool checkPrecondition() const override;
@@ -21,7 +18,6 @@ protected:
private:
ContextConnectToServer *context;
- bool promptForMissingCredentials;
};
#endif // COCKATRICE_INTENT_LOGIN_H
diff --git a/cockatrice/src/interface/intents/intent_open_shared_deck.cpp b/cockatrice/src/interface/intents/intent_open_shared_deck.cpp
deleted file mode 100644
index 016de63e6..000000000
--- a/cockatrice/src/interface/intents/intent_open_shared_deck.cpp
+++ /dev/null
@@ -1,208 +0,0 @@
-#include "intent_open_shared_deck.h"
-
-#include "../deck_loader/deck_loader.h"
-#include "../widgets/dialogs/dlg_shared_decks_preview.h"
-#include "../widgets/tabs/tab_supervisor.h"
-#include "intent_connect_to_server.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-IntentOpenSharedDeck::IntentOpenSharedDeck(TabSupervisor *_tabSupervisor,
- RemoteClient *_remoteClient,
- const CardDatabaseQuerier *_querier,
- std::unique_ptr _context)
- : Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), querier(_querier),
- context(_context.release())
-{
- downloadTimer = new QTimer(this);
- downloadTimer->setSingleShot(true);
- downloadTimer->setInterval(15000);
- connect(downloadTimer, &QTimer::timeout, this, &IntentOpenSharedDeck::onDownloadTimeout);
-}
-
-bool IntentOpenSharedDeck::checkPrecondition() const
-{
- if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) {
- return false;
- }
- // serverName()/serverPort() reflect the server the client was configured
- // to connect to, which may differ from the actual TCP peer (e.g. when
- // connecting through a proxy), so compare those configured values. The
- // share token must be resolved against the host the link named — a link to
- // the same host on another port is a different server.
- if (remoteClient->serverName().compare(context->serverContext.hostname, Qt::CaseInsensitive) != 0) {
- return false;
- }
- return QString::number(remoteClient->serverPort()) == context->serverContext.port;
-}
-
-void IntentOpenSharedDeck::onPreconditionSatisfied()
-{
- // Resolve the share token to its items first; a share can contain more than
- // one deck, and each item is downloaded by id. Time the round trip like the
- // downloads, so a silent server cannot hang the chain forever.
- listPhase = true;
- downloadTimer->start();
-
- Command_DeckShareList cmd;
- cmd.set_token(context->shareToken.toStdString());
-
- PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd);
- connect(pend, &PendingCommand::finished, this, &IntentOpenSharedDeck::listShareFinished);
- remoteClient->sendCommand(pend);
-}
-
-void IntentOpenSharedDeck::onPreconditionNotSatisfied()
-{
- runDependency(new IntentConnectToServer(remoteClient, &context->serverContext));
-}
-
-void IntentOpenSharedDeck::listShareFinished(const Response &response, const CommandContainer & /* commandContainer */)
-{
- downloadTimer->stop();
- listPhase = false;
-
- if (response.response_code() != Response::RespOk) {
- emitFailed(tr("The shared deck could not be found or has expired"));
- return;
- }
-
- const Response_DeckShareList &resp = response.GetExtension(Response_DeckShareList::ext);
- if (resp.items_size() == 0) {
- emitFailed(tr("The shared deck is empty"));
- return;
- }
-
- QList items;
- items.reserve(resp.items_size());
- for (const ServerInfo_DeckShareItem &item : resp.items()) {
- items.append(item);
- itemNames.insert(item.id(), QString::fromStdString(item.name()));
- }
-
- const QString serverText = context->serverContext.hostname + ":" + context->serverContext.port;
-
- // Ask the user which decks to open before downloading anything.
- previewDialog = new DlgSharedDecksPreview(tabSupervisor, querier, QString::fromStdString(resp.name()),
- resp.expires_at(), serverText, items);
- connect(previewDialog, &DlgSharedDecksPreview::openRequested, this, &IntentOpenSharedDeck::startDownloads);
- connect(previewDialog, &DlgSharedDecksPreview::cancelled, this, &IntentOpenSharedDeck::emitCancelled);
- connect(previewDialog, &DlgSharedDecksPreview::cancelled, previewDialog, &QWidget::deleteLater);
- previewDialog->show();
- previewDialog->raise();
- previewDialog->activateWindow();
-}
-
-void IntentOpenSharedDeck::startDownloads(const QList &itemIds)
-{
- pendingItemIds = itemIds;
- totalItems = itemIds.size();
- completedItems = 0;
- loadedDecks.clear();
- downloadNextItem();
-}
-
-void IntentOpenSharedDeck::downloadNextItem()
-{
- if (pendingItemIds.isEmpty()) {
- finishAll();
- return;
- }
-
- currentItemId = pendingItemIds.takeFirst();
- downloadTimer->start();
-
- Command_DeckShareDownload cmd;
- cmd.set_token(context->shareToken.toStdString());
- cmd.set_item_id(currentItemId);
-
- PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd);
- connect(pend, &PendingCommand::finished, this, &IntentOpenSharedDeck::downloadShareFinished);
- remoteClient->sendCommand(pend);
-}
-
-void IntentOpenSharedDeck::downloadShareFinished(const Response &response,
- const CommandContainer & /* commandContainer */)
-{
- downloadTimer->stop();
-
- QString failureReason;
- if (response.response_code() != Response::RespOk) {
- failureReason = tr("Failed to download the shared deck");
- } else {
- const Response_DeckShareDownload &resp = response.GetExtension(Response_DeckShareDownload::ext);
- const QString deckString = QString::fromStdString(resp.deck());
- if (deckString.isEmpty()) {
- failureReason = tr("The shared deck is empty");
- } else {
- std::optional deckOpt =
- DeckLoader::loadFromRemote(deckString, LoadedDeck::LoadInfo::NON_REMOTE_ID);
- if (!deckOpt) {
- failureReason = tr("The shared deck could not be loaded");
- } else {
- loadedDecks.append(deckOpt.value());
- ++completedItems;
- previewDialog->setDownloadProgress(completedItems, totalItems,
- itemNames.value(currentItemId, tr("Unknown deck")));
- downloadNextItem();
- return;
- }
- }
- }
-
- onItemFailure(failureReason);
-}
-
-void IntentOpenSharedDeck::onItemFailure(const QString &reason)
-{
- downloadTimer->stop();
-
- if (loadedDecks.isEmpty()) {
- previewDialog->deleteLater();
- emitFailed(reason);
- return;
- }
-
- const int downloadedCount = loadedDecks.size();
- const QMessageBox::StandardButton answer = QMessageBox::question(
- previewDialog, tr("Open shared decks"),
- tr("Could not download the deck \"%1\".\n\n%n deck(s) were already downloaded. Open them?", "", downloadedCount)
- .arg(itemNames.value(currentItemId, tr("Unknown deck"))),
- QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
-
- if (answer == QMessageBox::Yes) {
- finishAll();
- } else {
- previewDialog->deleteLater();
- emitCancelled();
- }
-}
-
-void IntentOpenSharedDeck::onDownloadTimeout()
-{
- // The list phase has no preview dialog yet to report progress into; fail the
- // whole intent instead of letting the shared deck hang in limbo.
- if (listPhase) {
- emitFailed(tr("Timed out while loading the shared deck"));
- return;
- }
- onItemFailure(tr("Timed out while downloading the shared deck"));
-}
-
-void IntentOpenSharedDeck::finishAll()
-{
- previewDialog->deleteLater();
- for (const LoadedDeck &deck : loadedDecks) {
- tabSupervisor->openDeckInNewTab(deck);
- }
- emitFinished();
-}
diff --git a/cockatrice/src/interface/intents/intent_open_shared_deck.h b/cockatrice/src/interface/intents/intent_open_shared_deck.h
deleted file mode 100644
index 87812bdea..000000000
--- a/cockatrice/src/interface/intents/intent_open_shared_deck.h
+++ /dev/null
@@ -1,60 +0,0 @@
-#ifndef COCKATRICE_INTENT_OPEN_SHARED_DECK_H
-#define COCKATRICE_INTENT_OPEN_SHARED_DECK_H
-
-#include "contexts/context_open_deck.h"
-#include "intent.h"
-#include "remote_client.h"
-
-#include
-#include
-#include
-#include
-
-class TabSupervisor;
-struct LoadedDeck;
-class CardDatabaseQuerier;
-class DlgSharedDecksPreview;
-class QTimer;
-
-class IntentOpenSharedDeck : public Intent
-{
- Q_OBJECT
-
-public:
- IntentOpenSharedDeck(TabSupervisor *_tabSupervisor,
- RemoteClient *_remoteClient,
- const CardDatabaseQuerier *_querier,
- std::unique_ptr _context);
-
-protected:
- bool checkPrecondition() const override;
- void onPreconditionSatisfied() override;
- void onPreconditionNotSatisfied() override;
-
-private slots:
- void listShareFinished(const Response &response, const CommandContainer &commandContainer);
- void downloadShareFinished(const Response &response, const CommandContainer &commandContainer);
- void onDownloadTimeout();
-
-private:
- void startDownloads(const QList &itemIds);
- void downloadNextItem();
- void onItemFailure(const QString &reason);
- void finishAll();
-
- TabSupervisor *tabSupervisor;
- RemoteClient *remoteClient;
- const CardDatabaseQuerier *querier;
- QScopedPointer context;
- DlgSharedDecksPreview *previewDialog = nullptr;
- QTimer *downloadTimer;
- QMap itemNames;
- QList pendingItemIds;
- QList loadedDecks;
- bool listPhase = true;
- int currentItemId = 0;
- int totalItems = 0;
- int completedItems = 0;
-};
-
-#endif // COCKATRICE_INTENT_OPEN_SHARED_DECK_H
diff --git a/cockatrice/src/interface/intents/url_parser.cpp b/cockatrice/src/interface/intents/url_parser.cpp
index 707863354..509390611 100644
--- a/cockatrice/src/interface/intents/url_parser.cpp
+++ b/cockatrice/src/interface/intents/url_parser.cpp
@@ -1,28 +1,19 @@
#include "url_parser.h"
-#include "../../client/settings/cache_settings.h"
#include "../widgets/tabs/tab_room.h"
#include "../widgets/tabs/tab_supervisor.h"
#include "../window_main.h"
#include "contexts/context_join_game.h"
-#include "contexts/context_open_deck.h"
-#include "intent.h"
#include "intent_join_server_game.h"
#include "intent_login.h"
-#include "intent_open_shared_deck.h"
#include
-#include
#include
#include
#include
-#include
#include
-#include
#include
-inline Q_LOGGING_CATEGORY(UrlParserLog, "url_parser");
-
IntentUrlParser::IntentUrlParser(QObject *parent, MainWindow *_mainWindow) : QObject(parent), mainWindow(_mainWindow)
{
}
@@ -38,33 +29,16 @@ void IntentUrlParser::handle(const QString &urlStr)
const QString action = url.host();
QUrlQuery query(url);
- qCDebug(UrlParserLog) << "Parsing intent URL, action:" << action;
-
- PendingIntentChain chain;
- Intent *firstIntent = nullptr;
if (action == "joingame") {
- firstIntent = createJoinGameIntent(query, chain);
+ handleJoinGame(query);
} else if (action == "opendeck") {
- firstIntent = createOpenDeckIntent(query, chain);
+ // handleOpenDeck(query);
} else {
qWarning() << "Unknown intent:" << action;
}
-
- if (firstIntent == nullptr) {
- // The link was invalid or the user declined the confirm: nothing runs.
- // Report the idle state when no other chain is queued so that a startup
- // launch (which skipped its own connection for this URL) falls back to it.
- if (!chainRunning && pendingChains.isEmpty()) {
- emit urlChainFinished(mainWindow->getRemoteClient()->getStatus() == StatusLoggedIn);
- }
- return;
- }
-
- pendingChains.append(chain);
- startNextChain();
}
-Intent *IntentUrlParser::createJoinGameIntent(const QUrlQuery &query, PendingIntentChain &chain)
+void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
{
auto showError = [this](const QString &message) { QMessageBox::warning(mainWindow, tr("Open game"), message); };
@@ -75,21 +49,21 @@ Intent *IntentUrlParser::createJoinGameIntent(const QUrlQuery &query, PendingInt
if (ctx->roomContext.serverContext.hostname.isEmpty()) {
showError(tr("Missing or empty hostname in the game link"));
- return nullptr;
+ return;
}
bool ok = false;
ctx->roomContext.serverContext.port.toUShort(&ok);
if (!ok) {
showError(tr("Invalid or missing port in the game link"));
- return nullptr;
+ return;
}
ctx->roomContext.roomId = query.queryItemValue("roomid").toInt(&ok);
if (!ok) {
showError(tr("Invalid or missing room id in the game link"));
- return nullptr;
+ return;
}
ok = false;
@@ -97,7 +71,7 @@ Intent *IntentUrlParser::createJoinGameIntent(const QUrlQuery &query, PendingInt
if (!ok) {
showError(tr("Invalid or missing game id in the game link"));
- return nullptr;
+ return;
}
const QString gameDescription = query.queryItemValue("game", QUrl::FullyDecoded);
@@ -106,33 +80,24 @@ Intent *IntentUrlParser::createJoinGameIntent(const QUrlQuery &query, PendingInt
const QMessageBox::StandardButton answer = QMessageBox::question(
mainWindow, tr("Join game"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (answer != QMessageBox::Yes) {
- return nullptr;
+ return;
}
- RemoteClient *client = mainWindow->getRemoteClient();
- ContextConnectToServer *serverContext = &ctx->roomContext.serverContext;
-
// The join game intent owns the context and the credential lookup; once the
// chain finishes (or fails) it deletes the whole tree.
- auto joinGameIntent = new IntentJoinServerGame(mainWindow->getTabSupervisor(), client, std::move(ctx));
+ ContextConnectToServer *serverContext = &ctx->roomContext.serverContext;
+ auto joinGameIntent =
+ new IntentJoinServerGame(mainWindow->getTabSupervisor(), mainWindow->getRemoteClient(), std::move(ctx));
joinGameIntent->setParent(this);
- chain.intents.append(joinGameIntent);
+
+ auto getLoginCredentialsIntent = new IntentGetLoginCredentials(serverContext);
+ getLoginCredentialsIntent->setParent(joinGameIntent);
+
+ connect(getLoginCredentialsIntent, &Intent::finished, joinGameIntent, &Intent::execute);
+ connect(getLoginCredentialsIntent, &Intent::failed, joinGameIntent, &Intent::failed);
connect(joinGameIntent, &Intent::failed, this, [showError](const QString &reason) { showError(reason); });
- Intent *firstIntent = joinGameIntent;
- if (!isConnectedTo(serverContext->hostname, serverContext->port)) {
- auto getLoginCredentialsIntent =
- new IntentGetLoginCredentials(serverContext, /*promptForMissingCredentials=*/true);
- getLoginCredentialsIntent->setParent(joinGameIntent);
- chain.intents.insert(0, getLoginCredentialsIntent);
-
- connect(getLoginCredentialsIntent, &Intent::finished, joinGameIntent, &Intent::execute);
- connect(getLoginCredentialsIntent, &Intent::failed, joinGameIntent, &Intent::failed);
- connect(getLoginCredentialsIntent, &Intent::cancelled, joinGameIntent, &Intent::cancelled);
- firstIntent = getLoginCredentialsIntent;
- }
-
- return firstIntent;
+ getLoginCredentialsIntent->execute();
}
QString IntentUrlParser::generateJoinGameMessage(const ContextJoinGame &context, const QString &gameDescription)
@@ -169,270 +134,3 @@ QString IntentUrlParser::generateJoinGameMessage(const ContextJoinGame &context,
.arg(gameDescription, gameIdStr, roomTab->getRoomName(), server)
: tr("Join game \"%1\" (#%2) on %3?").arg(gameDescription, gameIdStr, server);
}
-
-Intent *IntentUrlParser::createOpenDeckIntent(const QUrlQuery &query, PendingIntentChain &chain)
-{
- auto showError = [this](const QString &message) {
- QMessageBox::warning(mainWindow, tr("Open shared deck"), message);
- };
-
- auto ctx = std::make_unique();
-
- ctx->serverContext.hostname = query.queryItemValue("hostname");
- ctx->serverContext.port = query.queryItemValue("port");
- ctx->shareToken = query.queryItemValue("share");
-
- qCDebug(UrlParserLog) << "Open-deck intent: host" << ctx->serverContext.hostname << "port"
- << ctx->serverContext.port << "token length" << ctx->shareToken.length();
-
- if (ctx->serverContext.hostname.isEmpty()) {
- showError(tr("Missing or empty hostname in the share link"));
- return nullptr;
- }
-
- bool ok = false;
- const quint16 port = ctx->serverContext.port.toUShort(&ok);
- if (!ok || port == 0) {
- showError(tr("Invalid or missing port in the share link"));
- return nullptr;
- }
-
- if (ctx->shareToken.isEmpty()) {
- showError(tr("Missing or empty share value in the share link"));
- return nullptr;
- }
-
- RemoteClient *client = mainWindow->getRemoteClient();
-
- // The open deck download needs a connection to the link's server. Ask before
- // taking the session anywhere it isn't already, naming the host we would
- // connect to. Remember the link's target when it moves us away from a live
- // session so a failed or cancelled chain can restore the session it left.
- // The hostname is link-supplied and percent-decoded, so escape it: QMessageBox
- // renders AutoText, and markup in a hostname would otherwise flip the whole
- // prompt to rich text and let a link pad the message the user is shown.
- const bool alreadyConnected = isConnectedTo(ctx->serverContext.hostname, ctx->serverContext.port);
- if (!alreadyConnected) {
- const QString target =
- QStringLiteral("%1:%2").arg(ctx->serverContext.hostname.toHtmlEscaped(), ctx->serverContext.port);
-
- if (client->getStatus() == StatusLoggedIn) {
- const QString current =
- QStringLiteral("%1:%2").arg(client->serverName(), QString::number(client->serverPort()));
- const QMessageBox::StandardButton answer = QMessageBox::question(
- mainWindow, tr("Open shared deck"),
- tr("Opening this share link connects you to %1 instead of %2.\n\nContinue?").arg(target, current),
- QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
- if (answer != QMessageBox::Yes) {
- return nullptr;
- }
- chain.migrationTargetHost = ctx->serverContext.hostname;
- chain.migrationTargetPort = ctx->serverContext.port;
- chain.pendingRestore = true;
- } else {
- // Fresh connection is harmless to wander away from, but a server the
- // client has never been configured for deserves a harder warning (no
- // by default) so a stray link cannot silently steer the client there.
- const bool knownHost = SettingsCache::instance().servers().findHostIndex(ctx->serverContext.hostname) >= 0;
- const QMessageBox::StandardButton answer =
- knownHost
- ? QMessageBox::question(mainWindow, tr("Open shared deck"),
- tr("Opening this share link connects you to %1.\n\nContinue?").arg(target))
- : QMessageBox::warning(mainWindow, tr("Open shared deck"),
- tr("Opening this share link connects you to %1, a server you have "
- "never connected to before.\n\nContinue?")
- .arg(target),
- QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- if (answer != QMessageBox::Yes) {
- return nullptr;
- }
- }
- }
-
- ContextConnectToServer *serverContext = &ctx->serverContext;
-
- // The open deck intent owns the context and the credential lookup; once
- // the chain finishes (or fails) it deletes the whole tree.
- auto openDeckIntent =
- new IntentOpenSharedDeck(mainWindow->getTabSupervisor(), client, CardDatabaseManager::query(), std::move(ctx));
- openDeckIntent->setParent(this);
- chain.intents.append(openDeckIntent);
- connect(openDeckIntent, &Intent::failed, this, [showError](const QString &reason) { showError(reason); });
-
- Intent *firstIntent = openDeckIntent;
- if (!isConnectedTo(serverContext->hostname, serverContext->port)) {
- auto getLoginCredentialsIntent =
- new IntentGetLoginCredentials(serverContext, /*promptForMissingCredentials=*/true);
- getLoginCredentialsIntent->setParent(openDeckIntent);
- chain.intents.insert(0, getLoginCredentialsIntent);
-
- connect(getLoginCredentialsIntent, &Intent::finished, openDeckIntent, &Intent::execute);
- connect(getLoginCredentialsIntent, &Intent::failed, openDeckIntent, &Intent::failed);
- connect(getLoginCredentialsIntent, &Intent::cancelled, openDeckIntent, &Intent::cancelled);
- firstIntent = getLoginCredentialsIntent;
- }
-
- return firstIntent;
-}
-
-bool IntentUrlParser::isConnectedTo(const QString &hostname, const QString &port) const
-{
- // serverName() reflects the server the client was configured to connect to,
- // which may differ from the actual TCP peer (e.g. when connecting through a
- // proxy), so compare the configured host and port — exactly what a link
- // names. A link to the same host on another port is a different server and
- // must not silently reuse an existing session there.
- RemoteClient *client = mainWindow->getRemoteClient();
- return client->getStatus() == StatusLoggedIn && client->serverName().compare(hostname, Qt::CaseInsensitive) == 0 &&
- QString::number(client->serverPort()) == port;
-}
-
-void IntentUrlParser::startNextChain()
-{
- if (chainRunning || pendingChains.isEmpty()) {
- return;
- }
- chainRunning = true;
-
- PendingIntentChain &chain = pendingChains.first();
- if (chain.intents.isEmpty()) {
- pendingChains.removeFirst();
- chainRunning = false;
- startNextChain();
- return;
- }
-
- // Snapshot the session this chain moves away from now that it actually
- // runs. Chains are parsed while earlier ones are still queued, so a capture
- // at parse time would follow whichever server the chain before it settled
- // on, not the one the user is really on when this link is handled.
- if (chain.pendingRestore) {
- RemoteClient *client = mainWindow->getRemoteClient();
- chain.previousServerHost = client->serverName();
- chain.previousServerPort = QString::number(client->serverPort());
- }
-
- // Only the last intent completes the chain; its terminal signal ends the
- // whole run. Cancellation of an intermediate intent (e.g. declined login
- // prompt) is forwarded onto the last intent in the chain builders above.
- Intent *finalIntent = chain.intents.last();
- connect(finalIntent, &Intent::finished, this, [this]() { chainEnded(true); });
- connect(finalIntent, &Intent::failed, this, [this]() { chainEnded(false); });
- connect(finalIntent, &Intent::cancelled, this, [this]() { chainEnded(false); });
- // Backstop: if the final intent is destroyed without emitting a terminal
- // signal (e.g. a network error dropped it while running), end the chain so
- // later links are not queued and dropped for the rest of the session.
- chainBackstopConnection = connect(finalIntent, &QObject::destroyed, this, &IntentUrlParser::onChainIntentDestroyed);
-
- chain.intents.first()->execute();
-}
-
-void IntentUrlParser::chainEnded(bool chainSucceeded)
-{
- chainRunning = false;
- QObject::disconnect(chainBackstopConnection);
-
- const PendingIntentChain chain = pendingChains.takeFirst();
-
- // Only a failed or cancelled chain restores the session the link migrated
- // away from; a successful one leaves the user where they are.
- if (chain.pendingRestore && !chainSucceeded) {
- restorePreviousServer(chain);
- }
-
- startNextChain();
-
- // Only report the terminal state once the queue has fully drained, so a
- // queued follow-up link keeps the startup fallback out of the picture.
- if (!chainRunning && pendingChains.isEmpty()) {
- emit urlChainFinished(mainWindow->getRemoteClient()->getStatus() == StatusLoggedIn);
- }
-}
-
-void IntentUrlParser::onChainIntentDestroyed()
-{
- if (!chainRunning) {
- return;
- }
- qCWarning(UrlParserLog) << "Share-link intent destroyed without a terminal signal; ending its chain";
- chainEnded(false);
-}
-
-void IntentUrlParser::restorePreviousServer(const PendingIntentChain &chain)
-{
- if (chain.previousServerHost.isEmpty()) {
- return;
- }
-
- RemoteClient *client = mainWindow->getRemoteClient();
- const ClientStatus status = client->getStatus();
-
- // A failed/cancelled chain can fire while the client is still settling the
- // in-flight connection attempt (wrong password, connect timeout). Only
- // decide once the client has settled into logged-in or disconnected;
- // deciding mid-connect would strand the user offline from their previous
- // server.
- if (status == StatusDisconnected || status == StatusLoggedIn) {
- restoreToPreviousServer(chain);
- return;
- }
- auto waitConnection = std::make_shared();
- *waitConnection = connect(client, &RemoteClient::statusChanged, this, [this, chain, client, waitConnection]() {
- const ClientStatus settled = client->getStatus();
- if (settled == StatusDisconnected || settled == StatusLoggedIn) {
- QObject::disconnect(*waitConnection);
- restoreToPreviousServer(chain);
- }
- });
-}
-
-void IntentUrlParser::restoreToPreviousServer(const PendingIntentChain &chain)
-{
- RemoteClient *client = mainWindow->getRemoteClient();
-
- // Back on the previous server already → nothing to undo.
- if (client->serverName().compare(chain.previousServerHost, Qt::CaseInsensitive) == 0 &&
- QString::number(client->serverPort()) == chain.previousServerPort) {
- return;
- }
-
- // When logged in somewhere, only intervene if that somewhere is the server
- // the link moved us to; if the user went elsewhere on their own, leave them.
- if (client->getStatus() == StatusLoggedIn) {
- const bool onMigrationTarget =
- client->serverName().compare(chain.migrationTargetHost, Qt::CaseInsensitive) == 0 &&
- QString::number(client->serverPort()) == chain.migrationTargetPort;
- if (!onMigrationTarget) {
- return;
- }
-
- ServersSettings &servers = SettingsCache::instance().servers();
- const int index = servers.findServerIndex(chain.previousServerHost, chain.previousServerPort);
- if (index >= 0 && servers.hasLoginData(chain.previousServerHost, chain.previousServerPort)) {
- const QString username =
- servers.getValue(QString("username%1").arg(index), "server", "server_details").toString();
- const QString password =
- servers.getValue(QString("password%1").arg(index), "server", "server_details").toString();
- client->connectToServer(chain.previousServerHost, chain.previousServerPort.toUInt(), username, password);
- return;
- }
- client->disconnectFromServer();
- return;
- }
-
- if (client->getStatus() != StatusDisconnected) {
- return;
- }
-
- // The link's connection attempt failed: reconnect to the previous server
- // when credentials are saved, otherwise stay offline.
- ServersSettings &servers = SettingsCache::instance().servers();
- const int index = servers.findServerIndex(chain.previousServerHost, chain.previousServerPort);
- if (index >= 0 && servers.hasLoginData(chain.previousServerHost, chain.previousServerPort)) {
- const QString username =
- servers.getValue(QString("username%1").arg(index), "server", "server_details").toString();
- const QString password =
- servers.getValue(QString("password%1").arg(index), "server", "server_details").toString();
- client->connectToServer(chain.previousServerHost, chain.previousServerPort.toUInt(), username, password);
- }
-}
diff --git a/cockatrice/src/interface/intents/url_parser.h b/cockatrice/src/interface/intents/url_parser.h
index ea29fed53..6d705e013 100644
--- a/cockatrice/src/interface/intents/url_parser.h
+++ b/cockatrice/src/interface/intents/url_parser.h
@@ -1,46 +1,10 @@
#ifndef COCKATRICE_URL_PARSER_H
#define COCKATRICE_URL_PARSER_H
-
-#include
#include
#include
-class Intent;
class MainWindow;
struct ContextJoinGame;
-
-/**
- * @brief One queued intent chain with the session-migration bookkeeping for it.
- *
- * The restore fields are per-chain on purpose: chains are parsed while earlier
- * ones are still queued, so parser-wide state would let one chain's failure
- * consume the restore data another chain recorded.
- */
-struct PendingIntentChain
-{
- QList intents;
-
- // Snapshot of the session in place when this chain started running, so a
- // queued chain follows whichever server the chain before it settled on.
- QString previousServerHost;
- QString previousServerPort;
-
- // Recorded at parse time when the user confirmed migrating away from a live
- // session to the host/port named by the link.
- QString migrationTargetHost;
- QString migrationTargetPort;
- bool pendingRestore = false;
-};
-
-/**
- * @brief Parses cockatrice:// links and runs them as serialized intent chains.
- *
- * Links are parsed by action (joingame/opendeck) and translated into an intent
- * chain. Chains are queued and run one at a time: a document can hand multiple
- * links to the window while an earlier chain still connects, and running two
- * connect chains concurrently tears the connection down. urlChainFinished is
- * emitted once the queue has fully drained.
- */
class IntentUrlParser : public QObject
{
Q_OBJECT
@@ -48,28 +12,12 @@ class IntentUrlParser : public QObject
public:
IntentUrlParser(QObject *parent, MainWindow *mainWindow);
void handle(const QString &urlStr);
-
-signals:
- /** @brief Emitted when the last queued chain ended; carries whether the client is logged in. */
- void urlChainFinished(bool connected);
+ void handleJoinGame(const QUrlQuery &query);
private:
- Intent *createJoinGameIntent(const QUrlQuery &query, PendingIntentChain &chain);
- Intent *createOpenDeckIntent(const QUrlQuery &query, PendingIntentChain &chain);
QString generateJoinGameMessage(const ContextJoinGame &context, const QString &gameDescription);
- [[nodiscard]] bool isConnectedTo(const QString &hostname, const QString &port) const;
- void startNextChain();
- void chainEnded(bool chainSucceeded);
- void onChainIntentDestroyed();
- void restorePreviousServer(const PendingIntentChain &chain);
- void restoreToPreviousServer(const PendingIntentChain &chain);
MainWindow *mainWindow;
- QList pendingChains;
- bool chainRunning = false;
- // Disconnects the destroyed-signal backstop once a chain ends, so an old
- // intent's deferred deletion cannot end the chain that runs after it.
- QMetaObject::Connection chainBackstopConnection;
};
#endif // COCKATRICE_URL_PARSER_H
diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp
index d5a168708..9cde72c01 100644
--- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp
+++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp
@@ -1,5 +1,6 @@
#include "palette_editor_dialog.h"
+#include "../../client/settings/cache_settings.h"
#include "../theme_manager.h"
#include "palette_generator.h"
#include "palette_grid_widget.h"
@@ -10,11 +11,31 @@
#include
#include
#include
+#include
#include
+#include
#include
+#include
#include
#include
+#include
#include
+#include
+
+// Probe whether a directory is truly writable by trying to create and remove a
+// temporary file. QFileInfo::isWritable() on a directory is unreliable (notably
+// on Windows where UAC VirtualStore can make a system dir appear writable).
+static bool isDirReallyWritable(const QString &dirPath)
+{
+ const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test");
+ QFile f(probe);
+ if (!f.open(QIODevice::WriteOnly)) {
+ return false;
+ }
+ f.close();
+ f.remove();
+ return true;
+}
PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QString &_themeName, QWidget *parent)
: QDialog(parent), themeDirPath(_themeDirPath), themeName(_themeName)
@@ -25,7 +46,14 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt
// Resolve a writable directory for saving. Built-in (Default / Fusion) and
// other read-only theme directories must be customised in the user-writable
// themes directory; otherwise the write would fail or be lost on upgrade.
- saveDir = ThemeManager::writableThemeDir(themeName);
+ if (!themeDirPath.isEmpty() && isDirReallyWritable(themeDirPath)) {
+ saveDir = themeDirPath;
+ } else {
+ saveDir = QDir(SettingsCache::instance().paths().getThemesPath()).absoluteFilePath(themeName);
+ if (!QDir().mkpath(saveDir)) {
+ qWarning() << "Failed to create palette save directory:" << saveDir;
+ }
+ }
// Load both scheme configs upfront so switching is instant
loadSchemes();
@@ -186,7 +214,7 @@ void PaletteEditorDialog::retranslateUi()
resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette"));
saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower()));
- if (saveDir.isEmpty() || !ThemeManager::isDirReallyWritable(saveDir)) {
+ if (saveDir.isEmpty() || !isDirReallyWritable(saveDir)) {
saveBtn->setEnabled(false);
saveBtn->setToolTip(tr("Cannot save: this theme has no writable directory"));
}
@@ -269,7 +297,7 @@ void PaletteEditorDialog::onSave()
if (it.key() == loadedScheme) {
continue;
}
- if (it.value() == savedConfig.value(it.key())) {
+ if (it.value().colors == savedConfig.value(it.key()).colors) {
continue;
}
if (!ThemeManager::commitPalette(saveDir, it.key(), it.value())) {
@@ -280,7 +308,7 @@ void PaletteEditorDialog::onSave()
}
// Commit the active scheme last so the global colour scheme matches.
- if (workingConfig[loadedScheme] != savedConfig.value(loadedScheme)) {
+ if (workingConfig[loadedScheme].colors != savedConfig.value(loadedScheme).colors) {
if (!ThemeManager::commitPalette(saveDir, loadedScheme, workingConfig[loadedScheme])) {
QMessageBox::warning(this, tr("Save failed"),
tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), saveDir));
diff --git a/cockatrice/src/interface/palette_editor/palette_generator.cpp b/cockatrice/src/interface/palette_editor/palette_generator.cpp
index 822e57250..d30dd14f1 100644
--- a/cockatrice/src/interface/palette_editor/palette_generator.cpp
+++ b/cockatrice/src/interface/palette_editor/palette_generator.cpp
@@ -150,17 +150,6 @@ PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &sch
cfg.colors[CG::Disabled][CR::HighlightedText] = disText;
cfg.colors[CG::Inactive][CR::HighlightedText] = dark ? Qt::white : Qt::black;
- // Accent: same primary hue as Highlight, so palettes derived from a
- // QuickSetup accent always carry a matching Accent role.
-#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
- set3(CR::Accent, hl, disText, hl);
-#endif
-
- // Application role colors: Strong tracks the primary accent, while Soft is
- // the lightened, desaturated companion used for button-gradient highlights.
- cfg.appColors[AppColor::AccentStrong] = hl;
- cfg.appColors[AppColor::AccentSoft] = hsl(accent.lightness() + 60, qRound(accent.hslSaturation() * 70 / 100.0));
-
// BrightText
QColor bright;
if (achromatic) {
diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp
index 97d28b731..67294cd98 100644
--- a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp
+++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp
@@ -1,7 +1,5 @@
#include "palette_grid_widget.h"
-#include "../theme_manager.h"
-
#include
#include
#include
@@ -47,11 +45,6 @@ static const QMap ROLE_DESCRIPTIONS = {
{QPalette::Shadow, QT_TR_NOOP("Very dark shadow colour")},
};
-static const QMap APP_ROLE_DESCRIPTIONS = {
- {AppColor::AccentStrong, QT_TR_NOOP("Vivid primary accent (e.g. home-tab button gradient start)")},
- {AppColor::AccentSoft, QT_TR_NOOP("Lightened, desaturated accent (e.g. home-tab button gradient end)")},
-};
-
PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)
{
scroll = new QScrollArea(this);
@@ -129,46 +122,6 @@ void PaletteGridWidget::buildGrid(QWidget *host)
grid->addWidget(btn, row + 1, col + 1, Qt::AlignHCenter | Qt::AlignVCenter);
}
}
-
- // Application color section: one ColorButton per role below the role grid.
- // These are not tied to a color group, so a single button spans the row.
- QMetaEnum appEnum = QMetaEnum::fromType();
-
- const int appHeaderRow = roles.size() + 1;
-
- auto *appHeader = new QLabel(tr("App colors"), host);
- appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette"));
- QFont appHeaderFont = appHeader->font();
- appHeaderFont.setBold(true);
- appHeader->setFont(appHeaderFont);
- appHeader->setAutoFillBackground(true);
- appHeader->setContentsMargins(4, 4, 4, 4);
- grid->addWidget(appHeader, appHeaderRow, 0, 1, 4);
- headerLabels.append(appHeader);
-
- for (int i = 0; i < appEnum.keyCount(); ++i) {
- auto role = static_cast(appEnum.value(i));
- const int row = appHeaderRow + 1 + i;
-
- if (i % 2 == 0) {
- for (int col = 0; col < 4; ++col) {
- auto *shade = new QWidget(host);
- shade->setAutoFillBackground(true);
- grid->addWidget(shade, row, col);
- rowShadeWidgets.push_back(shade);
- }
- }
-
- auto *label = new QLabel(QString(appEnum.valueToKey(role)), host);
- label->setToolTip(APP_ROLE_DESCRIPTIONS.value(role, {}));
- label->setContentsMargins(4, 2, 8, 2);
- grid->addWidget(label, row, 0);
-
- auto *btn = new ColorButton(host);
- connect(btn, &ColorButton::colorChanged, this, [this] { emit paletteChanged(); });
- appColorButtons[role] = btn;
- grid->addWidget(btn, row, 1, Qt::AlignHCenter | Qt::AlignVCenter);
- }
}
void PaletteGridWidget::changeEvent(QEvent *e)
@@ -213,16 +166,6 @@ void PaletteGridWidget::loadPalette(const PaletteConfig &cfg)
colorButtons[group][role]->setColor(color);
}
}
-
- QMetaEnum appEnum = QMetaEnum::fromType();
- for (int i = 0; i < appEnum.keyCount(); ++i) {
- auto role = static_cast(appEnum.value(i));
- QColor color = cfg.appColors.value(role);
- if (!color.isValid()) {
- color = themeManager->appColor(role);
- }
- appColorButtons[role]->setColor(color);
- }
}
PaletteConfig PaletteGridWidget::currentPaletteConfig() const
@@ -233,12 +176,5 @@ PaletteConfig PaletteGridWidget::currentPaletteConfig() const
cfg.colors[group][role] = colorButtons[group][role]->getColor();
}
}
-
- QMetaEnum appEnum = QMetaEnum::fromType();
- for (int i = 0; i < appEnum.keyCount(); ++i) {
- auto role = static_cast(appEnum.value(i));
- cfg.appColors[role] = appColorButtons[role]->getColor();
- }
-
return cfg;
}
\ No newline at end of file
diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.h b/cockatrice/src/interface/palette_editor/palette_grid_widget.h
index 77cbf1c62..1a665971a 100644
--- a/cockatrice/src/interface/palette_editor/palette_grid_widget.h
+++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.h
@@ -31,7 +31,6 @@ private:
void refreshChromePalettes();
QMap> colorButtons;
- QMap appColorButtons;
QScrollArea *scroll;
QWidget *gridHost;
QVBoxLayout *layout;
diff --git a/cockatrice/src/interface/pixel_map_generator.cpp b/cockatrice/src/interface/pixel_map_generator.cpp
index b70dc576f..9b8c4bcdc 100644
--- a/cockatrice/src/interface/pixel_map_generator.cpp
+++ b/cockatrice/src/interface/pixel_map_generator.cpp
@@ -1,7 +1,5 @@
#include "pixel_map_generator.h"
-#include "theme_manager.h"
-
#include
#include
#include
@@ -84,13 +82,7 @@ static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnl
/**
* Try to load path image from non-SVG formats, otherwise fall back to SVG.
* This is to allow custom themes to support non-SVG format type overrides, since SVG requires custom loading.
- *
- * The path may already carry the resolved file extension (e.g. via
- * ThemeManager::assetPath); such paths are loaded directly. Otherwise a
- * format-agnostic lookup probes png, jpg and finally svg.
- *
- * @param path The path to the file, with no file extension unless the caller
- * already resolved it. File formats will be automatically detected.
+ * @param path The path to the file, with no file extension. File formats will be automatically detected.
* @param size The desired size of the pixmap.
* @param expandOnly If true, then keep the size of the initial pixmap to at least the size (Only relevant if SVG).
*
@@ -98,19 +90,6 @@ static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnl
*/
static QPixmap tryLoadImage(const QString &path, const QSize &size, bool expandOnly = false)
{
- if (path.endsWith(QLatin1String(".svg"), Qt::CaseInsensitive)) {
- return loadSvg(path, size, expandOnly);
- }
- if (path.endsWith(QLatin1String(".png"), Qt::CaseInsensitive) ||
- path.endsWith(QLatin1String(".jpg"), Qt::CaseInsensitive) ||
- path.endsWith(QLatin1String(".jpeg"), Qt::CaseInsensitive)) {
- QPixmap pix(path);
- if (!pix.isNull()) {
- return pix.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- }
- return {};
- }
-
const auto formats = {"png", "jpg"};
QPixmap returnPixmap;
@@ -132,8 +111,7 @@ QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name)
return pmCache.value(key);
}
- QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("phases/") + name),
- QSize(height, height));
+ QPixmap pixmap = tryLoadImage("theme:phases/" + name, QSize(height, height));
pmCache.insert(key, pixmap);
return pixmap;
@@ -361,10 +339,6 @@ static QString getIconType(const bool isBuddy, const UserLevelFlags &userLevelFl
return "pawn_judge";
}
- if (userLevelFlags.testFlag(ServerInfo_User::IsDeveloper)) {
- return "pawn_dev";
- }
-
if (!privLevel.isEmpty() && privLevel.toLower() != "none") {
return QString("pawn_%1").arg(privLevel.toLower());
}
@@ -422,8 +396,7 @@ QPixmap LockPixmapGenerator::generatePixmap(int height)
return pmCache.value(key);
}
- QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/lock")),
- QSize(height, height), true);
+ QPixmap pixmap = tryLoadImage("theme:icons/lock", QSize(height, height), true);
pmCache.insert(key, pixmap);
return pixmap;
}
@@ -438,8 +411,7 @@ QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
}
QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed";
- QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/") + name),
- QSize(height, height), true);
+ QPixmap pixmap = tryLoadImage("theme:icons/" + name, QSize(height, height), true);
pmCache.insert(key, pixmap);
return pixmap;
@@ -500,13 +472,6 @@ QHash ManaSymbolPixmapGenerator::scaledCache;
QPixmap loadColorAdjustedPixmap(const QString &name)
{
- // Prefer an authored scheme-qualified variant when one exists for this asset.
- const QString variant = themeManager->schemeVariantPath(QStringView(name).mid(QStringLiteral("theme:").size()));
- if (!variant.isEmpty()) {
- return QPixmap(QStringLiteral("theme:") + variant);
- }
-
- // Legacy fallback: runtime-invert for dark mode when no authored variant.
if (qApp->palette().windowText().color().lightness() > 200) {
QImage img(name);
img.invertPixels();
@@ -517,21 +482,3 @@ QPixmap loadColorAdjustedPixmap(const QString &name)
return QPixmap(name);
}
}
-
-QPixmap themePixmap(QStringView prefix)
-{
- const QString resolved = themeManager->assetPath(prefix);
- return QPixmap(QStringLiteral("theme:") + resolved);
-}
-
-void clearPixmapGeneratorCaches()
-{
- PhasePixmapGenerator::clear();
- CounterPixmapGenerator::clear();
- PingPixmapGenerator::clear();
- CountryPixmapGenerator::clear();
- UserLevelPixmapGenerator::clear();
- LockPixmapGenerator::clear();
- DropdownIconPixmapGenerator::clear();
- ManaSymbolPixmapGenerator::clear();
-}
diff --git a/cockatrice/src/interface/pixel_map_generator.h b/cockatrice/src/interface/pixel_map_generator.h
index b6e822fd9..17720166a 100644
--- a/cockatrice/src/interface/pixel_map_generator.h
+++ b/cockatrice/src/interface/pixel_map_generator.h
@@ -156,15 +156,4 @@ public:
QPixmap loadColorAdjustedPixmap(const QString &name);
-// Loads a "theme:" asset (with no file extension in prefix), preferring the
-// scheme-qualified variant (prefix-dark / prefix-light, resolved via
-// ThemeManager::assetPath) and falling back to the plain asset. Callers load
-// the returned path directly. Use for scheme-sensitive pixmaps like
-// backgrounds, the card back, and the app logo.
-QPixmap themePixmap(QStringView prefix);
-
-// Clears every PixmapGenerator's static cache so scheme variants are
-// re-resolved when the active theme or color scheme changes.
-void clearPixmapGeneratorCaches();
-
#endif
diff --git a/cockatrice/src/interface/theme_config.cpp b/cockatrice/src/interface/theme_config.cpp
index 8293a82cf..3c43c467d 100644
--- a/cockatrice/src/interface/theme_config.cpp
+++ b/cockatrice/src/interface/theme_config.cpp
@@ -16,7 +16,7 @@ QString ThemeConfig::toIni() const
out += "[Appearance]\n";
out += QString("ColorScheme = %1\n").arg(colorScheme.isEmpty() ? "System" : colorScheme);
out += "\n[Style]\n";
- out += QString("Name = %1\n").arg(styleName.isEmpty() ? "System" : styleName);
+ out += QString("Name = %1\n").arg(styleName.isEmpty() ? "Default" : styleName);
return out;
}
@@ -96,7 +96,7 @@ bool ThemeConfig::save(const QString &themeDirPath) const
bool PaletteConfig::hasPalette() const
{
- return !colors.isEmpty() || !appColors.isEmpty();
+ return !colors.isEmpty();
}
QString PaletteConfig::toToml() const
@@ -133,24 +133,6 @@ QString PaletteConfig::toToml() const
out += "\n";
}
- if (!appColors.isEmpty()) {
- QMetaEnum appEnum = QMetaEnum::fromType();
-
- out += "[AppColors]\n";
-
- for (auto it = appColors.cbegin(); it != appColors.cend(); ++it) {
- const char *roleName = appEnum.valueToKey(it.key());
-
- if (!roleName) {
- continue;
- }
-
- out += QString("%1 = %2\n").arg(QString(roleName), -20).arg(it.value().name(QColor::HexArgb));
- }
-
- out += "\n";
- }
-
return out;
}
@@ -170,7 +152,6 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath)
}
QMetaEnum roleEnum = QMetaEnum::fromType();
- QMetaEnum appEnum = QMetaEnum::fromType();
QString currentSection;
QPalette::ColorGroup currentGroup = QPalette::Active;
@@ -221,26 +202,6 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath)
}
}
- QColor color(value);
-
- if (!color.isValid()) {
- continue;
- }
-
- if (currentSection.compare("AppColors", Qt::CaseInsensitive) == 0) {
- if (key.startsWith("AppColor::")) {
- key = key.mid(10);
- }
-
- int appRoleInt = appEnum.keyToValue(key.toUtf8().constData());
-
- if (appRoleInt >= 0) {
- cfg.appColors[static_cast(appRoleInt)] = color;
- }
-
- continue;
- }
-
if (!currentSection.startsWith("Palette", Qt::CaseInsensitive)) {
continue;
}
@@ -255,7 +216,11 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath)
continue;
}
- cfg.colors[currentGroup][static_cast(roleInt)] = color;
+ QColor color(value);
+
+ if (color.isValid()) {
+ cfg.colors[currentGroup][static_cast(roleInt)] = color;
+ }
}
return cfg;
diff --git a/cockatrice/src/interface/theme_config.h b/cockatrice/src/interface/theme_config.h
index 567aeccda..07bf55b7a 100644
--- a/cockatrice/src/interface/theme_config.h
+++ b/cockatrice/src/interface/theme_config.h
@@ -3,25 +3,9 @@
#include
#include
-#include
#include
#include
-// Application-specific color roles, layered on top of the fixed QPalette role
-// set. Stored in the same palette-.toml under an [AppColors] section
-// and editable from the palette editor, so theme authors can control colors
-// beyond what Qt's palette can express.
-namespace AppColor
-{
-Q_NAMESPACE
-enum Role
-{
- AccentStrong,
- AccentSoft,
-};
-Q_ENUM_NS(Role)
-} // namespace AppColor
-
struct ThemeConfig
{
QString colorScheme;
@@ -37,16 +21,7 @@ struct ThemeConfig
struct PaletteConfig
{
QMap> colors;
- QMap appColors;
- bool operator==(const PaletteConfig &rhs) const
- {
- return colors == rhs.colors && appColors == rhs.appColors;
- }
- bool operator!=(const PaletteConfig &rhs) const
- {
- return !(*this == rhs);
- }
bool hasPalette() const;
QString toToml() const;
diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp
index d86ed77f9..e6b4b3c7f 100644
--- a/cockatrice/src/interface/theme_manager.cpp
+++ b/cockatrice/src/interface/theme_manager.cpp
@@ -1,13 +1,10 @@
#include "theme_manager.h"
#include "../../client/settings/cache_settings.h"
-#include "pixel_map_generator.h"
#include
#include
#include
-#include
-#include
#include
#include
#include
@@ -22,7 +19,7 @@
#include
#include
-#define SYSTEM_THEME_NAME "System"
+#define NONE_THEME_NAME "Default"
#define FUSION_THEME_NAME "Fusion"
#define STYLE_CSS_NAME "style.css"
#define HANDZONE_BG_NAME "handzone"
@@ -96,7 +93,7 @@ struct PaletteColorInfo
static QString usableDefaultStyle(const QString &style)
{
// The Windows 11 native style is broken: when the OS default
- // ("System" theme selection) would use it, fall back to the Vista style.
+ // ("Default" theme selection) would use it, fall back to the Vista style.
// Explicitly choosing "windows11" in a theme is still honored.
return style.compare("windows11", Qt::CaseInsensitive) == 0 ? QStringLiteral("windowsvista") : style;
}
@@ -119,16 +116,10 @@ ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
void ThemeManager::ensureThemeDirectoryExists()
{
- auto &settings = SettingsCache::instance();
-
- // Migrate the old "Default" theme name to "System"
- if (settings.getThemeName() == "Default") {
- settings.setThemeName(SYSTEM_THEME_NAME);
- }
-
- if (settings.getThemeName().isEmpty() || !getAvailableThemes().contains(settings.getThemeName())) {
+ if (SettingsCache::instance().getThemeName().isEmpty() ||
+ !getAvailableThemes().contains(SettingsCache::instance().getThemeName())) {
qCInfo(ThemeManagerLog) << "Theme name not set, setting default value";
- settings.setThemeName(FUSION_THEME_NAME);
+ SettingsCache::instance().setThemeName(NONE_THEME_NAME);
}
}
@@ -149,74 +140,11 @@ bool ThemeManager::isDarkMode(const QString &themeDirPath) const
}
}
-QString ThemeManager::schemeVariantPath(QStringView prefix) const
+bool ThemeManager::isBuiltInTheme()
{
- static const QStringList formats = {QStringLiteral(".png"), QStringLiteral(".jpg"), QStringLiteral(".jpeg"),
- QStringLiteral(".svg")};
- const QString scheme = isDarkMode(currentThemePath) ? QStringLiteral("dark") : QStringLiteral("light");
- const QString variantStem = prefix.toString() + QLatin1Char('-') + scheme;
+ const auto themeName = SettingsCache::instance().getThemeName();
- for (const QString &format : formats) {
- if (QFileInfo::exists(QStringLiteral("theme:") + variantStem + format)) {
- return variantStem + format;
- }
- }
- return QString();
-}
-
-QString ThemeManager::assetPath(QStringView prefix) const
-{
- // Probe order mirrors tryLoadImage: a theme may override the default SVG
- // with a raster of the same stem, so raster wins over SVG within a stem.
- static const QStringList formats = {QStringLiteral(".png"), QStringLiteral(".jpg"), QStringLiteral(".jpeg"),
- QStringLiteral(".svg")};
-
- auto findExisting = [](const QString &stem) {
- for (const QString &format : formats) {
- if (QFileInfo::exists(QStringLiteral("theme:") + stem + format)) {
- return stem + format;
- }
- }
- return QString();
- };
-
- // Prefer the scheme-qualified variant when it exists, else the plain
- // asset as the super fallback. Both return the resolved path including
- // its file extension so callers can load it directly.
- const QString variant = schemeVariantPath(prefix);
- if (!variant.isEmpty()) {
- return variant;
- }
- const QString resolvedPlain = findExisting(prefix.toString());
- return resolvedPlain.isEmpty() ? prefix.toString() : resolvedPlain;
-}
-
-// Probe whether a directory is truly writable by trying to create and remove a
-// temporary file. QFileInfo::isWritable() on a directory is unreliable (notably
-// on Windows where UAC VirtualStore can make a system dir appear writable).
-bool ThemeManager::isDirReallyWritable(const QString &dirPath)
-{
- const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test");
- QFile f(probe);
- if (!f.open(QIODevice::WriteOnly)) {
- return false;
- }
- f.close();
- f.remove();
- return true;
-}
-
-QString ThemeManager::writableThemeDir(const QString &themeName)
-{
- // All theme writes go to the user themes directory regardless of whether
- // the resolved (system) theme directory happens to be writable. Even when a
- // write would succeed in-place, routing it to the user directory keeps the
- // install intact and guarantees changes survive upgrades.
- const QString dirPath = QDir(SettingsCache::instance().paths().getThemesPath()).absoluteFilePath(themeName);
- if (!QDir().mkpath(dirPath)) {
- qWarning() << "Failed to create theme save directory:" << dirPath;
- }
- return dirPath;
+ return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME;
}
// System (read-only) themes location, relative to the application binary.
@@ -241,7 +169,9 @@ QStringMap &ThemeManager::getAvailableThemes()
// load themes from user profile dir
dir.setPath(SettingsCache::instance().paths().getThemesPath());
- availableThemes.insert(SYSTEM_THEME_NAME, dir.absoluteFilePath("System"));
+ // add default value
+ availableThemes.insert(NONE_THEME_NAME, dir.absoluteFilePath("Default"));
+
availableThemes.insert(FUSION_THEME_NAME, dir.absoluteFilePath("Fusion"));
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
@@ -250,7 +180,7 @@ QStringMap &ThemeManager::getAvailableThemes()
}
}
- // Load themes from Cockatrice system dir
+ // load themes from cockatrice system dir
dir.setPath(systemThemesBasePath());
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
@@ -265,7 +195,7 @@ QStringMap &ThemeManager::getAvailableThemes()
QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor)
{
QBrush brush;
- QPixmap tmp = QPixmap("theme:" + assetPath(QStringLiteral("zones/") + fileName));
+ QPixmap tmp = QPixmap("theme:zones/" + fileName);
if (tmp.isNull()) {
brush.setColor(fallbackColor);
brush.setStyle(Qt::SolidPattern);
@@ -279,7 +209,7 @@ QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor)
QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush)
{
QBrush brush;
- QPixmap tmp = QPixmap("theme:" + assetPath(QStringLiteral("zones/") + fileName));
+ QPixmap tmp = QPixmap("theme:zones/" + fileName);
if (tmp.isNull()) {
brush = fallbackBrush;
@@ -357,7 +287,7 @@ bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &col
void ThemeManager::setColorScheme(const QString &scheme)
{
- const QString dirPath = writableThemeDir(SettingsCache::instance().getThemeName());
+ const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName());
ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
cfg.colorScheme = scheme;
@@ -368,7 +298,7 @@ void ThemeManager::setColorScheme(const QString &scheme)
void ThemeManager::setStyleName(const QString &styleName)
{
- const QString dirPath = writableThemeDir(SettingsCache::instance().getThemeName());
+ const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName());
ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
cfg.styleName = styleName;
@@ -399,7 +329,7 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
Q_UNUSED(activeScheme)
#endif
QString styleName = themeCfg.styleName;
- if (styleName.isEmpty() || styleName.compare("System", Qt::CaseInsensitive) == 0) {
+ if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) {
if (themeName == FUSION_THEME_NAME) {
styleName = "Fusion";
} else {
@@ -442,8 +372,6 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
qApp->setPalette(base);
qApp->setStyle(style);
- currentAppColors = palCfg.appColors;
-
// Force every widget to re-polish and repaint immediately rather than
// waiting for natural expose events, which produces a patchwork of old
// and new colours during a live preview.
@@ -456,35 +384,6 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
style->polish(widget);
widget->update();
}
-
- emit paletteChanged();
-}
-
-QColor ThemeManager::appColor(AppColor::Role role) const
-{
- const auto it = currentAppColors.constFind(role);
- if (it != currentAppColors.constEnd()) {
- return it.value();
- }
-
- // QPalette::Accent was introduced in Qt 6.6 and several shipped palettes
- // set it to a value barely distinguishable from Window, so it is not a
- // reliable accent source. The selection highlight is the stable accent
- // (Accent defaults to Highlight when unset), and deriving from it
- // unconditionally keeps every Qt version rendering identically.
- const QColor accent = qApp->palette().color(QPalette::Active, QPalette::Highlight);
-
- if (role == AppColor::AccentSoft) {
- constexpr int SOFT_SATURATION_PERCENT = 70;
- constexpr int SOFT_LIGHTNESS_OFFSET = 60;
-
- // Light end of the gradient: same hue, softened and lightened
- return QColor::fromHsl(qMax(0, accent.hslHue()),
- qBound(0, qRound(accent.hslSaturation() * SOFT_SATURATION_PERCENT / 100.0), 255),
- qBound(0, accent.lightness() + SOFT_LIGHTNESS_OFFSET, 255));
- }
-
- return accent;
}
void ThemeManager::themeChangedSlot()
@@ -494,19 +393,9 @@ void ThemeManager::themeChangedSlot()
currentThemePath = dirPath;
QDir dir(dirPath);
- // CSS — prefer the scheme-qualified stylesheet (style-dark.css /
- // style-light.css) when present, else the plain style.css as fallback.
- if (!dirPath.isEmpty()) {
- const QString scheme = isDarkMode(dirPath) ? QStringLiteral("dark") : QStringLiteral("light");
- const QString schemeCss = QFileInfo(QStringLiteral(STYLE_CSS_NAME)).completeBaseName() + QLatin1Char('-') +
- scheme + QStringLiteral(".css");
- if (dir.exists(schemeCss)) {
- qApp->setStyleSheet("file:///" + dir.absoluteFilePath(schemeCss));
- } else if (dir.exists(STYLE_CSS_NAME)) {
- qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
- } else {
- qApp->setStyleSheet("");
- }
+ // CSS
+ if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) {
+ qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
} else {
qApp->setStyleSheet("");
}
@@ -521,19 +410,8 @@ void ThemeManager::themeChangedSlot()
// ── Load palette: custom first, then theme default ────────────────────
PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme);
- const PaletteConfig themeDefault = ThemeManager::loadDefaultPaletteConfig(dirPath, themeName, activeScheme);
- if (palette.hasPalette()) {
- // A custom palette written before [AppColors] existed carries no app
- // colors; merge the theme's shipped defaults so the identity colors
- // survive (hasPalette() counts an app-colors-only file as a palette,
- // so those are kept wholesale and never reach here empty).
- for (auto it = themeDefault.appColors.cbegin(); it != themeDefault.appColors.cend(); ++it) {
- if (!palette.appColors.contains(it.key())) {
- palette.appColors.insert(it.key(), it.value());
- }
- }
- } else {
- palette = themeDefault;
+ if (!palette.hasPalette()) {
+ palette = ThemeManager::loadDefaultPaletteConfig(dirPath, themeName, activeScheme);
}
applyStyleAndPalette(themeName, themeCfg, palette, activeScheme);
@@ -568,7 +446,6 @@ void ThemeManager::themeChangedSlot()
}
QPixmapCache::clear();
- clearPixmapGeneratorCaches();
emit themeChanged();
}
diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h
index aadb38ee9..79a1b6470 100644
--- a/cockatrice/src/interface/theme_manager.h
+++ b/cockatrice/src/interface/theme_manager.h
@@ -50,7 +50,6 @@ private:
QString currentThemePath;
std::array brushes;
QStringMap availableThemes;
- QMap currentAppColors;
/*
Internal cache for multiple backgrounds
*/
@@ -66,16 +65,7 @@ protected:
const QString &activeScheme);
public:
- // Resolves the directory to write theme changes to for the given theme
- // name. The resolved theme dir (user or system) is used when writable;
- // read-only system themes fall back to the user themes directory, creating
- // it if needed, so customisations never get lost on upgrade.
- static QString writableThemeDir(const QString &themeName);
- // Probe whether a directory is truly writable by trying to create and remove
- // a temporary file. QFileInfo::isWritable() on a directory is unreliable
- // (notably on Windows where UAC VirtualStore can make a system dir appear
- // writable).
- static bool isDirReallyWritable(const QString &dirPath);
+ bool isBuiltInTheme();
// Explicit color scheme of the theme: theme.cfg's ColorScheme setting
// (Dark/Light), falling back to the OS color scheme when it is "System".
bool isDarkMode(const QString &themeDirPath) const;
@@ -97,20 +87,6 @@ public:
// Load/save per-scheme palette colors
static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme);
static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg);
- // Resolve prefix to a scheme-qualified "theme:" path. Existence is probed
- // internally across the formats themes may ship (.png/.jpg/.svg), so
- // callers load the returned path directly. Prefers "-"
- // when a file exists at that stem, otherwise the plain "" as the
- // super fallback. The resolved scheme covers explicit light/dark as well
- // as OS-resolved "system". Returns the path with its file extension when a
- // match is found; unqualified assets keep working unchanged.
- QString assetPath(QStringView prefix) const;
- // Like assetPath, but resolves only the scheme-qualified variant
- // ("-.") and returns an empty string when no
- // variant exists — it never falls back to the plain "" asset.
- // Callers that must distinguish "no authored variant" (e.g. to keep a
- // legacy runtime fallback alive) should use this instead of assetPath.
- QString schemeVariantPath(QStringView prefix) const;
// Load the theme's shipped default palette, falling back to the system
// theme directory when it is absent from the resolved (user) directory.
static PaletteConfig
@@ -125,17 +101,12 @@ public:
void reloadCurrentTheme();
void previewPalette(const PaletteConfig &cfg, const QString &scheme);
- // Resolves an application color role: the theme's stored [AppColors] value
- // when present, otherwise a palette-accent-derived fallback.
- QColor appColor(AppColor::Role role) const;
-
QBrush &getBgBrush(Role zone);
QBrush getExtraBgBrush(Role zone, int zoneId = 0);
protected slots:
void themeChangedSlot();
signals:
void themeChanged();
- void paletteChanged();
};
extern ThemeManager *themeManager;
diff --git a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp
index 2199faf30..1ea1bcb10 100644
--- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp
+++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp
@@ -85,7 +85,7 @@ void ColorIdentityWidget::resizeEvent(QResizeEvent *event)
}
lastWidth = totalWidth;
- const int totalHeight = qMax(0, totalWidth / 6); // Set height to 1/4 of the width
+ const int totalHeight = totalWidth / 6; // Set height to 1/4 of the width
setFixedHeight(totalHeight);
const int count = layout->count();
@@ -97,10 +97,6 @@ void ColorIdentityWidget::resizeEvent(QResizeEvent *event)
const int availableWidth = totalWidth - (spacing * (count - 1));
const int iconSize = qMin(availableWidth / count, totalHeight); // Ensure icons fit within the new height
- if (iconSize <= 0) {
- lastIconSize = iconSize;
- return;
- }
if (iconSize == lastIconSize) {
return;
}
diff --git a/cockatrice/src/interface/widgets/cards/additional_info/deck_color_identity.cpp b/cockatrice/src/interface/widgets/cards/additional_info/deck_color_identity.cpp
deleted file mode 100644
index 62b01511e..000000000
--- a/cockatrice/src/interface/widgets/cards/additional_info/deck_color_identity.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-#include "deck_color_identity.h"
-
-#include
-#include
-#include