diff --git a/.ci/Fedora45/Dockerfile b/.ci/Fedora43/Dockerfile
similarity index 96%
rename from .ci/Fedora45/Dockerfile
rename to .ci/Fedora43/Dockerfile
index a7c49b249..8aa345651 100644
--- a/.ci/Fedora45/Dockerfile
+++ b/.ci/Fedora43/Dockerfile
@@ -1,4 +1,4 @@
-FROM fedora:45
+FROM fedora:43
RUN dnf install -y \
ca-certificates \
diff --git a/.ci/compile.sh b/.ci/compile.sh
index 7414b2dc3..569ae9f6b 100755
--- a/.ci/compile.sh
+++ b/.ci/compile.sh
@@ -203,11 +203,6 @@ if [[ ${WITH_CRASH_REPORTING:-} == "ON" ]]; then
# developer builds never reach Sentry.
flags+=("-DWITH_CRASH_REPORTING=ON")
fi
-if [[ ${COCKATRICE_DEBUG_INFO_LEVEL:-} ]]; then
- # CI lowers the DWARF level so one build configuration fits its compiler-cache bound;
- # see the option in CMakeLists.txt. Empty keeps the built-in flags.
- flags+=("-DCOCKATRICE_DEBUG_INFO_LEVEL=$COCKATRICE_DEBUG_INFO_LEVEL")
-fi
if [[ ${BUILDTYPE:-Release} == "Release" && -n ${COCKATRICE_SENTRY_DSN:-} ]]; then
# Release-only: Debug builds stay DSN-free so crashes from developer builds
# never reach Sentry. Empty (local builds, forks without the secret, and non-tag
diff --git a/.ci/docker.sh b/.ci/docker.sh
index c78ba3c31..64ea6baa7 100644
--- a/.ci/docker.sh
+++ b/.ci/docker.sh
@@ -181,11 +181,6 @@ function RUN ()
if [[ ${WITH_CRASH_REPORTING:-} ]]; then
args+=(--env "WITH_CRASH_REPORTING=${WITH_CRASH_REPORTING}")
fi
- # Same reason: the DWARF level reaches compile.sh only if it is handed to the container,
- # and without it CI would cache objects twice the size its bounds were measured for.
- if [[ ${COCKATRICE_DEBUG_INFO_LEVEL:-} ]]; then
- args+=(--env "COCKATRICE_DEBUG_INFO_LEVEL=${COCKATRICE_DEBUG_INFO_LEVEL}")
- fi
# shellcheck disable=2086
docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@"
return $?
diff --git a/.ci/release_template.md b/.ci/release_template.md
index 8eb4c7ebd..23b475150 100644
--- a/.ci/release_template.md
+++ b/.ci/release_template.md
@@ -21,8 +21,8 @@ Available pre-compiled binaries for installation:
• Ubuntu 24.04 LTS Noble Numbat
• Debian 13 Trixie
• Debian 12 Bookworm
- • Fedora 45
• Fedora 44
+ • Fedora 43
General Linux support is available via a flatpak package hosted at Flathub (x86 & ARM)!
Thanks to courtesy of @FFY00, the app is also available in Arch Linux 's official extra repository .
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index b859556aa..4ac28d3a5 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -149,8 +149,7 @@ jobs:
allow-failure: yes
package: skip # We are packaged in Arch already
- debug_test: yes # see the note on Fedora 45 below
- ccache_size: 850M # Debug plus the suite: 660 MB of objects at -g1
+ debug_test: yes # see the note on Fedora 44 below
- distro: Servatrice_Debian
version: 12
@@ -158,49 +157,33 @@ jobs:
package: DEB
server_only: yes
test: skip
- # 4.7 min and the server alone: nothing here for a compiler cache to save.
- persist_ccache: 'false'
+ ccache_size: 400M # server only, no client to compile
- distro: Debian
version: 13
package: DEB
- ccache_size: 800M # Release plus the suite: 614 MB of objects at -g1
-
- # The single Fedora leg used to build Debug for the suite and Release for the
- # package in one job (measured on Fedora 44: 30.8 min, the only leg slower than
- # Windows), so the package compile waited behind the Debug one. Two legs run in
- # parallel and each keeps a cache that holds exactly one configuration.
- #
- # Arch and Fedora 45 are the two newest toolchains, and the -Wno-error= exceptions
- # in CMakeLists.txt exist for exactly their warnings. This leg compiles Debug
- # because only Debug passes -Werror, so it is where a new warning still fails.
- - distro: Fedora
- version: 45
- variant: debug # separate image and ccache from the release leg below
-
- package: skip
- debug_test: yes
- ccache_size: 850M # Debug plus the suite: 660 MB of objects at -g1
+ ccache_size: 850M # the Release build plus the suite it runs
- distro: Fedora
- version: 45
- variant: release # separate image and ccache from the debug leg above
+ version: 44
package: RPM
- test: skip # The debug leg above runs this distro's suite
- ccache_size: 800M # Release plus the package: 614 MB of objects at -g1
+ # Arch and Fedora 44 are the two newest toolchains, and the -Wno-error= exceptions
+ # in CMakeLists.txt exist for exactly their warnings. They keep compiling Debug:
+ # only the Debug configuration passes -Werror, so these two are the legs where a
+ # new warning in either compiler still fails the build.
+ debug_test: yes
+ ccache_size: 900M # Debug for the suite and Release for the package
- distro: Ubuntu
version: 26.04
package: DEB
test: skip # The sanitizer leg below runs this distro's suite
- # 18.1 min, and the same distro as the sanitize leg below, which runs this suite.
- persist_ccache: 'false'
# The one leg that runs the suite under AddressSanitizer and
- # UndefinedBehaviorSanitizer. Debian 13 and Fedora 45 keep covering the
+ # UndefinedBehaviorSanitizer. Debian 13 and Fedora 44 keep covering the
# uninstrumented suite, so nothing is lost by instrumenting this one.
#
# It packages nothing. A sanitized Debug build is not something to ship, and
@@ -213,7 +196,7 @@ jobs:
package: skip
sanitize: yes
- ccache_size: 1200M # instrumented objects are larger than the 614 MB measured; estimated
+ ccache_size: 800M # instrumented objects are larger
name: ${{ matrix.distro }} ${{ matrix.version }}${{ matrix.variant && format(' {0}', matrix.variant) || '' }}
needs: configure
@@ -228,47 +211,25 @@ jobs:
CCACHE_EVICTION_AGE: 7d
# The bound is sized against the repository's 10 GB cache quota
# (https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy),
- # not against what one leg would like: the quota also holds about 0.9 GB of buildkit
- # layers (mode=min in docker-release.yml), 0.8 GB of vcpkg, 0.7 GB of Qt and the CodeQL
- # trap caches (about 1 GB each, which prune-caches below does not manage).
+ # not against what one leg would like. The quota currently holds about 1.1 GB of
+ # CodeQL trap, 1.1 GB of vcpkg, 0.7 GB of Qt and 2.1 GB of docker buildkit layers
+ # (mode=min in docker-release.yml, added here, brings the last one down to roughly
+ # 0.3 GB - the one input here that is an expectation rather than a measurement, since
+ # mode=min still writes one layer per pushed binary) plus the compiler caches of the nine legs a pull request runs. The five
+ # nightly-only legs keep no compiler cache (see PERSIST_CCACHE below), which is what
+ # makes these per-leg bounds affordable. Accounting for all of it: 6.45G of ccache
+ # bounds + 3.2G of the fixed entries = 9.65G against the 10G quota.
#
- # Measured 2026-11-04: one configuration costs 1.2 GB with full DWARF and 660 MB with
- # -g1 (COCKATRICE_DEBUG_INFO_LEVEL below), and the size of the cache decides the hit
- # rate far more sharply than its fill suggests. A 6G cache served 1059 of 1059 calls;
- # the 700M bound on the same leg served 14%, because ccache fills to the limit and then
- # evicts the entries the next build is about to ask for. A cache that cannot hold a
- # whole configuration therefore buys almost nothing, and the quota goes to fewer legs
- # at a size that holds one:
- #
- # Arch Debug + suite 660 MB working set 850M
- # Debian 13 Release + suite 614 MB 800M
- # Fedora 45 debug Debug + suite 660 MB 850M
- # Fedora 45 release Release + package 614 MB 800M
- # Ubuntu 26.04 sanitize ~1.0 GB 1200M
- #
- # That is about 5.3 GB of bounds, and with the fixed entries above it puts the quota at
- # roughly 9.8 GB of 10 - so only legs worth their share get one. A leg keeps no cache
- # when it duplicates another leg's coverage (Ubuntu 26.04 behind the sanitize leg of
- # the same distro; macOS 15 and macOS 27 Debug behind macOS 14; Servatrice covers the
- # server alone and finishes in 4.7 min). Those four run cold: 4.7, 18.1, 16.5 and
- # 15.8 min. The five that stay compile the client on the toolchains a change can break
- # and are the ones a cache shortens by the most. Measured job totals on master
- # 2026-11-04 also show the ceiling this leaves: Windows 10 is 29.5 min and compiles for
- # 23.7 of it with no compiler cache at all, so Windows still sets the pull-request
- # wall clock no matter how warm these legs are (see build-vcpkg).
- #
- # Re-add a leg only with a bound that holds its whole configuration: a smaller one is
- # worse than none.
+ # A configuration needs roughly 500-850 MB on Linux, so the old uniform 600M bound sat
+ # below what a leg writes: ccache spent each build deleting the entries the next run
+ # asked for (measured: 2624-3720 cleanup passes per build, 8% of calls served). Each
+ # leg states the size it needs instead: one configuration for most, two for Fedora 44
+ # (Debug for the suite, Release for the package), the server alone for Servatrice.
+ # Raising these further means caching fewer legs, since GitHub evicts a whole entry
+ # when the total is exceeded.
#
# Every key listed in LIVE_CCACHE_KEYS in prune-caches below must match this matrix.
CCACHE_SIZE: ${{ matrix.ccache_size || '700M' }}
- # Debug information dominates a cached object: with the built-in flags one
- # configuration is 1.2 GB and no per-leg bound can hold it, so CI lowers the level to
- # 1 (file and line, which is what a failing test or a sanitizer backtrace reports) and
- # one configuration halves. Empty on tags and on the nightly legs, which ship binaries
- # and keep full DWARF for the Sentry symbols; a tag restores a cache it cannot use and
- # is slower, which is accepted because a tag is rare and only pull requests gate.
- COCKATRICE_DEBUG_INFO_LEVEL: ${{ github.ref_type != 'tag' && '1' || '' }}
CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
IMAGE_SUFFIX: ${{ matrix.variant }}
@@ -281,13 +242,11 @@ jobs:
# also package, so testing and packaging cost one compile instead of two.
DEBUG_TEST: ${{ matrix.sanitize == 'yes' || matrix.coverage == 'yes' || matrix.debug_test == 'yes' }}
RUN_TESTS: ${{ matrix.test != 'skip' && !(matrix.sanitize == 'yes' || matrix.coverage == 'yes' || matrix.debug_test == 'yes') }}
- # Whether this leg restores and saves its compiler cache. Not every pull-request leg
- # keeps one: a cache only pays for itself when its bound holds a whole configuration
- # (see the sizing comment above), and a leg that duplicates another leg's coverage
- # spends quota without adding any. The nightly-only legs keep none either. Fedora 45
- # states it per leg: its two configurations have their own job and key, so neither
- # bound is shared with a build whose objects it would evict.
- PERSIST_CCACHE: ${{ matrix.persist_ccache || 'true' }}
+ # Whether this leg restores and saves its compiler cache. Every leg a pull request
+ # runs keeps one; the nightly-only legs do not, so that the quota is spent on the
+ # builds that review a change. A tag build runs both sets, and the nightly legs are
+ # cold there - accepted, because a tag is rare and only the pull-request legs gate.
+ PERSIST_CCACHE: 'true'
# Shared with build-linux-nightly so the two leg sets run the same steps.
steps: &linux-steps
@@ -507,7 +466,7 @@ jobs:
test: skip # Running tests on all distros is superfluous
- distro: Fedora
- version: 44 # previous release, still covered by the nightly
+ version: 43 # previous release, still covered by the nightly
package: RPM
test: skip # Running tests on all distros is superfluous
@@ -559,12 +518,6 @@ jobs:
CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
IMAGE_SUFFIX: ${{ matrix.variant }}
- # The shared steps below gate on these, so they have to be derived here too. Left
- # undefined they silently skipped the build the leg exists for: the coverage leg
- # found build/ empty and its report collection failed on every nightly and tag run.
- # Same derivation as the pull request job above.
- DEBUG_TEST: ${{ matrix.sanitize == 'yes' || matrix.coverage == 'yes' || matrix.debug_test == 'yes' }}
- RUN_TESTS: ${{ matrix.test != 'skip' && !(matrix.sanitize == 'yes' || matrix.coverage == 'yes' || matrix.debug_test == 'yes') }}
steps: *linux-steps
build-vcpkg:
@@ -581,7 +534,6 @@ jobs:
make_package: 1
override_target: 14
package_suffix: "-macOS14"
- ccache_size: 800M # one Release configuration plus the suite at -g1
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
@@ -600,7 +552,6 @@ jobs:
make_package: 1
override_target: 15
package_suffix: "-macOS15"
- persist_ccache: 'false' # 16.5 min: shorter than the Windows leg
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
@@ -615,7 +566,6 @@ jobs:
ccache_eviction_age: 7d
cmake_generator: Ninja
- persist_ccache: 'false' # 15.8 min: shorter than the Windows leg
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
@@ -653,17 +603,13 @@ jobs:
timeout-minutes: 100
env:
CCACHE_DIR: ${{ github.workspace }}/.cache/
- # One macOS configuration is about 600 MB of objects at -g1, so its bound needs to be
- # 800M to hold one whole; a smaller cache serves a few percent and is worthless (see
- # the sizing comment in build-linux). macOS 14 keeps the one macOS cache: it runs the
- # suite against the shipping Release build and is the longest of the three at 24.2 min.
- # macOS 15 (16.5) and macOS 27 Debug (15.8) duplicate its coverage and are shorter, so
- # they run cold.
- CCACHE_SIZE: ${{ matrix.ccache_size || '700M' }}
- PERSIST_CCACHE: ${{ matrix.persist_ccache || 'true' }}
- # MSVC has no -g level, so the Windows leg does not ask for one; it has no compiler
- # cache to shrink anyway (see the sizing comment in build-linux).
- COCKATRICE_DEBUG_INFO_LEVEL: ${{ matrix.os != 'Windows' && github.ref_type != 'tag' && '1' || '' }}
+ # macOS configurations compile about twice the TUs of a Linux one, so each of these
+ # three legs gets a bound above what it has been measured to write. The four macOS
+ # legs used to be bounded at 600M from the same quota as the Linux ones; the three
+ # pull-request legs now get 700M each while the nightly Intel leg keeps none, and
+ # that is what fits (see CCACHE_SIZE in build-linux for the full arithmetic).
+ CCACHE_SIZE: 700M
+ PERSIST_CCACHE: 'true'
# Shared with build-vcpkg-nightly so the two leg sets run the same steps.
steps: &vcpkg-steps
@@ -1213,10 +1159,9 @@ jobs:
LIVE_VCPKG_SCOPES: 'vcpkg/macos13-x64-xcode26.3/ vcpkg/macos14-arm64-xcode26.6/ vcpkg/windows10-x64-vs2026/'
# Must list the ccache key of every leg that keeps a compiler cache
# (PERSIST_CCACHE in build-linux and build-vcpkg), or a live entry is deleted.
- # The nightly legs and the pull-request legs that finish under the Windows leg
- # keep none, so their leftover entries are reclaimed here instead of waiting out
- # the age rule.
- LIVE_CCACHE_KEYS: 'ccache-Arch ccache-Debian13 ccache-Fedora45debug ccache-Fedora45release ccache-Ubuntu26.04sanitize ccache-macOS14-xcode-27'
+ # The nightly legs keep none, so their leftover entries are reclaimed here
+ # instead of waiting out the age rule.
+ LIVE_CCACHE_KEYS: 'ccache-Arch ccache-Servatrice_Debian12 ccache-Debian13 ccache-Fedora44 ccache-Ubuntu26.04 ccache-Ubuntu26.04sanitize ccache-macOS14-xcode-27 ccache-macOS15-xcode-27 ccache-macOS-xcode-27'
run: |
gh api --paginate "repos/$GITHUB_REPOSITORY/actions/caches?per_page=100" \
--jq '.actions_caches[] | [.id, .ref, .key, .last_accessed_at] | @tsv' >"$RUNNER_TEMP/caches.tsv"
@@ -1240,9 +1185,8 @@ jobs:
if [[ $ref != refs/heads/master ]]; then
reason="scoped to $ref"
elif [[ $key == ccache-* ]]; then
- # An exact token compare, because one key can be a prefix of another:
- # ccache-Ubuntu26.04 is a prefix of ccache-Ubuntu26.04sanitize, and only
- # the sanitize entry is live.
+ # An exact token compare: ccache-Ubuntu26.04 is a prefix of
+ # ccache-Ubuntu26.04sanitize, and both keys are live
if [[ $live_ccache != *" $key "* ]]; then
reason="no pull-request leg keeps it"
fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 53508fcd1..9044bc005 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -238,27 +238,6 @@ include(Sanitizers)
include(Coverage)
include(CrashReporting)
-# Debug information is what makes a cached object large, and a build configuration needs more
-# room than a per-leg cache bound can spare when it carries full DWARF: ccache then fills to
-# its limit and evicts the entries the next build is about to ask for, so it serves a few
-# percent of calls while holding most of a build. CI sets a lower level so one whole
-# configuration fits; level 1 keeps file and line, which is what a failing test or a sanitizer
-# backtrace reports. Empty (the default) leaves local, distro and release builds exactly as
-# they were. This comes after CrashReporting, so the -g it adds for Sentry symbols cannot
-# raise the level again.
-set(COCKATRICE_DEBUG_INFO_LEVEL
- ""
- CACHE STRING "DWARF level for Debug and Release objects (empty keeps the built-in flags)"
-)
-if(NOT COCKATRICE_DEBUG_INFO_LEVEL STREQUAL "")
- if(MSVC)
- message(WARNING "COCKATRICE_DEBUG_INFO_LEVEL is ignored on MSVC")
- else()
- string(APPEND CMAKE_CXX_FLAGS_DEBUG " -g${COCKATRICE_DEBUG_INFO_LEVEL}")
- string(APPEND CMAKE_CXX_FLAGS_RELEASE " -g${COCKATRICE_DEBUG_INFO_LEVEL}")
- endif()
-endif()
-
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_definitions("-DSFMT_MEXP=19937")
diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index f34961b0b..b76783167 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -151,7 +151,6 @@ set(cockatrice_SOURCES
src/interface/widgets/utility/reversed_completer_model.cpp
src/interface/pixel_map_generator.cpp
src/interface/theme_config.cpp
- src/interface/theme_icon.cpp
src/interface/theme_manager.cpp
src/interface/palette_editor/color_button.cpp
src/interface/palette_editor/palette_generator.cpp
@@ -253,8 +252,6 @@ set(cockatrice_SOURCES
src/interface/widgets/playmat/playmat_settings_dialog.cpp
src/interface/widgets/quick_settings/settings_button_widget.cpp
src/interface/widgets/quick_settings/settings_popup_widget.cpp
- src/interface/widgets/replay/replay_event_index.cpp
- src/interface/widgets/replay/replay_event_sidebar.cpp
src/interface/widgets/replay/replay_manager.cpp
src/interface/widgets/replay/replay_quick_settings_widget.cpp
src/interface/widgets/replay/replay_timeline_widget.cpp
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index 229cdd125..4ec3d2385 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -6,18 +6,19 @@
resources/hand.svg
resources/hr.jpg
- resources/icons/arrow_down.svg
+ resources/icons/arrow_bottom_green.svg
+ resources/icons/arrow_down_green.svg
resources/icons/arrow_history.svg
- resources/icons/arrow_left.svg
+ resources/icons/arrow_left_green.svg
resources/icons/arrow_redo.svg
- resources/icons/arrow_right.svg
- resources/icons/arrow_right_sideboard.svg
- resources/icons/arrow_to_bottom.svg
- resources/icons/arrow_to_top.svg
- resources/icons/arrow_up.svg
+ resources/icons/arrow_right_blue.svg
+ resources/icons/arrow_right_green.svg
+ resources/icons/arrow_top_green.svg
+ resources/icons/arrow_up_green.svg
resources/icons/arrow_undo.svg
resources/icons/circle_half_stroke.svg
resources/icons/clearsearch.svg
+ resources/icons/cogwheel.svg
resources/icons/conceded.svg
resources/icons/decrement.svg
resources/icons/delete.svg
@@ -27,6 +28,7 @@
resources/icons/filter.svg
resources/icons/floppy_disk.svg
resources/icons/forgot_password.svg
+ resources/icons/gear.svg
resources/icons/increment.svg
resources/icons/info.svg
resources/icons/lock.svg
@@ -39,6 +41,7 @@
resources/icons/reload.svg
resources/icons/remove_row.svg
resources/icons/rename.svg
+ resources/icons/scale_balanced.svg
resources/icons/scales.svg
resources/icons/scroll.svg
resources/icons/search.svg
@@ -49,6 +52,7 @@
resources/icons/swap.svg
resources/icons/sync.svg
resources/icons/tab_changed.svg
+ resources/icons/update.png
resources/icons/view.svg
resources/icons/mana/B.svg
diff --git a/cockatrice/resources/icons/arrow_bottom_green.svg b/cockatrice/resources/icons/arrow_bottom_green.svg
new file mode 100644
index 000000000..3a45053ad
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_bottom_green.svg
@@ -0,0 +1,793 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/arrow_down.svg b/cockatrice/resources/icons/arrow_down.svg
deleted file mode 100644
index 0ff237f52..000000000
--- a/cockatrice/resources/icons/arrow_down.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/icons/arrow_down_green.svg b/cockatrice/resources/icons/arrow_down_green.svg
new file mode 100644
index 000000000..574c12060
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_down_green.svg
@@ -0,0 +1,793 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/arrow_history.svg b/cockatrice/resources/icons/arrow_history.svg
index 29374d23a..d130758e6 100644
--- a/cockatrice/resources/icons/arrow_history.svg
+++ b/cockatrice/resources/icons/arrow_history.svg
@@ -1,5 +1,8 @@
-
-
-
-
-
+
+
+
+ history
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/arrow_left.svg b/cockatrice/resources/icons/arrow_left.svg
deleted file mode 100644
index 7a88deddb..000000000
--- a/cockatrice/resources/icons/arrow_left.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/icons/arrow_left_green.svg b/cockatrice/resources/icons/arrow_left_green.svg
new file mode 100644
index 000000000..1b9eb0684
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_left_green.svg
@@ -0,0 +1,791 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/arrow_redo.svg b/cockatrice/resources/icons/arrow_redo.svg
index 141260269..dd89e3314 100644
--- a/cockatrice/resources/icons/arrow_redo.svg
+++ b/cockatrice/resources/icons/arrow_redo.svg
@@ -1,4 +1,40 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/arrow_right.svg b/cockatrice/resources/icons/arrow_right.svg
deleted file mode 100644
index f0ac5d5bf..000000000
--- a/cockatrice/resources/icons/arrow_right.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/icons/arrow_right_blue.svg b/cockatrice/resources/icons/arrow_right_blue.svg
new file mode 100644
index 000000000..334cd68fd
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_right_blue.svg
@@ -0,0 +1,789 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/arrow_right_green.svg b/cockatrice/resources/icons/arrow_right_green.svg
new file mode 100644
index 000000000..db491985d
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_right_green.svg
@@ -0,0 +1,789 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/arrow_right_sideboard.svg b/cockatrice/resources/icons/arrow_right_sideboard.svg
deleted file mode 100644
index 79a8cd5d3..000000000
--- a/cockatrice/resources/icons/arrow_right_sideboard.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/cockatrice/resources/icons/arrow_to_bottom.svg b/cockatrice/resources/icons/arrow_to_bottom.svg
deleted file mode 100644
index 291a527d2..000000000
--- a/cockatrice/resources/icons/arrow_to_bottom.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/cockatrice/resources/icons/arrow_to_top.svg b/cockatrice/resources/icons/arrow_to_top.svg
deleted file mode 100644
index aac38cc33..000000000
--- a/cockatrice/resources/icons/arrow_to_top.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/cockatrice/resources/icons/arrow_top_green.svg b/cockatrice/resources/icons/arrow_top_green.svg
new file mode 100644
index 000000000..c515c6a1d
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_top_green.svg
@@ -0,0 +1,793 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/arrow_undo.svg b/cockatrice/resources/icons/arrow_undo.svg
index c748b37b8..2e182ffb2 100644
--- a/cockatrice/resources/icons/arrow_undo.svg
+++ b/cockatrice/resources/icons/arrow_undo.svg
@@ -1,4 +1,40 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/arrow_up.svg b/cockatrice/resources/icons/arrow_up.svg
deleted file mode 100644
index ac32b5c92..000000000
--- a/cockatrice/resources/icons/arrow_up.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/icons/arrow_up_green.svg b/cockatrice/resources/icons/arrow_up_green.svg
new file mode 100644
index 000000000..1d0f61112
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_up_green.svg
@@ -0,0 +1,792 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/circle_half_stroke.svg b/cockatrice/resources/icons/circle_half_stroke.svg
index c16f84858..42e4dbca2 100644
--- a/cockatrice/resources/icons/circle_half_stroke.svg
+++ b/cockatrice/resources/icons/circle_half_stroke.svg
@@ -1,4 +1 @@
-
-
-
-
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/clearsearch.svg b/cockatrice/resources/icons/clearsearch.svg
index 49d883c68..be50d68d6 100644
--- a/cockatrice/resources/icons/clearsearch.svg
+++ b/cockatrice/resources/icons/clearsearch.svg
@@ -1,4 +1,276 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/cogwheel.svg b/cockatrice/resources/icons/cogwheel.svg
new file mode 100644
index 000000000..8c7aad617
--- /dev/null
+++ b/cockatrice/resources/icons/cogwheel.svg
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/conceded.svg b/cockatrice/resources/icons/conceded.svg
index b9faf047c..a487f18bb 100644
--- a/cockatrice/resources/icons/conceded.svg
+++ b/cockatrice/resources/icons/conceded.svg
@@ -1,4 +1,103 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/decrement.svg b/cockatrice/resources/icons/decrement.svg
index 033245f85..0ceddf69f 100644
--- a/cockatrice/resources/icons/decrement.svg
+++ b/cockatrice/resources/icons/decrement.svg
@@ -1,3 +1,205 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/delete.svg b/cockatrice/resources/icons/delete.svg
index d79312f1e..008d8d429 100644
--- a/cockatrice/resources/icons/delete.svg
+++ b/cockatrice/resources/icons/delete.svg
@@ -1,5 +1,357 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/dragon.svg b/cockatrice/resources/icons/dragon.svg
index cc48d1f5c..f45af39ef 100644
--- a/cockatrice/resources/icons/dragon.svg
+++ b/cockatrice/resources/icons/dragon.svg
@@ -1,6 +1 @@
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/dropdown_collapsed.svg b/cockatrice/resources/icons/dropdown_collapsed.svg
index 429c24974..52e833e65 100644
--- a/cockatrice/resources/icons/dropdown_collapsed.svg
+++ b/cockatrice/resources/icons/dropdown_collapsed.svg
@@ -1,3 +1,10 @@
-
-
+
+
+
+
+ ]>
+
+
diff --git a/cockatrice/resources/icons/dropdown_expanded.svg b/cockatrice/resources/icons/dropdown_expanded.svg
index 0b24aeb48..fa3ee205c 100644
--- a/cockatrice/resources/icons/dropdown_expanded.svg
+++ b/cockatrice/resources/icons/dropdown_expanded.svg
@@ -1,3 +1,10 @@
-
-
+
+
+
+
+ ]>
+
+
diff --git a/cockatrice/resources/icons/filter.svg b/cockatrice/resources/icons/filter.svg
index d9cdf879c..d53d9ba29 100644
--- a/cockatrice/resources/icons/filter.svg
+++ b/cockatrice/resources/icons/filter.svg
@@ -1,3 +1,12 @@
-
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/floppy_disk.svg b/cockatrice/resources/icons/floppy_disk.svg
index 10b9e8172..3c5b5054d 100644
--- a/cockatrice/resources/icons/floppy_disk.svg
+++ b/cockatrice/resources/icons/floppy_disk.svg
@@ -1,5 +1 @@
-
-
-
-
-
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/forgot_password.svg b/cockatrice/resources/icons/forgot_password.svg
index 3bfb3bdb1..f938ca3f4 100644
--- a/cockatrice/resources/icons/forgot_password.svg
+++ b/cockatrice/resources/icons/forgot_password.svg
@@ -1,6 +1,6 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/cockatrice/resources/icons/gear.svg b/cockatrice/resources/icons/gear.svg
new file mode 100644
index 000000000..fdee3a297
--- /dev/null
+++ b/cockatrice/resources/icons/gear.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/increment.svg b/cockatrice/resources/icons/increment.svg
index 52dbdb2aa..6d5f7a85a 100644
--- a/cockatrice/resources/icons/increment.svg
+++ b/cockatrice/resources/icons/increment.svg
@@ -1,4 +1,199 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/info.svg b/cockatrice/resources/icons/info.svg
index 1305f6b3d..00b23cc2c 100644
--- a/cockatrice/resources/icons/info.svg
+++ b/cockatrice/resources/icons/info.svg
@@ -1,5 +1,43 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/lock.svg b/cockatrice/resources/icons/lock.svg
index f150ece49..b7ebf1a53 100644
--- a/cockatrice/resources/icons/lock.svg
+++ b/cockatrice/resources/icons/lock.svg
@@ -1,4 +1,750 @@
-
-
-
+
+
+
+
+
+
+
+
+ padlock
+
+
+
+ icon
+
+ lock
+ security
+ secure
+ padlock
+ signs_and_symbols
+
+
+
+
+ AJ Ashton
+
+
+
+
+ AJ Ashton
+
+
+
+
+ AJ Ashton
+
+
+
+ image/svg+xml
+
+
+ en
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/not_ready_start.svg b/cockatrice/resources/icons/not_ready_start.svg
index c20a5cfe6..73afda9db 100644
--- a/cockatrice/resources/icons/not_ready_start.svg
+++ b/cockatrice/resources/icons/not_ready_start.svg
@@ -1,4 +1,368 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/pen_to_square.svg b/cockatrice/resources/icons/pen_to_square.svg
index 7d3e161ac..60ff1d9c4 100644
--- a/cockatrice/resources/icons/pen_to_square.svg
+++ b/cockatrice/resources/icons/pen_to_square.svg
@@ -1,4 +1 @@
-
-
-
-
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/pencil.svg b/cockatrice/resources/icons/pencil.svg
index d274e98db..c8b81f698 100644
--- a/cockatrice/resources/icons/pencil.svg
+++ b/cockatrice/resources/icons/pencil.svg
@@ -1,4 +1,123 @@
-
-
-
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/pin.svg b/cockatrice/resources/icons/pin.svg
index 9c173286b..71d4c9c9c 100644
--- a/cockatrice/resources/icons/pin.svg
+++ b/cockatrice/resources/icons/pin.svg
@@ -1,4 +1,24 @@
-
-
-
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/player.svg b/cockatrice/resources/icons/player.svg
index 9fbe39a11..e83b24654 100644
--- a/cockatrice/resources/icons/player.svg
+++ b/cockatrice/resources/icons/player.svg
@@ -1,8 +1,142 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/ready_start.svg b/cockatrice/resources/icons/ready_start.svg
index 4c4ea5d2d..238d2aa46 100644
--- a/cockatrice/resources/icons/ready_start.svg
+++ b/cockatrice/resources/icons/ready_start.svg
@@ -1,3 +1,81 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/reload.svg b/cockatrice/resources/icons/reload.svg
index e09b1b7ef..102995876 100644
--- a/cockatrice/resources/icons/reload.svg
+++ b/cockatrice/resources/icons/reload.svg
@@ -1,4 +1,7 @@
-
-
-
-
+
+
+
+ Svg Vector Icons : http://www.onlinewebfonts.com/icon
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/remove_row.svg b/cockatrice/resources/icons/remove_row.svg
index 5d00ed57c..6f133cdb6 100644
--- a/cockatrice/resources/icons/remove_row.svg
+++ b/cockatrice/resources/icons/remove_row.svg
@@ -1,4 +1,356 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/rename.svg b/cockatrice/resources/icons/rename.svg
index bab850808..c68a00246 100644
--- a/cockatrice/resources/icons/rename.svg
+++ b/cockatrice/resources/icons/rename.svg
@@ -1,7 +1,10 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/scale_balanced.svg b/cockatrice/resources/icons/scale_balanced.svg
new file mode 100644
index 000000000..fae468d38
--- /dev/null
+++ b/cockatrice/resources/icons/scale_balanced.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/scales.svg b/cockatrice/resources/icons/scales.svg
index 4b2e16fcc..a410921dd 100644
--- a/cockatrice/resources/icons/scales.svg
+++ b/cockatrice/resources/icons/scales.svg
@@ -1,7 +1,6 @@
-
-
-
-
-
-
+
+
+
+
+
diff --git a/cockatrice/resources/icons/scroll.svg b/cockatrice/resources/icons/scroll.svg
index edcf29d11..f4828e679 100644
--- a/cockatrice/resources/icons/scroll.svg
+++ b/cockatrice/resources/icons/scroll.svg
@@ -1,6 +1 @@
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/search.svg b/cockatrice/resources/icons/search.svg
index be4a87f00..05439a12e 100644
--- a/cockatrice/resources/icons/search.svg
+++ b/cockatrice/resources/icons/search.svg
@@ -1,4 +1,198 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/settings.svg b/cockatrice/resources/icons/settings.svg
index 3942a0916..77a2ccfa9 100644
--- a/cockatrice/resources/icons/settings.svg
+++ b/cockatrice/resources/icons/settings.svg
@@ -1,4 +1,280 @@
-
-
-
+
+
+
+
+
+
+
+ Ruota dentata grigia
+
+
+
+ unsorted
+
+
+
+
+ Open Clip Art Library
+
+
+
+
+ Architetto Francesco Rollandin
+
+
+
+
+ Architetto Francesco Rollandin
+
+
+
+ image/svg+xml
+
+
+ en
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/share.svg b/cockatrice/resources/icons/share.svg
index 9d6ff21a0..a84f55847 100644
--- a/cockatrice/resources/icons/share.svg
+++ b/cockatrice/resources/icons/share.svg
@@ -1,7 +1,25 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/sort_arrow_down.svg b/cockatrice/resources/icons/sort_arrow_down.svg
index e10b401f2..adf10f9f0 100644
--- a/cockatrice/resources/icons/sort_arrow_down.svg
+++ b/cockatrice/resources/icons/sort_arrow_down.svg
@@ -1,7 +1 @@
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/spectator.svg b/cockatrice/resources/icons/spectator.svg
index 19242f058..c580c448d 100644
--- a/cockatrice/resources/icons/spectator.svg
+++ b/cockatrice/resources/icons/spectator.svg
@@ -1,4 +1,106 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/swap.svg b/cockatrice/resources/icons/swap.svg
index 4eddb8c08..a18ca2f06 100644
--- a/cockatrice/resources/icons/swap.svg
+++ b/cockatrice/resources/icons/swap.svg
@@ -1,6 +1,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/sync.svg b/cockatrice/resources/icons/sync.svg
index fa00b3024..e287b1993 100644
--- a/cockatrice/resources/icons/sync.svg
+++ b/cockatrice/resources/icons/sync.svg
@@ -1,6 +1,6 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/cockatrice/resources/icons/tab_changed.svg b/cockatrice/resources/icons/tab_changed.svg
index 1d9b8d09c..ae09ef8fd 100644
--- a/cockatrice/resources/icons/tab_changed.svg
+++ b/cockatrice/resources/icons/tab_changed.svg
@@ -1,4 +1,116 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cockatrice/resources/icons/update.png b/cockatrice/resources/icons/update.png
new file mode 100644
index 000000000..825c0aee7
Binary files /dev/null and b/cockatrice/resources/icons/update.png differ
diff --git a/cockatrice/resources/icons/view.svg b/cockatrice/resources/icons/view.svg
index 19242f058..e5676d825 100644
--- a/cockatrice/resources/icons/view.svg
+++ b/cockatrice/resources/icons/view.svg
@@ -1,4 +1,29 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/src/client/network/update/client/update_downloader.cpp b/cockatrice/src/client/network/update/client/update_downloader.cpp
index 8b5ef3d6c..8868876fa 100644
--- a/cockatrice/src/client/network/update/client/update_downloader.cpp
+++ b/cockatrice/src/client/network/update/client/update_downloader.cpp
@@ -1,16 +1,15 @@
#include "update_downloader.h"
+#include
+#include
#include
-#include
+#include
#include
#include
#include
-#include
-#include
#include
#include
#include
-#include
UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(nullptr)
{
@@ -24,116 +23,48 @@ void UpdateDownloader::beginDownload(QUrl downloadUrl)
originalUrl = downloadUrl;
}
- if (auto *old = std::exchange(response, nullptr)) {
- old->abort();
- old->deleteLater();
- }
-
- errorReported = false;
- cancelRequested = false;
-
- // The transfer timeout comes from the manager (NetworkManagerFactory).
response = netMan->get(QNetworkRequest(downloadUrl));
connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished);
- connect(response, &QNetworkReply::errorOccurred, this, &UpdateDownloader::downloadError);
connect(response, &QNetworkReply::downloadProgress, this, &UpdateDownloader::downloadProgress);
- connect(this, &UpdateDownloader::stopDownload, this, &UpdateDownloader::cancelDownload);
+ connect(this, &UpdateDownloader::stopDownload, response, &QNetworkReply::abort);
}
-void UpdateDownloader::downloadError(QNetworkReply::NetworkError networkError)
+void UpdateDownloader::downloadError(QNetworkReply::NetworkError)
{
- // A reply that was superseded by beginDownload() can still deliver a
- // queued error; it must not be reported against the current reply.
- if (response == nullptr || sender() != response || errorReported) {
+ if (response == nullptr) {
return;
}
- // A transfer timeout aborts the reply, so it arrives as OperationCanceledError.
- // Only an abort the user asked for is suppressed; every other cancel is a
- // stalled transfer and has to be reported.
- if (networkError == QNetworkReply::OperationCanceledError) {
- if (cancelRequested) {
- return;
- }
-
- errorReported = true;
- emit error(tr("The download timed out. Check your connection and try again."));
- return;
- }
-
- errorReported = true;
- emit error(response->errorString());
-}
-
-void UpdateDownloader::cancelDownload()
-{
- cancelRequested = true;
- if (response != nullptr) {
- response->abort();
- }
+ emit error(response->errorString().toUtf8());
}
void UpdateDownloader::fileFinished()
{
- // An aborted, superseded reply also emits finished(). Only the reply
- // currently stored in response may be consumed.
- if (response == nullptr || sender() != response) {
+ // If we finished but there's a redirect, follow it
+ QVariant redirect = response->attribute(QNetworkRequest::RedirectionTargetAttribute);
+ if (!redirect.isNull()) {
+ beginDownload(redirect.toUrl());
return;
}
- // errorOccurred normally reports this first; keep the check for errors that
- // only surface when the reply finishes.
- if (response->error() != QNetworkReply::NoError) {
- downloadError(response->error());
- response->deleteLater();
- response = nullptr;
+ // Handle any errors we had
+ if (response->error()) {
+ emit error(response->errorString());
return;
}
- // Give the update its own private directory. The name is unpredictable, so a
- // local user cannot pre-create or symlink the path, and the original file
- // name (including the installer suffix) is preserved inside it.
- QTemporaryDir downloadDir(QDir::temp().filePath(QStringLiteral("cockatrice-update-XXXXXX")));
- if (!downloadDir.isValid()) {
- emit error(tr("Could not create a temporary directory for the download."));
- response->deleteLater();
- response = nullptr;
- return;
- }
- downloadDir.setAutoRemove(false);
+ // Work out the file name of the download
+ QString fileName = QDir::temp().path() + QDir::separator() + originalUrl.toString().section('/', -1);
- const QString remoteName = QFileInfo(originalUrl.path()).fileName();
- const QString fileName = downloadDir.filePath(remoteName.isEmpty() ? QStringLiteral("update") : remoteName);
-
- QSaveFile file(fileName);
+ // Save the build in a temporary directory
+ QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
- downloadDir.remove();
- emit error(tr("Could not open the file for writing: %1").arg(file.errorString()));
- response->deleteLater();
- response = nullptr;
+ emit error(tr("Could not open the file for reading."));
return;
}
- const QByteArray data = response->readAll();
- if (file.write(data) != data.size()) {
- file.cancelWriting();
- downloadDir.remove();
- emit error(tr("Could not write the downloaded file to disk."));
- response->deleteLater();
- response = nullptr;
- return;
- }
-
- if (!file.commit()) {
- downloadDir.remove();
- emit error(tr("Could not save the downloaded file: %1").arg(file.errorString()));
- response->deleteLater();
- response = nullptr;
- return;
- }
-
- response->deleteLater();
- response = nullptr;
+ file.write(response->readAll());
+ file.close();
// Emit the success signal with a URL to the download file
emit downloadSuccessful(QUrl::fromLocalFile(fileName));
diff --git a/cockatrice/src/client/network/update/client/update_downloader.h b/cockatrice/src/client/network/update/client/update_downloader.h
index 7704eca38..8aeefb22a 100644
--- a/cockatrice/src/client/network/update/client/update_downloader.h
+++ b/cockatrice/src/client/network/update/client/update_downloader.h
@@ -32,13 +32,10 @@ private:
QUrl originalUrl;
QNetworkAccessManager *netMan;
QNetworkReply *response;
- bool errorReported = false;
- bool cancelRequested = false;
private slots:
void fileFinished();
void downloadProgress(qint64 bytesRead, qint64 totalBytes);
void downloadError(QNetworkReply::NetworkError);
- void cancelDownload();
};
#endif // COCKATRICE_UPDATEDOWNLOADER_H
diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp
index b40e26377..08eccf1e4 100644
--- a/cockatrice/src/client/settings/cache_settings.cpp
+++ b/cockatrice/src/client/settings/cache_settings.cpp
@@ -6,7 +6,6 @@
#include "shortcuts_settings.h"
#include
-#include
#include
#include
#include
@@ -48,32 +47,7 @@ Q_GLOBAL_STATIC(SettingsCache, settingsCache)
SettingsCache &SettingsCache::instance()
{
- SettingsCache &cache = *settingsCache; // returns a QT managed singleton reference
- // The release channels own QNetworkAccessManagers, which must be destroyed
- // while the application object is alive. ~SettingsCache() runs during
- // static teardown, after QApplication is gone, so hook the application's
- // shutdown instead. Retried here because the first access may predate the
- // application object.
- cache.installShutdownHook();
- return cache;
-}
-
-void SettingsCache::installShutdownHook()
-{
- if (shutdownHookInstalled) {
- return;
- }
- QCoreApplication *application = QCoreApplication::instance();
- if (application == nullptr) {
- return;
- }
- connect(application, &QCoreApplication::aboutToQuit, this, [this] {
- for (ReleaseChannel *releaseChannel : std::as_const(releaseChannels)) {
- delete releaseChannel;
- }
- releaseChannels.clear();
- });
- shutdownHookInstalled = true;
+ return *settingsCache; // returns a QT managed singleton reference
}
QString SettingsCache::getDataPath()
diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h
index 8ccf6f76b..9aeb88a18 100644
--- a/cockatrice/src/client/settings/cache_settings.h
+++ b/cockatrice/src/client/settings/cache_settings.h
@@ -90,13 +90,8 @@ private:
QString themeName;
QList releaseChannels;
- bool shutdownHookInstalled = false;
bool isPortableBuild;
- // Connects the aboutToQuit handler that deletes the release channels while
- // the application object is still alive; a no-op before it exists.
- void installShutdownHook();
-
[[nodiscard]] QString getSafeConfigPath(QString configEntry, QString defaultPath) const;
[[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const;
void loadPaths();
diff --git a/cockatrice/src/filters/filter_builder.cpp b/cockatrice/src/filters/filter_builder.cpp
index 9c2a91a5b..22679abb1 100644
--- a/cockatrice/src/filters/filter_builder.cpp
+++ b/cockatrice/src/filters/filter_builder.cpp
@@ -1,6 +1,6 @@
#include "filter_builder.h"
-#include "../interface/theme_icon.h"
+#include "../interface/pixel_map_generator.h"
#include "../interface/widgets/utility/custom_line_edit.h"
#include
@@ -30,7 +30,7 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
typeCombo->addItem(CardFilter::typeName(static_cast(i)), QVariant(i));
}
- QPushButton *ok = new QPushButton(loadThemedIcon(QStringLiteral("icons/increment")), QString());
+ QPushButton *ok = new QPushButton(themePixmap(QStringLiteral("icons/increment")), QString());
ok->setObjectName("ok");
ok->setMaximumSize(20, 20);
diff --git a/cockatrice/src/game_graphics/log/message_log_widget.cpp b/cockatrice/src/game_graphics/log/message_log_widget.cpp
index d526766af..8b351e022 100644
--- a/cockatrice/src/game_graphics/log/message_log_widget.cpp
+++ b/cockatrice/src/game_graphics/log/message_log_widget.cpp
@@ -4,8 +4,6 @@
#include "../../client/settings/card_counter_settings.h"
#include "../../client/sound_engine.h"
#include "../../interface/card_localization.h"
-#include "../../interface/theme_icon.h"
-#include "../../interface/theme_manager.h"
#include "../../interface/widgets/server/chat_view/chat_view.h"
#include "../board/card_item.h"
#include "../board/translate_counter_name.h"
@@ -14,12 +12,8 @@
#include
#include
#include
-#include
-#include
#include
#include
-#include
-#include
#include
#include
#include
@@ -126,7 +120,6 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
void MessageLogWidget::containerProcessingDone()
{
currentContext = MessageContext_None;
- contextJudgeName.clear();
messageSuffix = messagePrefix = QString();
}
@@ -862,27 +855,8 @@ void MessageLogWidget::logUndoDrawFailed(PlayerLogic *player)
void MessageLogWidget::setContextJudgeName(QString name)
{
- contextJudgeName = std::move(name);
- refreshJudgeSuffix();
-}
-
-void MessageLogWidget::refreshJudgeSuffix()
-{
- const QPixmap pixmap = loadThemedIcon(QStringLiteral("icons/scales")).pixmap(QSize(24, 24));
- if (!pixmap.isNull()) {
- // Rich text cannot resolve `currentColor`; register a palette-rendered
- // image so already-appended judge tags repaint on a theme switch.
- document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("cockatrice:scales")), pixmap);
- document()->markContentsDirty(0, document()->characterCount());
- }
-
- if (contextJudgeName.isEmpty()) {
- return;
- }
-
- messagePrefix = QStringLiteral("").arg(palette().color(QPalette::WindowText).name());
- messageSuffix =
- QStringLiteral(" [ %1]").arg(sanitizeHtml(contextJudgeName));
+ messagePrefix = QString("");
+ messageSuffix = QString(" [ %1]").arg(sanitizeHtml(name));
}
void MessageLogWidget::appendHtmlServerMessage(const QString &html, bool optionalIsBold, QString optionalFontColor)
@@ -931,6 +905,4 @@ void MessageLogWidget::connectToPlayerEventHandler(PlayerEventHandler *playerEve
MessageLogWidget::MessageLogWidget(TabSupervisor *_tabSupervisor, AbstractGame *_game, QWidget *parent)
: ChatView(_tabSupervisor, _game, true, parent), currentContext(MessageContext_None)
{
- connect(themeManager, &ThemeManager::paletteChanged, this, &MessageLogWidget::refreshJudgeSuffix);
- connect(themeManager, &ThemeManager::themeChanged, this, &MessageLogWidget::refreshJudgeSuffix);
}
diff --git a/cockatrice/src/game_graphics/log/message_log_widget.h b/cockatrice/src/game_graphics/log/message_log_widget.h
index 3ec5ff93c..5632f5af8 100644
--- a/cockatrice/src/game_graphics/log/message_log_widget.h
+++ b/cockatrice/src/game_graphics/log/message_log_widget.h
@@ -39,10 +39,8 @@ private:
MessageContext currentContext;
QString messagePrefix, messageSuffix;
- QString contextJudgeName;
static QPair getFromStr(CardZoneLogic *zone, QString cardName, int position, bool ownerChange);
- void refreshJudgeSuffix();
public:
void connectToPlayerEventHandler(PlayerEventHandler *player);
diff --git a/cockatrice/src/game_graphics/player/player_list_widget.cpp b/cockatrice/src/game_graphics/player/player_list_widget.cpp
index 6895e7db8..7b358010f 100644
--- a/cockatrice/src/game_graphics/player/player_list_widget.cpp
+++ b/cockatrice/src/game_graphics/player/player_list_widget.cpp
@@ -1,7 +1,6 @@
#include "player_list_widget.h"
#include "../../interface/pixel_map_generator.h"
-#include "../../interface/theme_icon.h"
#include "../../interface/widgets/server/user/user_context_menu.h"
#include "libcockatrice/protocol/pb/serverinfo_user.pb.h"
#include "user_level.h"
@@ -68,13 +67,13 @@ PlayerListWidget::PlayerListWidget(TabSupervisor *_tabSupervisor,
QWidget *parent)
: QTreeWidget(parent), tabSupervisor(_tabSupervisor), client(_client), game(_game), gameStarted(false)
{
- readyIcon = loadThemedIcon(QStringLiteral("icons/ready_start"));
- notReadyIcon = loadThemedIcon(QStringLiteral("icons/not_ready_start"));
- concededIcon = loadThemedIcon(QStringLiteral("icons/conceded"));
- playerIcon = loadThemedIcon(QStringLiteral("icons/player"));
- judgeIcon = loadThemedIcon(QStringLiteral("icons/scales"));
- spectatorIcon = loadThemedIcon(QStringLiteral("icons/spectator"));
- lockIcon = loadThemedIcon(QStringLiteral("icons/lock"));
+ readyIcon = themePixmap(QStringLiteral("icons/ready_start"));
+ notReadyIcon = themePixmap(QStringLiteral("icons/not_ready_start"));
+ concededIcon = themePixmap(QStringLiteral("icons/conceded"));
+ playerIcon = loadColorAdjustedPixmap("theme:icons/player");
+ judgeIcon = loadColorAdjustedPixmap("theme:icons/scales");
+ spectatorIcon = loadColorAdjustedPixmap("theme:icons/spectator");
+ lockIcon = themePixmap(QStringLiteral("icons/lock"));
if (tabSupervisor) {
itemDelegate = new PlayerListItemDelegate(this);
diff --git a/cockatrice/src/game_graphics/zones/view_zone_widget.cpp b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp
index 3c6cf9d02..b1ca42ee1 100644
--- a/cockatrice/src/game_graphics/zones/view_zone_widget.cpp
+++ b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp
@@ -3,7 +3,6 @@
#include "../../client/settings/cache_settings.h"
#include "../../filters/syntax_help.h"
#include "../../interface/pixel_map_generator.h"
-#include "../../interface/theme_icon.h"
#include "../card_dimensions.h"
#include "../z_values.h"
#include "libcockatrice/utility/macros.h"
@@ -94,8 +93,8 @@ ZoneViewWidget::ZoneViewWidget(PlayerLogic *_player,
searchEdit.setFocusPolicy(Qt::ClickFocus);
searchEdit.setPlaceholderText(tr("Search by card name (or search expressions)"));
searchEdit.setClearButtonEnabled(true);
- searchEdit.addAction(loadThemedIcon(QStringLiteral("icons/search")), QLineEdit::LeadingPosition);
- auto help = searchEdit.addAction(loadThemedIcon(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
+ searchEdit.addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
+ auto help = searchEdit.addAction(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(&searchEdit); });
diff --git a/cockatrice/src/interface/pixel_map_generator.cpp b/cockatrice/src/interface/pixel_map_generator.cpp
index 037293af9..2f5c4b6dd 100644
--- a/cockatrice/src/interface/pixel_map_generator.cpp
+++ b/cockatrice/src/interface/pixel_map_generator.cpp
@@ -1,7 +1,6 @@
#include "pixel_map_generator.h"
#include "libcockatrice/network/server/remote/user_level.h"
-#include "theme_icon.h"
#include "theme_manager.h"
#include
@@ -16,6 +15,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -436,19 +436,33 @@ QIcon UserLevelPixmapGenerator::generateIconWithColorOverride(int height,
QMap UserLevelPixmapGenerator::iconCache;
-QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
+QPixmap LockPixmapGenerator::generatePixmap(int height)
{
- const QString key = QString::number(expanded) + ":" + QString::number(height);
+
+ int key = height;
if (pmCache.contains(key)) {
return pmCache.value(key);
}
- // Render through the themed icon engine: it keeps the glyph's aspect ratio,
- // follows the palette, and renders at the device pixel ratio instead of
- // handing a DPR-1 bitmap to a label that would upscale and blur it on a 2x
- // display.
- const QString prefix = QStringLiteral("icons/") + (expanded ? "dropdown_expanded" : "dropdown_collapsed");
- const QPixmap pixmap = loadThemedIcon(prefix).pixmap(QSize(height, height), qApp->devicePixelRatio());
+ QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/lock")),
+ QSize(height, height), true);
+ pmCache.insert(key, pixmap);
+ return pixmap;
+}
+
+QMap LockPixmapGenerator::pmCache;
+
+QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
+{
+ QString key = QString::number(expanded) + ":" + QString::number(height);
+ if (pmCache.contains(key)) {
+ return pmCache.value(key);
+ }
+
+ QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed";
+ QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/") + name),
+ QSize(height, height), true);
+
pmCache.insert(key, pixmap);
return pixmap;
}
@@ -506,11 +520,30 @@ QPixmap ManaSymbolPixmapGenerator::generatePixmap(const QString &symbol, const Q
QHash ManaSymbolPixmapGenerator::masterCache;
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();
+ QPixmap result;
+ result.convertFromImage(img);
+ return result;
+ } else {
+ return QPixmap(name);
+ }
+}
+
QPixmap themePixmap(QStringView prefix)
{
const QString resolved = themeManager->assetPath(prefix);
- const QPixmap sentinel = loadSentinelSvgPixmap(resolved);
- return sentinel.isNull() ? QPixmap(QStringLiteral("theme:") + resolved) : sentinel;
+ return QPixmap(QStringLiteral("theme:") + resolved);
}
void clearPixmapGeneratorCaches()
@@ -520,6 +553,7 @@ void clearPixmapGeneratorCaches()
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 951f4231b..7c01bb332 100644
--- a/cockatrice/src/interface/pixel_map_generator.h
+++ b/cockatrice/src/interface/pixel_map_generator.h
@@ -106,6 +106,19 @@ public:
}
};
+class LockPixmapGenerator
+{
+private:
+ static QMap pmCache;
+
+public:
+ static QPixmap generatePixmap(int height);
+ static void clear()
+ {
+ pmCache.clear();
+ }
+};
+
class DropdownIconPixmapGenerator
{
private:
@@ -147,11 +160,13 @@ public:
}
};
-// Returns a "theme:" asset (with no file extension in prefix), preferring the
+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. Token-driven
-// SVGs resolve their `currentColor` sentinel from the palette. Use for
-// scheme-sensitive pixmaps like backgrounds, the card back, and the app logo.
+// 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
diff --git a/cockatrice/src/interface/theme_icon.cpp b/cockatrice/src/interface/theme_icon.cpp
deleted file mode 100644
index 161c436ca..000000000
--- a/cockatrice/src/interface/theme_icon.cpp
+++ /dev/null
@@ -1,300 +0,0 @@
-#include "theme_icon.h"
-
-#include "libcockatrice/utility/svg_recolor.h"
-#include "theme_manager.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-namespace
-{
-/**
- * @brief Palette color that replaces `currentColor`, following the icon mode.
- */
-QByteArray sentinelColor(QIcon::Mode mode, QPalette::ColorRole role)
-{
- const QPalette::ColorGroup group = mode == QIcon::Disabled ? QPalette::Disabled : QPalette::Active;
- const QPalette::ColorRole effective =
- mode == QIcon::Selected && role == QPalette::WindowText ? QPalette::HighlightedText : role;
- return qApp->palette().color(group, effective).name(QColor::HexRgb).toLatin1();
-}
-
-/**
- * @brief Reads an SVG through the `theme:` search path.
- */
-QByteArray readSvg(const QString &resolvedPath)
-{
- QFile file(QStringLiteral("theme:") + resolvedPath);
- if (!file.open(QIODevice::ReadOnly)) {
- return {};
- }
- return file.readAll();
-}
-
-/**
- * @brief Renders SVG bytes, scaled to @p size when one is given.
- *
- * An empty size means the asset's intrinsic size; @p devicePixelRatio only
- * applies in that case so HiDPI callers still get a crisp glyph.
- */
-QPixmap renderSvg(const QByteArray &data, const QSize &size, qreal devicePixelRatio)
-{
- QSvgRenderer renderer(data);
- if (!renderer.isValid()) {
- return {};
- }
-
- QSize renderSize = size;
- if (renderSize.isEmpty()) {
- renderSize = renderer.defaultSize();
- renderSize =
- QSize(qRound(renderSize.width() * devicePixelRatio), qRound(renderSize.height() * devicePixelRatio));
- }
- if (!renderSize.isValid() || renderSize.isEmpty()) {
- return {};
- }
-
- QPixmap pixmap(renderSize);
- pixmap.fill(Qt::transparent);
- QPainter painter(&pixmap);
- renderer.render(&painter);
- return pixmap;
-}
-
-bool hasSentinel(const QByteArray &svg)
-{
- return !svg.isEmpty() && QString::fromUtf8(svg).contains(QStringLiteral("currentColor"), Qt::CaseInsensitive);
-}
-
-QString cacheKey(const QString &path, const QByteArray &variant, const QSize &size, qreal devicePixelRatio)
-{
- return path + QLatin1Char('|') + QString::fromLatin1(variant) + QLatin1Char('|') + QString::number(size.width()) +
- QLatin1Char('x') + QString::number(size.height()) + QLatin1Char('@') + QString::number(devicePixelRatio);
-}
-
-/**
- * @brief QIcon engine rendering a `theme:` SVG with `currentColor` resolved on demand.
- *
- * Qt's SVG renderer has no ambient color, so the sentinel is substituted from
- * the current palette on every pixmap request. Asset resolution, the file bytes
- * and the intrinsic size are cached until the theme changes: a repaint then
- * does no file I/O at all. The render cache is keyed by resolved path and pixel
- * size plus a variant: the palette color for sentinel SVGs, the icon mode for
- * assets that carry their own colors. A palette change therefore produces new
- * keys instead of reusing art built under the previous palette, and the
- * non-sentinel path (raster overrides, plain SVGs) is cached like any other.
- */
-class ThemeSvgIconEngine final : public QObject, public QIconEngine
-{
-public:
- ThemeSvgIconEngine(QString _prefix, QPalette::ColorRole _role) : prefix(std::move(_prefix)), role(_role)
- {
- if (themeManager) {
- // A theme switch changes the resolved asset (scheme-qualified
- // variants, raster overrides) and a palette change invalidates
- // every cached colour, so both drop the caches instead of
- // re-reading theme.cfg and the assets on every repaint.
- connect(themeManager, &ThemeManager::themeChanged, this, &ThemeSvgIconEngine::invalidate);
- connect(themeManager, &ThemeManager::paletteChanged, this, &ThemeSvgIconEngine::invalidate);
- }
- }
-
- QIconEngine *clone() const override
- {
- return new ThemeSvgIconEngine(prefix, role);
- }
-
- void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State) override
- {
- const qreal devicePixelRatio = painter->device() ? painter->device()->devicePixelRatioF() : 1.0;
- const QSize logicalSize = fitToSize(rect.size());
- if (logicalSize.isEmpty()) {
- return;
- }
- const QSize pixelSize(qRound(logicalSize.width() * devicePixelRatio),
- qRound(logicalSize.height() * devicePixelRatio));
- const QPixmap pixmap = render(pixelSize, devicePixelRatio, mode);
- if (!pixmap.isNull()) {
- // QRect::center() would floor and drop the last row/column of a
- // 16x16 glyph; the center has to be fractional so glyphs stay
- // unshifted and unsclipped in item views.
- const QSizeF contentSize = pixmap.deviceIndependentSize();
- const QRectF targetRect(
- QRectF(rect).center() - QPointF(contentSize.width() / 2.0, contentSize.height() / 2.0), contentSize);
- painter->drawPixmap(targetRect, pixmap, QRectF(pixmap.rect()));
- }
- }
-
- QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State) override
- {
- return render(fitToSize(size), 1.0, mode);
- }
-
- QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State, qreal scale) override
- {
- const QSize logicalSize = fitToSize(size);
-#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
- const QSize pixelSize(qRound(logicalSize.width() * scale), qRound(logicalSize.height() * scale));
-#else
- // Qt < 6.8 passes the device-dependent size to the engine.
- const QSize pixelSize = logicalSize;
-#endif
- return render(pixelSize, scale, mode);
- }
-
- QSize actualSize(const QSize &size, QIcon::Mode, QIcon::State) override
- {
- return fitToSize(size);
- }
-
-private:
- void invalidate()
- {
- hasResolvedAsset = false;
- hasIntrinsicSize = false;
- cache.clear();
- }
-
- void ensureResolved() const
- {
- if (hasResolvedAsset && (!themeManager || themeManager->getCurrentThemePath() == resolvedThemePath)) {
- return;
- }
- resolvedThemePath = themeManager ? themeManager->getCurrentThemePath() : QString();
- resolvedPath = themeManager ? themeManager->assetPath(prefix) : QString();
- resolvedAssetIsSvg = resolvedPath.endsWith(QStringLiteral(".svg"), Qt::CaseInsensitive);
- resolvedSvg = resolvedAssetIsSvg ? readSvg(resolvedPath) : QByteArray();
- resolvedHasSentinel = hasSentinel(resolvedSvg);
- hasResolvedAsset = true;
- hasIntrinsicSize = false;
- }
-
- QSize intrinsicSize() const
- {
- ensureResolved();
- if (!hasIntrinsicSize) {
- if (resolvedAssetIsSvg) {
- QSvgRenderer renderer(resolvedSvg);
- intrinsicSizeCache = renderer.isValid() ? renderer.defaultSize() : QSize();
- } else {
- // A theme may override a glyph with a raster; take its real
- // aspect ratio instead of assuming the 24x24 SVG default, so
- // actualSize() and the rendered pixmap agree.
- QImageReader reader(QStringLiteral("theme:") + resolvedPath);
- intrinsicSizeCache = reader.size();
- }
- if (intrinsicSizeCache.isEmpty()) {
- intrinsicSizeCache = QSize(24, 24);
- }
- hasIntrinsicSize = true;
- }
- return intrinsicSizeCache;
- }
-
- QSize fitToSize(const QSize &size) const
- {
- if (size.isEmpty()) {
- return size;
- }
- return intrinsicSize().scaled(size, Qt::KeepAspectRatio);
- }
-
- QPixmap fromAsset(const QSize &pixelSize, QIcon::Mode mode) const
- {
- QPixmap pixmap(QStringLiteral("theme:") + resolvedPath);
- if (!pixelSize.isEmpty() && pixmap.size() != pixelSize) {
- pixmap = pixmap.scaled(pixelSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- }
- if (mode == QIcon::Disabled && !pixmap.isNull()) {
- pixmap = QApplication::style()->generatedIconPixmap(QIcon::Disabled, pixmap, nullptr);
- }
- return pixmap;
- }
-
- QPixmap render(const QSize &pixelSize, qreal devicePixelRatio, QIcon::Mode mode) const
- {
- ensureResolved();
-
- // Same cache for both paths: sentinel art varies by palette color, art
- // with its own colors varies by mode (Disabled is generated). A color
- // variant is never a bare digit, so the two key spaces cannot collide.
- const QByteArray variant =
- resolvedHasSentinel ? sentinelColor(mode, role) : QByteArray::number(static_cast(mode));
- const QString key = cacheKey(resolvedPath, variant, pixelSize, devicePixelRatio);
- const auto cached = cache.constFind(key);
- if (cached != cache.constEnd()) {
- return cached.value();
- }
-
- QPixmap pixmap;
- if (resolvedHasSentinel) {
- pixmap = renderSvg(resolveSvgColorSentinel(resolvedSvg, variant), pixelSize, devicePixelRatio);
- } else {
- // Authored art carries its own colors: use Qt's normal loader so
- // raster overrides and non-SVG assets keep working.
- pixmap = fromAsset(pixelSize, mode);
- }
- if (!pixmap.isNull()) {
- // Set the ratio before caching: assigning it to a cached pixmap
- // afterwards detaches (deep-copies) it on every paint.
- pixmap.setDevicePixelRatio(devicePixelRatio);
- }
- if (cache.size() >= 64) {
- cache.clear();
- }
- cache.insert(key, pixmap);
- return pixmap;
- }
-
- QString prefix;
- QPalette::ColorRole role;
- mutable QHash cache;
- mutable QString resolvedPath;
- mutable QString resolvedThemePath;
- mutable QByteArray resolvedSvg;
- mutable QSize intrinsicSizeCache;
- mutable bool resolvedAssetIsSvg = false;
- mutable bool resolvedHasSentinel = false;
- mutable bool hasResolvedAsset = false;
- mutable bool hasIntrinsicSize = false;
-};
-} // namespace
-
-QIcon loadThemedIcon(QStringView prefix, QPalette::ColorRole role)
-{
- return QIcon(new ThemeSvgIconEngine(prefix.toString(), role));
-}
-
-QPixmap loadSentinelSvgPixmap(const QString &resolvedPath, QPalette::ColorRole role, const QSize &size)
-{
- if (!resolvedPath.endsWith(QStringLiteral(".svg"), Qt::CaseInsensitive)) {
- return {};
- }
-
- const QByteArray svg = readSvg(resolvedPath);
- if (!hasSentinel(svg)) {
- return {};
- }
-
- const QByteArray color = qApp->palette().color(role).name(QColor::HexRgb).toLatin1();
- return renderSvg(resolveSvgColorSentinel(svg, color), size, 1.0);
-}
diff --git a/cockatrice/src/interface/theme_icon.h b/cockatrice/src/interface/theme_icon.h
deleted file mode 100644
index e26015ea1..000000000
--- a/cockatrice/src/interface/theme_icon.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * @file theme_icon.h
- * @ingroup Interface
- */
-
-#ifndef THEME_ICON_H
-#define THEME_ICON_H
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-/**
- * @brief Builds a palette-aware icon from a `theme:` asset.
- *
- * SVG glyphs may use the `currentColor` sentinel. A plain QPixmap snapshot
- * cannot follow palette changes, so this returns a QIcon whose engine resolves
- * the sentinel from the current palette every time it is asked for a pixmap.
- * Icons therefore stay legible when the user switches between light and dark
- * themes without recreating any actions or buttons.
- *
- * @param prefix Asset prefix relative to the `theme:` search path, with no file
- * extension (for example `icons/search`), matching themePixmap().
- * @param role Palette role whose color replaces the sentinel. Selected glyphs
- * use HighlightedText unless a role other than WindowText is given.
- * @return An icon that follows the application palette.
- */
-QIcon loadThemedIcon(QStringView prefix, QPalette::ColorRole role = QPalette::WindowText);
-
-/**
- * @brief Renders a resolved theme SVG when it carries the `currentColor` sentinel.
- *
- * Non-SVG assets and SVGs without the sentinel return a null pixmap so callers
- * can fall back to their usual loading path (including legacy inversion).
- *
- * @param resolvedPath Asset path relative to the `theme:` search path, as
- * returned by ThemeManager::assetPath (includes extension).
- * @param role Palette role whose color replaces the sentinel.
- * @param size Pixel size to render at; an empty size uses the SVG's intrinsic size.
- * @return The recolored pixmap, or a null pixmap when the asset is not a
- * sentinel-carrying SVG.
- */
-QPixmap loadSentinelSvgPixmap(const QString &resolvedPath,
- QPalette::ColorRole role = QPalette::WindowText,
- const QSize &size = {});
-
-#endif // THEME_ICON_H
diff --git a/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp
index c8571ab08..730043d95 100644
--- a/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp
+++ b/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp
@@ -1,6 +1,6 @@
#include "abstract_analytics_panel_widget.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../general/display/banner_widget.h"
#include "deck_list_statistics_analyzer.h"
@@ -28,7 +28,7 @@ AbstractAnalyticsPanelWidget::AbstractAnalyticsPanelWidget(QWidget *parent, Deck
// config button
configureButton = new QPushButton(this);
- configureButton->setIcon(loadThemedIcon(QStringLiteral("icons/settings")));
+ configureButton->setIcon(themePixmap(QStringLiteral("icons/cogwheel")));
configureButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
connect(configureButton, &QPushButton::clicked, this, &AbstractAnalyticsPanelWidget::applyConfigFromDialog);
bannerAndSettingsLayout->addWidget(configureButton, 0);
diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
index f25e6c0ff..7ed398535 100644
--- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
@@ -3,7 +3,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../filters/syntax_help.h"
#include "../../key_signals.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../utility/custom_line_edit.h"
#include "card_database_display_model.h"
#include "card_database_model.h"
@@ -40,8 +40,8 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent
searchEdit->setObjectName("searchEdit");
searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)"));
searchEdit->setClearButtonEnabled(true);
- searchEdit->addAction(loadThemedIcon(QStringLiteral("icons/search")), QLineEdit::LeadingPosition);
- auto help = searchEdit->addAction(loadThemedIcon(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
+ searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
+ auto help = searchEdit->addAction(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
setFocusProxy(searchEdit);
setFocusPolicy(Qt::ClickFocus);
@@ -83,13 +83,13 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent
&DeckEditorDatabaseDisplayWidget::onRelatedCardClicked);
aAddCard = new QAction(QString(), this);
- aAddCard->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_right")));
+ aAddCard->setIcon(themePixmap(QStringLiteral("icons/arrow_right_green")));
connect(aAddCard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
auto *tbAddCard = new QToolButton(this);
tbAddCard->setDefaultAction(aAddCard);
aAddCardToSideboard = new QAction(QString(), this);
- aAddCardToSideboard->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_right_sideboard")));
+ aAddCardToSideboard->setIcon(themePixmap(QStringLiteral("icons/arrow_right_blue")));
connect(aAddCardToSideboard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
auto *tbAddCardToSideboard = new QToolButton(this);
tbAddCardToSideboard->setDefaultAction(aAddCardToSideboard);
diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
index 47d2a1aae..30fde1595 100644
--- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
@@ -3,7 +3,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../key_signals.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../playmat/playmat_settings_dialog.h"
#include "../quick_settings/settings_button_widget.h"
#include "../tabs/abstract_tab_deck_editor.h"
@@ -248,25 +248,25 @@ void DeckEditorDeckDockWidget::createDeckDock()
&DeckEditorDeckDockWidget::applyActiveGroupCriteria);
aIncrement = new QAction(QString(), this);
- aIncrement->setIcon(loadThemedIcon(QStringLiteral("icons/increment")));
+ aIncrement->setIcon(themePixmap(QStringLiteral("icons/increment")));
connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrementSelection);
auto *tbIncrement = new QToolButton(this);
tbIncrement->setDefaultAction(aIncrement);
aDecrement = new QAction(QString(), this);
- aDecrement->setIcon(loadThemedIcon(QStringLiteral("icons/decrement")));
+ aDecrement->setIcon(themePixmap(QStringLiteral("icons/decrement")));
connect(aDecrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actDecrementSelection);
auto *tbDecrement = new QToolButton(this);
tbDecrement->setDefaultAction(aDecrement);
aRemoveCard = new QAction(QString(), this);
- aRemoveCard->setIcon(loadThemedIcon(QStringLiteral("icons/remove_row")));
+ aRemoveCard->setIcon(themePixmap(QStringLiteral("icons/remove_row")));
connect(aRemoveCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actRemoveCard);
auto *tbRemoveCard = new QToolButton(this);
tbRemoveCard->setDefaultAction(aRemoveCard);
aSwapCard = new QAction(QString(), this);
- aSwapCard->setIcon(loadThemedIcon(QStringLiteral("icons/swap")));
+ aSwapCard->setIcon(themePixmap(QStringLiteral("icons/swap")));
connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapSelection);
auto *tbSwapCard = new QToolButton(this);
tbSwapCard->setDefaultAction(aSwapCard);
diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
index 8122d1847..adeb5c3e1 100644
--- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
@@ -5,7 +5,7 @@
#include "../../../filters/filter_builder.h"
#include "../../../filters/filter_tree_model.h"
#include "../../key_signals.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../tabs/abstract_tab_deck_editor.h"
#include "libcockatrice/filters/filter_card.h"
@@ -64,11 +64,11 @@ void DeckEditorFilterDockWidget::createFiltersDock()
connect(filterBuilder, &FilterBuilder::add, filterModel, &FilterTreeModel::addFilter);
aClearFilterOne = new QAction(QString(), this);
- aClearFilterOne->setIcon(loadThemedIcon(QStringLiteral("icons/decrement")));
+ aClearFilterOne->setIcon(themePixmap(QStringLiteral("icons/decrement")));
connect(aClearFilterOne, &QAction::triggered, this, &DeckEditorFilterDockWidget::actClearFilterOne);
aClearFilterAll = new QAction(QString(), this);
- aClearFilterAll->setIcon(loadThemedIcon(QStringLiteral("icons/clearsearch")));
+ aClearFilterAll->setIcon(themePixmap(QStringLiteral("icons/clearsearch")));
connect(aClearFilterAll, &QAction::triggered, this, &DeckEditorFilterDockWidget::actClearFilterAll);
auto *filterDelOne = new QToolButton();
diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
index a1914e1b5..209dc7273 100644
--- a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
@@ -1,6 +1,6 @@
#include "deck_list_history_manager_widget.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../quick_settings/settings_button_widget.h"
#include "deck_state_manager.h"
#include "libcockatrice/deck_list/deck_list_history_manager.h"
@@ -32,7 +32,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_de
layout = new QHBoxLayout(this);
aUndo = new QAction(QString(), this);
- aUndo->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_undo")));
+ aUndo->setIcon(themePixmap(QStringLiteral("icons/arrow_undo")));
aUndo->setShortcut(QKeySequence::Undo);
aUndo->setShortcutContext(Qt::ApplicationShortcut);
connect(aUndo, &QAction::triggered, this, &DeckListHistoryManagerWidget::doUndo);
@@ -41,7 +41,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_de
undoButton->setDefaultAction(aUndo);
aRedo = new QAction(QString(), this);
- aRedo->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_redo")));
+ aRedo->setIcon(themePixmap(QStringLiteral("icons/arrow_redo")));
aRedo->setShortcut(QKeySequence::Redo);
aRedo->setShortcutContext(Qt::ApplicationShortcut);
connect(aRedo, &QAction::triggered, this, &DeckListHistoryManagerWidget::doRedo);
@@ -53,7 +53,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_de
layout->addWidget(redoButton);
historyButton = new SettingsButtonWidget(this);
- historyButton->setButtonIcon(loadThemedIcon(QStringLiteral("icons/arrow_history")));
+ historyButton->setButtonIcon(themePixmap(QStringLiteral("icons/arrow_history")));
historyLabel = new QLabel(this);
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp
index daee5f1f9..853a00828 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp
@@ -1,7 +1,7 @@
#include "dlg_connect.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../server/handle_public_servers.h"
#include "../server/user/user_info_connection.h"
#include "libcockatrice/utility/macros.h"
@@ -38,7 +38,7 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
previousHosts = new QComboBox(this);
btnDeleteServer = new QPushButton(this);
- btnDeleteServer->setIcon(loadThemedIcon(QStringLiteral("icons/remove_row")));
+ btnDeleteServer->setIcon(themePixmap(QStringLiteral("icons/remove_row")));
btnDeleteServer->setToolTip(tr("Delete the currently selected saved server"));
btnDeleteServer->setFixedWidth(30);
@@ -46,7 +46,7 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
hps = new HandlePublicServers(this);
btnRefreshServers = new QPushButton(this);
- btnRefreshServers->setIcon(loadThemedIcon(QStringLiteral("icons/sync")));
+ btnRefreshServers->setIcon(themePixmap(QStringLiteral("icons/sync")));
btnRefreshServers->setToolTip(tr("Refresh the server list with known public servers"));
btnRefreshServers->setFixedWidth(30);
@@ -121,7 +121,7 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
updateDisplayInfo(previousHosts->currentText());
btnForgotPassword = new QPushButton(this);
- btnForgotPassword->setIcon(loadThemedIcon(QStringLiteral("icons/forgot_password")));
+ btnForgotPassword->setIcon(themePixmap(QStringLiteral("icons/forgot_password")));
btnForgotPassword->setToolTip(tr("Reset Password"));
btnForgotPassword->setFixedWidth(30);
connect(btnForgotPassword, &QPushButton::released, this, &DlgConnect::actForgotPassword);
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp
index b06481c4b..3845029f7 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp
@@ -1,6 +1,6 @@
#include "dlg_edit_tokens.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../interface/widgets/utility/get_text_with_max.h"
#include "card_database_display_model.h"
#include "libcockatrice/card/card_info.h"
@@ -108,10 +108,10 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nul
&DlgEditTokens::tokenSelectionChanged);
QAction *aAddToken = new QAction(tr("Add token"), this);
- aAddToken->setIcon(loadThemedIcon(QStringLiteral("icons/increment")));
+ aAddToken->setIcon(themePixmap(QStringLiteral("icons/increment")));
connect(aAddToken, &QAction::triggered, this, &DlgEditTokens::actAddToken);
QAction *aRemoveToken = new QAction(tr("Remove token"), this);
- aRemoveToken->setIcon(loadThemedIcon(QStringLiteral("icons/decrement")));
+ aRemoveToken->setIcon(themePixmap(QStringLiteral("icons/decrement")));
connect(aRemoveToken, &QAction::triggered, this, &DlgEditTokens::actRemoveToken);
auto *databaseToolBar = new QToolBar;
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp
index c6e640a9f..caca5da92 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp
@@ -1,7 +1,7 @@
#include "dlg_manage_sets.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../interface/card_picture_loader/card_picture_loader.h"
#include "../interface/widgets/utility/custom_line_edit.h"
#include "libcockatrice/card/database/card_database.h"
@@ -58,28 +58,28 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
setsEditToolBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
aTop = new QAction(QString(), this);
- aTop->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_to_top")));
+ aTop->setIcon(themePixmap(QStringLiteral("icons/arrow_top_green")));
aTop->setToolTip(tr("Move selected set to the top"));
aTop->setEnabled(false);
connect(aTop, &QAction::triggered, this, &WndSets::actTop);
setsEditToolBar->addAction(aTop);
aUp = new QAction(QString(), this);
- aUp->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_up")));
+ aUp->setIcon(themePixmap(QStringLiteral("icons/arrow_up_green")));
aUp->setToolTip(tr("Move selected set up"));
aUp->setEnabled(false);
connect(aUp, &QAction::triggered, this, &WndSets::actUp);
setsEditToolBar->addAction(aUp);
aDown = new QAction(QString(), this);
- aDown->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_down")));
+ aDown->setIcon(themePixmap(QStringLiteral("icons/arrow_down_green")));
aDown->setToolTip(tr("Move selected set down"));
aDown->setEnabled(false);
connect(aDown, &QAction::triggered, this, &WndSets::actDown);
setsEditToolBar->addAction(aDown);
aBottom = new QAction(QString(), this);
- aBottom->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_to_bottom")));
+ aBottom->setIcon(themePixmap(QStringLiteral("icons/arrow_bottom_green")));
aBottom->setToolTip(tr("Move selected set to the bottom"));
aBottom->setEnabled(false);
connect(aBottom, &QAction::triggered, this, &WndSets::actBottom);
@@ -89,7 +89,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
searchField = new LineEditUnfocusable;
searchField->setObjectName("searchEdit");
searchField->setPlaceholderText(tr("Search by set name, code, type, or release date"));
- searchField->addAction(loadThemedIcon(QStringLiteral("icons/search")), LineEditUnfocusable::LeadingPosition);
+ searchField->addAction(themePixmap(QStringLiteral("icons/search")), LineEditUnfocusable::LeadingPosition);
searchField->setClearButtonEnabled(true);
setFocusProxy(searchField);
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp
index 09afee32d..9e32e87d1 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp
@@ -1,7 +1,7 @@
#include "dlg_register.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../server/handle_public_servers.h"
#include "../server/user/user_info_connection.h"
@@ -34,7 +34,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
previousHosts = new QComboBox(this);
btnDeleteServer = new QPushButton(this);
- btnDeleteServer->setIcon(loadThemedIcon(QStringLiteral("icons/remove_row")));
+ btnDeleteServer->setIcon(themePixmap(QStringLiteral("icons/remove_row")));
btnDeleteServer->setToolTip(tr("Delete the currently selected saved server"));
btnDeleteServer->setFixedWidth(30);
@@ -42,7 +42,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
hps = new HandlePublicServers(this);
btnRefreshServers = new QPushButton(this);
- btnRefreshServers->setIcon(loadThemedIcon(QStringLiteral("icons/sync")));
+ btnRefreshServers->setIcon(themePixmap(QStringLiteral("icons/sync")));
btnRefreshServers->setToolTip(tr("Refresh the server list with known public servers"));
btnRefreshServers->setFixedWidth(30);
diff --git a/cockatrice/src/interface/widgets/dialogs/settings_view.cpp b/cockatrice/src/interface/widgets/dialogs/settings_view.cpp
index b1a80c86b..005824995 100644
--- a/cockatrice/src/interface/widgets/dialogs/settings_view.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/settings_view.cpp
@@ -6,7 +6,7 @@
#include "settings_view.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../interface/theme_manager.h"
#include "../settings_page/abstract_settings_page.h"
#include "../settings_page/appearance_settings_page.h"
@@ -135,7 +135,7 @@ void SettingsView::setupUi()
// Search bar
searchEdit = new QLineEdit;
searchEdit->setClearButtonEnabled(true);
- searchEdit->addAction(loadThemedIcon(QStringLiteral("icons/search")), QLineEdit::LeadingPosition);
+ searchEdit->addAction(themePixmap(QStringLiteral("icons/search")), QLineEdit::LeadingPosition);
searchEdit->installEventFilter(this);
connect(searchEdit, &QLineEdit::textChanged, this, &SettingsView::onSearchTextChanged);
searchEdit->setObjectName("settingsSearchEdit");
diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
index a5177a6bc..94acdbdce 100644
--- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
+++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
@@ -2,8 +2,6 @@
#include "../../../client/settings/cache_settings.h"
#include "../../card_picture_loader/card_picture_loader.h"
-#include "../../theme_icon.h"
-#include "../../theme_manager.h"
#include "../cards/card_info_picture_widget.h"
#include "../tabs/abstract_tab_deck_editor.h"
#include "all_zones_card_amount_widget.h"
@@ -14,19 +12,16 @@
#include
#include
-#include
#include
#include
#include
#include
+#include
#include
#include
#include
#include
#include
-#include
-#include
-#include
#include
#include
#include
@@ -41,6 +36,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -124,12 +120,6 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa
mainLayout->addWidget(cardInfoPicture);
initializePinBadge();
- auto refreshPinBadge = [this] {
- initializePinBadge();
- updatePinBadgeVisibility();
- };
- connect(themeManager, &ThemeManager::paletteChanged, this, refreshPinBadge);
- connect(themeManager, &ThemeManager::themeChanged, this, refreshPinBadge);
// Parent the preview to this overlay so it is destroyed with it (Qt::ToolTip keeps
// it a frameless, non-activating top-level window despite the parent).
@@ -411,55 +401,10 @@ void PrintingSelectorCardOverlayWidget::customMenu(QPoint point)
}
/**
- * @brief Composes the pin badge: the glyph over a rounded palette backdrop.
+ * @brief Initializes the pin badge overlay and loads its icon with DPI-aware rasterization.
*
- * The badge floats on card art rather than on a palette surface, so a bare glyph
- * colored WindowText is not enough: a thin dark stroke disappears on dark art in a
- * light palette, and a thin light stroke disappears on light art in a dark palette.
- * A Window-filled rounded rect with a WindowText outline gives the glyph a solid
- * backing, and the outline guarantees the badge edge contrasts with the art even when
- * the fill matches it. Both roles follow the palette, like the glyph.
- *
- * @param logicalSize The badge's size in logical pixels.
- * @param devicePixelRatio The label's device pixel ratio, applied to the pixmap so it
- * stays crisp on HiDPI displays.
- * @param palette The palette the glyph's WindowText resolves against.
- */
-static QPixmap composePinBadgePixmap(const QSize &logicalSize, qreal devicePixelRatio, const QPalette &palette)
-{
- // Inset the glyph so the backdrop reads as a badge around it, not as a border.
- const int inset = qRound(logicalSize.width() * 0.16);
- const QSize glyphSize = logicalSize - QSize(inset * 2, inset * 2);
- const QPixmap glyph = loadThemedIcon(QStringLiteral("icons/pin")).pixmap(glyphSize, devicePixelRatio);
- if (glyph.isNull()) {
- // Let the caller fall back to the textual badge instead of drawing an empty panel.
- return QPixmap();
- }
-
- QPixmap badge(logicalSize * devicePixelRatio);
- badge.setDevicePixelRatio(devicePixelRatio);
- badge.fill(Qt::transparent);
-
- QPainter painter(&badge);
- painter.setRenderHint(QPainter::Antialiasing);
-
- // Half-pixel inset keeps the 1.5px outline inside the pixmap instead of clipping it.
- const QRectF box(QPointF(0.5, 0.5), QSizeF(logicalSize) - QSizeF(1, 1));
- painter.setPen(QPen(palette.color(QPalette::WindowText), 1.5));
- painter.setBrush(palette.color(QPalette::Window));
- painter.drawRoundedRect(box, 8, 8);
- painter.drawPixmap(QPointF(inset, inset), glyph);
-
- return badge;
-}
-
-/**
- * @brief Initializes the pin badge overlay and loads a DPI-aware icon pixmap.
- *
- * The glyph is drawn onto a rounded palette backdrop so the badge stays readable on
- * card art, and it is rasterized at the label's device pixel ratio so it stays crisp on
- * HiDPI displays. It is re-rendered when the palette or theme changes so the badge
- * follows the surrounding UI instead of freezing at its first colors.
+ * The icon is rasterized once using the label's device pixel ratio so it stays crisp on HiDPI
+ * displays. The resulting pixmap is cached on the QLabel and simply shown/hidden when needed.
* If the SVG cannot be read, a textual fallback badge is created instead.
*/
void PrintingSelectorCardOverlayWidget::initializePinBadge()
@@ -475,13 +420,24 @@ void PrintingSelectorCardOverlayWidget::initializePinBadge()
}
bool pinLoaded = false;
- const QSize targetSize(64, 64);
- const QPixmap pinPix = composePinBadgePixmap(targetSize, pinBadge->devicePixelRatioF(), pinBadge->palette());
- if (!pinPix.isNull()) {
- pinBadge->setPixmap(pinPix);
- pinBadge->setFixedSize(targetSize);
- pinBadge->setStyleSheet(QStringLiteral("background: transparent;"));
- pinLoaded = true;
+ QImageReader pinReader(QStringLiteral("theme:icons/pin"));
+
+ if (pinReader.canRead()) {
+ // Rasterize a 64×64 logical icon so it has a consistent size regardless of card scaling.
+ const QSize targetSize(64, 64);
+ const qreal dpr = pinBadge->devicePixelRatioF();
+ const QSize rasterSize(qMax(1, qCeil(targetSize.width() * dpr)), qMax(1, qCeil(targetSize.height() * dpr)));
+ pinReader.setScaledSize(rasterSize);
+ const QImage pinImage = pinReader.read();
+ if (!pinImage.isNull()) {
+ // Tag the pixmap with the same DPR so Qt renders it at the correct physical size.
+ QPixmap pinPix = QPixmap::fromImage(pinImage);
+ pinPix.setDevicePixelRatio(dpr);
+ pinBadge->setPixmap(pinPix);
+ pinBadge->setFixedSize(targetSize);
+ pinBadge->setStyleSheet(QStringLiteral("background: transparent;"));
+ pinLoaded = true;
+ }
}
if (!pinLoaded) {
diff --git a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp
index c6a6d989f..ea0d2db5a 100644
--- a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp
+++ b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp
@@ -1,12 +1,12 @@
#include "settings_button_widget.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "settings_popup_widget.h"
#include
#include
-#include
#include
+#include
#include
#include
#include
@@ -21,7 +21,7 @@
SettingsButtonWidget::SettingsButtonWidget(QWidget *parent)
: QWidget(parent), button(new QToolButton(this)), popup(new SettingsPopupWidget(nullptr))
{
- button->setIcon(loadThemedIcon(QStringLiteral("icons/settings")));
+ button->setIcon(themePixmap(QStringLiteral("icons/cogwheel")));
button->setCheckable(true);
button->setFixedSize(32, 32);
connect(button, &QToolButton::clicked, this, &SettingsButtonWidget::togglePopup);
@@ -49,9 +49,9 @@ void SettingsButtonWidget::removeSettingsWidget(QWidget *toRemove) const
popup->removeSettingsWidget(toRemove);
}
-void SettingsButtonWidget::setButtonIcon(const QIcon &icon)
+void SettingsButtonWidget::setButtonIcon(QPixmap iconMap)
{
- button->setIcon(icon);
+ button->setIcon(iconMap);
}
void SettingsButtonWidget::setButtonText(const QString &text)
diff --git a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h
index 56d542d37..015300bf8 100644
--- a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h
+++ b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h
@@ -13,7 +13,7 @@
#include
class QHBoxLayout;
-class QIcon;
+class QPixmap;
class QToolButton;
class SettingsPopupWidget;
@@ -28,7 +28,7 @@ public:
void addSettingsWidget(QWidget *toAdd) const;
void removeSettingsWidget(QWidget *toRemove) const;
- void setButtonIcon(const QIcon &icon);
+ void setButtonIcon(QPixmap iconMap);
void setButtonText(const QString &buttonText);
void setCompact(bool compact);
bool isCompact() const
diff --git a/cockatrice/src/interface/widgets/replay/replay_event_index.cpp b/cockatrice/src/interface/widgets/replay/replay_event_index.cpp
deleted file mode 100644
index d21671b5a..000000000
--- a/cockatrice/src/interface/widgets/replay/replay_event_index.cpp
+++ /dev/null
@@ -1,876 +0,0 @@
-#include "replay_event_index.h"
-
-#include "libcockatrice/protocol/get_pb_extension.h"
-#include "libcockatrice/protocol/pb/card_attributes.pb.h"
-#include "libcockatrice/protocol/pb/context_concede.pb.h"
-#include "libcockatrice/protocol/pb/context_connection_state_changed.pb.h"
-#include "libcockatrice/protocol/pb/context_deck_select.pb.h"
-#include "libcockatrice/protocol/pb/context_mulligan.pb.h"
-#include "libcockatrice/protocol/pb/context_ready_start.pb.h"
-#include "libcockatrice/protocol/pb/context_set_sideboard_lock.pb.h"
-#include "libcockatrice/protocol/pb/context_undo_draw.pb.h"
-#include "libcockatrice/protocol/pb/event_attach_card.pb.h"
-#include "libcockatrice/protocol/pb/event_change_zone_properties.pb.h"
-#include "libcockatrice/protocol/pb/event_create_counter.pb.h"
-#include "libcockatrice/protocol/pb/event_create_token.pb.h"
-#include "libcockatrice/protocol/pb/event_del_counter.pb.h"
-#include "libcockatrice/protocol/pb/event_destroy_card.pb.h"
-#include "libcockatrice/protocol/pb/event_draw_cards.pb.h"
-#include "libcockatrice/protocol/pb/event_dump_zone.pb.h"
-#include "libcockatrice/protocol/pb/event_flip_card.pb.h"
-#include "libcockatrice/protocol/pb/event_game_closed.pb.h"
-#include "libcockatrice/protocol/pb/event_game_host_changed.pb.h"
-#include "libcockatrice/protocol/pb/event_game_log_notice.pb.h"
-#include "libcockatrice/protocol/pb/event_game_say.pb.h"
-#include "libcockatrice/protocol/pb/event_game_state_changed.pb.h"
-#include "libcockatrice/protocol/pb/event_join.pb.h"
-#include "libcockatrice/protocol/pb/event_kicked.pb.h"
-#include "libcockatrice/protocol/pb/event_leave.pb.h"
-#include "libcockatrice/protocol/pb/event_move_card.pb.h"
-#include "libcockatrice/protocol/pb/event_player_properties_changed.pb.h"
-#include "libcockatrice/protocol/pb/event_reveal_cards.pb.h"
-#include "libcockatrice/protocol/pb/event_reverse_turn.pb.h"
-#include "libcockatrice/protocol/pb/event_roll_die.pb.h"
-#include "libcockatrice/protocol/pb/event_set_active_phase.pb.h"
-#include "libcockatrice/protocol/pb/event_set_active_player.pb.h"
-#include "libcockatrice/protocol/pb/event_set_card_attr.pb.h"
-#include "libcockatrice/protocol/pb/event_set_card_counter.pb.h"
-#include "libcockatrice/protocol/pb/event_set_counter.pb.h"
-#include "libcockatrice/protocol/pb/event_shuffle.pb.h"
-#include "libcockatrice/protocol/pb/game_event.pb.h"
-#include "libcockatrice/protocol/pb/game_event_container.pb.h"
-#include "libcockatrice/protocol/pb/game_event_context.pb.h"
-#include "libcockatrice/protocol/pb/game_replay.pb.h"
-#include "libcockatrice/protocol/pb/serverinfo_card.pb.h"
-#include "libcockatrice/protocol/pb/serverinfo_counter.pb.h"
-#include "libcockatrice/protocol/pb/serverinfo_player.pb.h"
-#include "libcockatrice/protocol/pb/serverinfo_playerproperties.pb.h"
-#include "libcockatrice/protocol/pb/serverinfo_zone.pb.h"
-#include "libcockatrice/utility/zone_names.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-namespace
-{
-struct PendingDetail
-{
- QString text;
- QString badge;
- ReplayEventKind kind = ReplayEventKind::Action;
- QString actor;
-};
-
-QString formatTime(int timeMs)
-{
- const int totalSeconds = qMax(0, timeMs) / 1000;
- const int hours = totalSeconds / 3600;
- const int minutes = (totalSeconds / 60) % 60;
- const int seconds = totalSeconds % 60;
-
- if (hours > 0) {
- return QStringLiteral("%1:%2:%3")
- .arg(hours)
- .arg(minutes, 2, 10, QLatin1Char('0'))
- .arg(seconds, 2, 10, QLatin1Char('0'));
- }
-
- return QStringLiteral("%1:%2").arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
-}
-
-class ReplayEventIndexBuilder
-{
- Q_DECLARE_TR_FUNCTIONS(ReplayEventIndex)
-
-public:
- ReplayEventIndexBuilder(const GameReplay *replay, const QList &timeline) : replay(replay), timeline(timeline)
- {
- }
-
- QList build()
- {
- QList entries;
-
- for (int i = 0; i < replay->event_list_size(); ++i) {
- const GameEventContainer &container = replay->event_list(i);
- const QList details = describeContainer(container);
- if (details.isEmpty()) {
- continue;
- }
-
- ReplayEventEntry entry;
- entry.eventIndex = i;
- entry.timeMs = timeline.value(i, static_cast(container.seconds_elapsed()) * 1000);
- entry.timeLabel = formatTime(entry.timeMs);
- entry.kind = details.first().kind;
- entry.badge = details.first().badge;
- if (details.size() > 1) {
- entry.badge += QStringLiteral(" +%1").arg(details.size() - 1);
- }
-
- const QString sharedActor = details.first().actor;
- const bool mixedActors = std::any_of(details.cbegin(), details.cend(), [&sharedActor](const auto &detail) {
- return detail.actor != sharedActor;
- });
-
- QStringList detailTexts;
- for (const PendingDetail &detail : details) {
- if (mixedActors && !detail.actor.isEmpty()) {
- detailTexts.append(tr("%1: %2").arg(detail.actor, detail.text));
- } else {
- detailTexts.append(detail.text);
- }
- }
- entry.actor = mixedActors ? QString() : sharedActor;
- entry.detail = detailTexts.join(QStringLiteral(" • "));
-
- entries.append(entry);
- }
-
- return entries;
- }
-
-private:
- const GameReplay *replay;
- const QList &timeline;
-
- QHash playerNames;
- QHash spectators;
- QHash cardNames;
- QHash counterNames;
-
- int activePlayerId = -1;
- bool gameStarted = false;
- bool sawGameState = false;
-
- static QString cardKey(int playerId, const QString &zone, int cardId)
- {
- return QStringLiteral("%1/%2/%3").arg(playerId).arg(zone).arg(cardId);
- }
-
- static QString counterKey(int playerId, int counterId)
- {
- return QStringLiteral("%1/%2").arg(playerId).arg(counterId);
- }
-
- QString playerName(int playerId) const
- {
- if (playerId < 0) {
- return tr("Game");
- }
- return playerNames.value(playerId, tr("Player %1").arg(playerId));
- }
-
- QString actorName(int playerId) const
- {
- if (playerId < 0) {
- return QString();
- }
- return playerNames.value(playerId, tr("Player %1").arg(playerId));
- }
-
- bool isSpectator(int playerId) const
- {
- return spectators.value(playerId, false);
- }
-
- void rememberPlayer(const ServerInfo_PlayerProperties &properties)
- {
- const int playerId = properties.player_id();
- if (playerId < 0) {
- return;
- }
-
- const QString name = QString::fromStdString(properties.user_info().name());
- if (!name.isEmpty()) {
- playerNames.insert(playerId, name);
- }
- spectators.insert(playerId, properties.spectator());
- }
-
- void rememberCard(int playerId, const QString &zone, int cardId, const QString &name)
- {
- if (cardId >= 0 && !name.isEmpty()) {
- cardNames.insert(cardKey(playerId, zone, cardId), name);
- }
- }
-
- void forgetCard(int playerId, const QString &zone, int cardId)
- {
- if (cardId >= 0) {
- cardNames.remove(cardKey(playerId, zone, cardId));
- }
- }
-
- QString cardName(int playerId, const QString &zone, int cardId) const
- {
- if (cardId < 0) {
- return QString();
- }
- return cardNames.value(cardKey(playerId, zone, cardId));
- }
-
- void rememberCounter(int playerId, const ServerInfo_Counter &counter)
- {
- const QString name = QString::fromStdString(counter.name());
- if (!name.isEmpty()) {
- counterNames.insert(counterKey(playerId, counter.id()), name);
- }
- }
-
- QString counterName(int playerId, int counterId) const
- {
- return counterNames.value(counterKey(playerId, counterId));
- }
-
- QString zoneName(const QString &zone) const
- {
- if (zone == ZoneNames::TABLE) {
- return tr("battlefield");
- }
- if (zone == ZoneNames::GRAVE) {
- return tr("graveyard");
- }
- if (zone == ZoneNames::EXILE) {
- return tr("exile");
- }
- if (zone == ZoneNames::HAND) {
- return tr("hand");
- }
- if (zone == ZoneNames::DECK) {
- return tr("library");
- }
- if (zone == ZoneNames::SIDEBOARD) {
- return tr("sideboard");
- }
- if (zone == ZoneNames::STACK) {
- return tr("stack");
- }
- return zone;
- }
-
- QString zonePhrase(int playerId, const QString &zone, int actorId) const
- {
- if (zone == ZoneNames::TABLE) {
- return tr("the battlefield");
- }
- if (zone == ZoneNames::EXILE) {
- return tr("exile");
- }
- if (zone == ZoneNames::STACK) {
- return tr("the stack");
- }
-
- const QString name = zoneName(zone);
- if (playerId < 0 || playerId == actorId) {
- return tr("their %1").arg(name);
- }
- return tr("%1's %2").arg(playerName(playerId), name);
- }
-
- QString formatCardList(const QStringList &names, int total) const
- {
- // Keep repeats: each entry is a drawn/revealed card, so the list length must agree with the count.
- QStringList listedNames;
- for (const QString &name : names) {
- if (!name.isEmpty()) {
- listedNames.append(name);
- }
- }
-
- if (listedNames.isEmpty()) {
- if (total <= 0) {
- return tr("no cards");
- }
- return total == 1 ? tr("1 card") : tr("%1 cards").arg(total);
- }
-
- if (total < listedNames.size()) {
- total = listedNames.size();
- }
-
- if (listedNames.size() > 3) {
- return tr("%1 and %2 more").arg(listedNames.mid(0, 3).join(QStringLiteral(", "))).arg(total - 3);
- }
- if (total > listedNames.size()) {
- return tr("%1 and %2 more").arg(listedNames.join(QStringLiteral(", "))).arg(total - listedNames.size());
- }
-
- return listedNames.join(QStringLiteral(", "));
- }
-
- void updateGameState(const Event_GameStateChanged &event)
- {
- for (int i = 0; i < event.player_list_size(); ++i) {
- const ServerInfo_Player &player = event.player_list(i);
- const int playerId = player.properties().player_id();
- rememberPlayer(player.properties());
-
- for (int j = 0; j < player.zone_list_size(); ++j) {
- const ServerInfo_Zone &zone = player.zone_list(j);
- for (int k = 0; k < zone.card_list_size(); ++k) {
- const ServerInfo_Card &card = zone.card_list(k);
- rememberCard(playerId, QString::fromStdString(zone.name()), card.id(),
- QString::fromStdString(card.name()));
- }
- }
-
- for (int j = 0; j < player.counter_list_size(); ++j) {
- rememberCounter(playerId, player.counter_list(j));
- }
- }
-
- if (event.has_active_player_id()) {
- activePlayerId = event.active_player_id();
- }
- if (event.has_game_started()) {
- gameStarted = event.game_started();
- }
- }
-
- QString describeMoveCard(const Event_MoveCard &event, int actorId, const GameEventContext &context)
- {
- const int startPlayerId = event.start_player_id() >= 0 ? event.start_player_id() : actorId;
- const int targetPlayerId = event.target_player_id() >= 0 ? event.target_player_id() : startPlayerId;
- const QString startZone = QString::fromStdString(event.start_zone());
- const QString targetZone = event.has_target_zone() ? QString::fromStdString(event.target_zone()) : startZone;
-
- QString name;
- if (event.has_card_name()) {
- name = QString::fromStdString(event.card_name());
- }
- if (name.isEmpty()) {
- name = cardName(startPlayerId, startZone, event.card_id());
- }
-
- const bool knownName = !name.isEmpty();
- if (name.isEmpty()) {
- name = event.face_down() ? tr("a face-down card") : tr("a card");
- }
-
- const int newCardId = event.has_new_card_id() ? event.new_card_id() : event.card_id();
- forgetCard(startPlayerId, startZone, event.card_id());
- if (knownName) {
- rememberCard(targetPlayerId, targetZone, newCardId, name);
- }
-
- if (startZone == targetZone) {
- return tr("moved %1 within %2.").arg(name, zonePhrase(targetPlayerId, targetZone, actorId));
- }
- if (context.HasExtension(Context_UndoDraw::ext)) {
- return tr("returned %1 to %2.").arg(name, zonePhrase(targetPlayerId, targetZone, actorId));
- }
-
- return tr("moved %1 from %2 to %3.")
- .arg(name, zonePhrase(startPlayerId, startZone, actorId), zonePhrase(targetPlayerId, targetZone, actorId));
- }
-
- QString describeSetCardAttribute(const Event_SetCardAttr &event, int actorId)
- {
- const QString zone = QString::fromStdString(event.zone_name());
- QString name;
- if (event.has_card_id()) {
- name = cardName(actorId, zone, event.card_id());
- }
- if (name.isEmpty()) {
- name = event.has_card_id() ? tr("a card") : tr("all cards");
- }
-
- const QString value = QString::fromStdString(event.attr_value());
- switch (event.attribute()) {
- case AttrTapped:
- return value == QLatin1String("1") ? tr("tapped %1.").arg(name) : tr("untapped %1.").arg(name);
- case AttrAttacking:
- return value == QLatin1String("1") ? tr("%1 is now attacking.").arg(name)
- : tr("%1 is no longer attacking.").arg(name);
- case AttrFaceDown:
- return value == QLatin1String("1") ? tr("turned %1 face down.").arg(name)
- : tr("turned %1 face up.").arg(name);
- case AttrColor:
- return tr("changed %1's color to %2.").arg(name, value);
- case AttrPT:
- return tr("set %1's power/toughness to %2.").arg(name, value);
- case AttrAnnotation:
- return value.isEmpty() ? tr("cleared %1's annotation.").arg(name)
- : tr("annotated %1: %2").arg(name, value);
- case AttrDoesntUntap:
- return value == QLatin1String("1") ? tr("%1 will no longer untap.").arg(name)
- : tr("%1 will untap normally again.").arg(name);
- }
-
- return tr("changed %1.").arg(name);
- }
-
- QString badgeForCardAttribute(CardAttribute attribute) const
- {
- switch (attribute) {
- case AttrTapped:
- return tr("TAP");
- case AttrAttacking:
- return tr("ATTACK");
- case AttrFaceDown:
- return tr("FLIP");
- case AttrColor:
- return tr("COLOR");
- case AttrPT:
- return tr("PT");
- case AttrAnnotation:
- return tr("NOTE");
- case AttrDoesntUntap:
- return tr("TAP");
- }
- return tr("CARD");
- }
-
- QString describeLeaveReason(Event_Leave::LeaveReason reason) const
- {
- switch (reason) {
- case Event_Leave::USER_KICKED:
- return tr("kicked by game host or moderator");
- case Event_Leave::USER_LEFT:
- return tr("left the game");
- case Event_Leave::USER_DISCONNECTED:
- return tr("disconnected");
- case Event_Leave::OTHER:
- default:
- return tr("reason unknown");
- }
- }
-
- void addDetail(QList &details,
- const QString &text,
- const QString &badge,
- ReplayEventKind kind,
- int actorId,
- bool forcedByJudge) const
- {
- if (text.isEmpty()) {
- return;
- }
-
- const QString finalText = forcedByJudge ? tr("%1 (judge)").arg(text) : text;
- details.append(PendingDetail{.text = finalText, .badge = badge, .kind = kind, .actor = actorName(actorId)});
- }
-
- QList describeContainer(const GameEventContainer &container)
- {
- QList details;
- const GameEventContext &context = container.context();
- const bool forcedByJudge = container.has_forced_by_judge();
- const auto contextType = static_cast(getPbExtension(context));
-
- for (int i = 0; i < container.event_list_size(); ++i) {
- const GameEvent &event = container.event_list(i);
- const int eventPlayerId = event.player_id();
- const auto eventType = static_cast(getPbExtension(event));
-
- switch (eventType) {
- case GameEvent::GAME_STATE_CHANGED: {
- const Event_GameStateChanged &state = event.GetExtension(Event_GameStateChanged::ext);
- const bool wasStarted = gameStarted;
- updateGameState(state);
- if (state.game_started() && !wasStarted) {
- addDetail(details, tr("Game started."), tr("GAME"), ReplayEventKind::System, -1, forcedByJudge);
- } else if (!sawGameState) {
- addDetail(details, tr("Game state loaded."), tr("GAME"), ReplayEventKind::System, -1,
- forcedByJudge);
- }
- sawGameState = true;
- break;
- }
- case GameEvent::PLAYER_PROPERTIES_CHANGED: {
- const Event_PlayerPropertiesChanged &propertiesEvent =
- event.GetExtension(Event_PlayerPropertiesChanged::ext);
- const bool hasProperties = propertiesEvent.has_player_properties();
- const ServerInfo_PlayerProperties &properties = propertiesEvent.player_properties();
- // The server only fills in the field that changed, so player_id is absent from these events
- // unless they are about a specific player; fall back to the event's own player id.
- const bool describesPlayer = hasProperties && properties.has_player_id();
- const int playerId = describesPlayer ? properties.player_id() : eventPlayerId;
- if (describesPlayer) {
- rememberPlayer(properties);
- }
-
- switch (contextType) {
- case GameEventContext::READY_START:
- addDetail(details,
- properties.ready_start() ? tr("is ready to start.") : tr("is no longer ready."),
- tr("READY"), ReplayEventKind::System, playerId, forcedByJudge);
- break;
- case GameEventContext::CONCEDE:
- addDetail(details, tr("conceded the game."), tr("CONCEDE"), ReplayEventKind::System,
- playerId, forcedByJudge);
- break;
- case GameEventContext::UNCONCEDE:
- addDetail(details, tr("unconceded the game."), tr("CONCEDE"), ReplayEventKind::System,
- playerId, forcedByJudge);
- break;
- case GameEventContext::DECK_SELECT: {
- const Context_DeckSelect &deckSelect = context.GetExtension(Context_DeckSelect::ext);
- const int sideboardSize = deckSelect.sideboard_size();
- addDetail(details,
- sideboardSize >= 0
- ? tr("selected a deck with %1 sideboard cards.").arg(sideboardSize)
- : tr("selected a deck."),
- tr("DECK"), ReplayEventKind::System, playerId, forcedByJudge);
- break;
- }
- case GameEventContext::MULLIGAN: {
- const Context_Mulligan &mulligan = context.GetExtension(Context_Mulligan::ext);
- addDetail(details,
- mulligan.number() > 0 ? tr("took a mulligan (%1).").arg(mulligan.number())
- : tr("took a mulligan."),
- tr("MULLIGAN"), ReplayEventKind::System, playerId, forcedByJudge);
- break;
- }
- case GameEventContext::SET_SIDEBOARD_LOCK:
- addDetail(details,
- properties.sideboard_locked() ? tr("locked their sideboard.")
- : tr("unlocked their sideboard."),
- tr("SIDEBOARD"), ReplayEventKind::System, playerId, forcedByJudge);
- break;
- case GameEventContext::CONNECTION_STATE_CHANGED:
- addDetail(details,
- properties.ping_seconds() != -1 ? tr("reconnected to the game.")
- : tr("lost connection to the game."),
- tr("CONNECTION"), ReplayEventKind::System, playerId, forcedByJudge);
- break;
- default:
- break;
- }
- break;
- }
- case GameEvent::JOIN: {
- const Event_Join &join = event.GetExtension(Event_Join::ext);
- if (join.has_player_properties()) {
- rememberPlayer(join.player_properties());
- }
- const int playerId = join.player_properties().player_id() >= 0
- ? join.player_properties().player_id()
- : eventPlayerId;
- addDetail(details, isSpectator(playerId) ? tr("joined as a spectator.") : tr("joined the game."),
- tr("JOIN"), ReplayEventKind::System, playerId, forcedByJudge);
- break;
- }
- case GameEvent::LEAVE: {
- const Event_Leave &leave = event.GetExtension(Event_Leave::ext);
- const QString reason = describeLeaveReason(leave.reason());
- if (eventPlayerId < 0) {
- addDetail(details, tr("A player left the game (%1).").arg(reason), tr("LEAVE"),
- ReplayEventKind::System, -1, forcedByJudge);
- } else {
- addDetail(details,
- isSpectator(eventPlayerId) ? tr("stopped spectating (%1).").arg(reason)
- : tr("left the game (%1).").arg(reason),
- tr("LEAVE"), ReplayEventKind::System, eventPlayerId, forcedByJudge);
- }
- break;
- }
- case GameEvent::KICKED:
- addDetail(details,
- eventPlayerId >= 0 ? tr("was kicked from the game.")
- : tr("A player was kicked from the game."),
- tr("KICK"), ReplayEventKind::System, eventPlayerId, forcedByJudge);
- break;
- case GameEvent::GAME_CLOSED:
- addDetail(details, tr("Game closed."), tr("GAME"), ReplayEventKind::System, -1, forcedByJudge);
- break;
- case GameEvent::GAME_HOST_CHANGED:
- addDetail(details, eventPlayerId >= 0 ? tr("is now the game host.") : tr("The game host changed."),
- tr("HOST"), ReplayEventKind::System, eventPlayerId, forcedByJudge);
- break;
- case GameEvent::GAME_SAY: {
- const Event_GameSay &say = event.GetExtension(Event_GameSay::ext);
- addDetail(details, QString::fromStdString(say.message()), tr("CHAT"), ReplayEventKind::Chat,
- eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::SET_ACTIVE_PLAYER: {
- const Event_SetActivePlayer &setActivePlayer = event.GetExtension(Event_SetActivePlayer::ext);
- activePlayerId = setActivePlayer.active_player_id();
- if (activePlayerId >= 0) {
- addDetail(details, tr("begins their turn."), tr("TURN"), ReplayEventKind::Turn, activePlayerId,
- forcedByJudge);
- }
- break;
- }
- case GameEvent::SET_ACTIVE_PHASE: {
- const Event_SetActivePhase &setActivePhase = event.GetExtension(Event_SetActivePhase::ext);
- if (setActivePhase.phase() >= 0) {
- const QString phaseName = Phases::getPhase(setActivePhase.phase()).getName();
- addDetail(details, tr("entered the %1 phase.").arg(phaseName), tr("PHASE"),
- ReplayEventKind::Phase, activePlayerId >= 0 ? activePlayerId : eventPlayerId,
- forcedByJudge);
- }
- break;
- }
- case GameEvent::REVERSE_TURN: {
- const Event_ReverseTurn &reverseTurn = event.GetExtension(Event_ReverseTurn::ext);
- addDetail(details,
- reverseTurn.reversed() ? tr("reversed the turn order.") : tr("restored the turn order."),
- tr("TURN"), ReplayEventKind::Turn, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::MOVE_CARD: {
- const Event_MoveCard &moveCard = event.GetExtension(Event_MoveCard::ext);
- const QString text = describeMoveCard(moveCard, eventPlayerId, context);
- const QString startZone = QString::fromStdString(moveCard.start_zone());
- const QString targetZone = QString::fromStdString(moveCard.target_zone());
- QString badge = tr("MOVE");
- if (context.HasExtension(Context_UndoDraw::ext)) {
- badge = tr("UNDO");
- } else if (startZone == ZoneNames::STACK) {
- badge = tr("RESOLVE");
- } else if (startZone == ZoneNames::HAND && targetZone == ZoneNames::TABLE) {
- badge = tr("PLAY");
- }
- addDetail(details, text, badge, ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::DRAW_CARDS: {
- const Event_DrawCards &drawCards = event.GetExtension(Event_DrawCards::ext);
- QStringList names;
- for (int j = 0; j < drawCards.cards_size(); ++j) {
- const ServerInfo_Card &card = drawCards.cards(j);
- const QString name = QString::fromStdString(card.name());
- names.append(name);
- rememberCard(eventPlayerId, ZoneNames::HAND, card.id(), name);
- }
- if (drawCards.number() > 0) {
- addDetail(details, tr("drew %1.").arg(formatCardList(names, drawCards.number())), tr("DRAW"),
- ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- }
- break;
- }
- case GameEvent::REVEAL_CARDS: {
- const Event_RevealCards &revealCards = event.GetExtension(Event_RevealCards::ext);
- const QString zone = QString::fromStdString(revealCards.zone_name());
- QStringList names;
- for (int j = 0; j < revealCards.cards_size(); ++j) {
- const ServerInfo_Card &card = revealCards.cards(j);
- const QString name = QString::fromStdString(card.name());
- names.append(name);
- rememberCard(eventPlayerId, zone, card.id(), name);
- }
- const int total =
- revealCards.number_of_cards() > 0 ? revealCards.number_of_cards() : revealCards.cards_size();
- QString text =
- tr("revealed %1 from %2.")
- .arg(formatCardList(names, total), zonePhrase(eventPlayerId, zone, eventPlayerId));
- if (revealCards.other_player_id() >= 0 && revealCards.other_player_id() != eventPlayerId) {
- text = tr("revealed %1 from %2 to %3.")
- .arg(formatCardList(names, total), zonePhrase(eventPlayerId, zone, eventPlayerId),
- playerName(revealCards.other_player_id()));
- }
- addDetail(details, text, tr("REVEAL"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::SHUFFLE: {
- const Event_Shuffle &shuffle = event.GetExtension(Event_Shuffle::ext);
- addDetail(
- details,
- tr("shuffled %1.")
- .arg(zonePhrase(eventPlayerId, QString::fromStdString(shuffle.zone_name()), eventPlayerId)),
- tr("SHUFFLE"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::ROLL_DIE: {
- const Event_RollDie &rollDie = event.GetExtension(Event_RollDie::ext);
- QStringList rolls;
- for (int j = 0; j < rollDie.values_size(); ++j) {
- rolls.append(QString::number(rollDie.values(j)));
- }
- if (rolls.isEmpty() && rollDie.value() > 0) {
- rolls.append(QString::number(rollDie.value()));
- }
- const QString sides = rollDie.sides() > 0 ? QString::number(rollDie.sides()) : QStringLiteral("?");
- addDetail(details, tr("rolled a d%1: %2.").arg(sides, rolls.join(QStringLiteral(", "))), tr("DICE"),
- ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::FLIP_CARD: {
- const Event_FlipCard &flipCard = event.GetExtension(Event_FlipCard::ext);
- const QString zone = QString::fromStdString(flipCard.zone_name());
- if (flipCard.has_card_name()) {
- rememberCard(eventPlayerId, zone, flipCard.card_id(),
- QString::fromStdString(flipCard.card_name()));
- }
- QString name = flipCard.has_card_name() ? QString::fromStdString(flipCard.card_name())
- : cardName(eventPlayerId, zone, flipCard.card_id());
- if (name.isEmpty()) {
- name = tr("a card");
- }
- addDetail(details,
- flipCard.face_down() ? tr("flipped %1 face down.").arg(name)
- : tr("flipped %1 face up.").arg(name),
- tr("FLIP"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::DESTROY_CARD: {
- const Event_DestroyCard &destroyCard = event.GetExtension(Event_DestroyCard::ext);
- const QString zone = QString::fromStdString(destroyCard.zone_name());
- const QString name = cardName(eventPlayerId, zone, destroyCard.card_id());
- forgetCard(eventPlayerId, zone, destroyCard.card_id());
- addDetail(details, tr("destroyed %1.").arg(name.isEmpty() ? tr("a card") : name), tr("DESTROY"),
- ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::ATTACH_CARD: {
- const Event_AttachCard &attachCard = event.GetExtension(Event_AttachCard::ext);
- const QString startZone = QString::fromStdString(attachCard.start_zone());
- const QString source = cardName(eventPlayerId, startZone, attachCard.card_id());
- const QString sourceText = source.isEmpty() ? tr("a card") : source;
- // An unattach event carries no target at all, so the target fields default to player 0 / no zone
- // / card 0; only a present target_player_id means the card was attached to something.
- if (!attachCard.has_target_player_id()) {
- addDetail(details, tr("unattached %1.").arg(sourceText), tr("ATTACH"), ReplayEventKind::Action,
- eventPlayerId, forcedByJudge);
- break;
- }
- const QString targetZone = QString::fromStdString(attachCard.target_zone());
- const int targetPlayerId =
- attachCard.target_player_id() >= 0 ? attachCard.target_player_id() : eventPlayerId;
- const QString target = cardName(targetPlayerId, targetZone, attachCard.target_card_id());
- addDetail(details,
- tr("attached %1 to %2.").arg(sourceText, target.isEmpty() ? tr("a card") : target),
- tr("ATTACH"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::CREATE_TOKEN: {
- const Event_CreateToken &createToken = event.GetExtension(Event_CreateToken::ext);
- const QString name = QString::fromStdString(createToken.card_name());
- rememberCard(eventPlayerId, QString::fromStdString(createToken.zone_name()), createToken.card_id(),
- name);
- const QString pt = QString::fromStdString(createToken.pt());
- addDetail(
- details,
- tr("created a token: %1%2.")
- .arg(name.isEmpty() ? tr("token") : name, pt.isEmpty() ? QString() : tr(" (%1)").arg(pt)),
- tr("TOKEN"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::SET_CARD_ATTR: {
- const Event_SetCardAttr &setCardAttr = event.GetExtension(Event_SetCardAttr::ext);
- addDetail(details, describeSetCardAttribute(setCardAttr, eventPlayerId),
- badgeForCardAttribute(setCardAttr.attribute()), ReplayEventKind::Action, eventPlayerId,
- forcedByJudge);
- break;
- }
- case GameEvent::SET_CARD_COUNTER: {
- const Event_SetCardCounter &setCardCounter = event.GetExtension(Event_SetCardCounter::ext);
- const QString name = cardName(eventPlayerId, QString::fromStdString(setCardCounter.zone_name()),
- setCardCounter.card_id());
- addDetail(
- details,
- tr("set a counter on %1 to %2.")
- .arg(name.isEmpty() ? tr("a card") : name, QString::number(setCardCounter.counter_value())),
- tr("COUNTER"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::CREATE_COUNTER: {
- const Event_CreateCounter &createCounter = event.GetExtension(Event_CreateCounter::ext);
- const ServerInfo_Counter &counter = createCounter.counter_info();
- rememberCounter(eventPlayerId, counter);
- const QString name = QString::fromStdString(counter.name());
- addDetail(details,
- tr("created the %1 counter (%2).")
- .arg(name.isEmpty() ? tr("new") : name, QString::number(counter.count())),
- tr("COUNTER"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::SET_COUNTER: {
- const Event_SetCounter &setCounter = event.GetExtension(Event_SetCounter::ext);
- const QString name = counterName(eventPlayerId, setCounter.counter_id());
- addDetail(details,
- tr("set %1 to %2.")
- .arg(name.isEmpty() ? tr("a counter") : tr("the %1 counter").arg(name),
- QString::number(setCounter.value())),
- tr("COUNTER"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::DEL_COUNTER: {
- const Event_DelCounter &delCounter = event.GetExtension(Event_DelCounter::ext);
- const QString name = counterName(eventPlayerId, delCounter.counter_id());
- counterNames.remove(counterKey(eventPlayerId, delCounter.counter_id()));
- addDetail(details,
- tr("removed %1.").arg(name.isEmpty() ? tr("a counter") : tr("the %1 counter").arg(name)),
- tr("COUNTER"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::DUMP_ZONE: {
- const Event_DumpZone &dumpZone = event.GetExtension(Event_DumpZone::ext);
- const QString zoneOwnerPhrase = zonePhrase(
- dumpZone.zone_owner_id(), QString::fromStdString(dumpZone.zone_name()), eventPlayerId);
- if (dumpZone.number_cards() < 0) {
- addDetail(details, tr("looked at %1.").arg(zoneOwnerPhrase), tr("LOOK"),
- ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- } else {
- addDetail(details,
- tr("looked at %1 from %2.")
- .arg(formatCardList(QStringList(), dumpZone.number_cards()), zoneOwnerPhrase),
- tr("LOOK"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- }
- break;
- }
- case GameEvent::CHANGE_ZONE_PROPERTIES: {
- const Event_ChangeZoneProperties &changeZone = event.GetExtension(Event_ChangeZoneProperties::ext);
- const QString zone = QString::fromStdString(changeZone.zone_name());
- QStringList changes;
- if (changeZone.always_reveal_top_card()) {
- changes.append(tr("top card revealed to all"));
- }
- if (changeZone.always_look_at_top_card()) {
- changes.append(tr("top card visible to its owner"));
- }
- addDetail(
- details,
- changes.isEmpty()
- ? tr("changed the properties of %1.").arg(zonePhrase(eventPlayerId, zone, eventPlayerId))
- : tr("changed %1: %2.")
- .arg(zonePhrase(eventPlayerId, zone, eventPlayerId),
- changes.join(QStringLiteral(", "))),
- tr("ZONE"), ReplayEventKind::Action, eventPlayerId, forcedByJudge);
- break;
- }
- case GameEvent::GAME_LOG_NOTICE: {
- const Event_GameLogNotice ¬ice = event.GetExtension(Event_GameLogNotice::ext);
- switch (notice.notice_type()) {
- case Event_GameLogNotice::UNDO_DRAW_FAILED:
- addDetail(details, tr("failed to undo a draw."), tr("NOTICE"), ReplayEventKind::System,
- eventPlayerId, forcedByJudge);
- break;
- default:
- addDetail(details, tr("A game notice was logged."), tr("NOTICE"), ReplayEventKind::System,
- -1, forcedByJudge);
- break;
- }
- break;
- }
- case GameEvent::CREATE_ARROW:
- case GameEvent::DELETE_ARROW:
- // Arrows are transient visual annotations and would make the history very noisy.
- break;
- default:
- addDetail(details, tr("Unrecognized game event."), tr("EVENT"), ReplayEventKind::Action,
- eventPlayerId, forcedByJudge);
- break;
- }
- }
-
- return details;
- }
-};
-} // namespace
-
-QList buildReplayEventEntries(const GameReplay *replay, const QList &timeline)
-{
- if (!replay) {
- return {};
- }
-
- return ReplayEventIndexBuilder(replay, timeline).build();
-}
diff --git a/cockatrice/src/interface/widgets/replay/replay_event_index.h b/cockatrice/src/interface/widgets/replay/replay_event_index.h
deleted file mode 100644
index 40f72a965..000000000
--- a/cockatrice/src/interface/widgets/replay/replay_event_index.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef COCKATRICE_REPLAY_EVENT_INDEX_H
-#define COCKATRICE_REPLAY_EVENT_INDEX_H
-
-#include
-#include
-
-class GameReplay;
-
-enum class ReplayEventKind
-{
- Action,
- Turn,
- Phase,
- Chat,
- System
-};
-
-/**
- * @brief One user-visible entry in the replay history sidebar.
- *
- * The event index refers to the corresponding GameEventContainer in the replay. It is used to seek the replay to that
- * container.
- */
-struct ReplayEventEntry
-{
- int eventIndex = -1;
- int timeMs = 0;
- QString timeLabel;
- QString actor;
- QString badge;
- QString detail;
- ReplayEventKind kind = ReplayEventKind::Action;
-};
-
-/**
- * @brief Builds a compact, human-readable index of the events in a replay.
- *
- * The returned entries copy all the data they need, so the caller does not need to keep the replay object alive after
- * this function returns.
- */
-QList buildReplayEventEntries(const GameReplay *replay, const QList &timeline);
-
-#endif // COCKATRICE_REPLAY_EVENT_INDEX_H
diff --git a/cockatrice/src/interface/widgets/replay/replay_event_sidebar.cpp b/cockatrice/src/interface/widgets/replay/replay_event_sidebar.cpp
deleted file mode 100644
index b5fab2794..000000000
--- a/cockatrice/src/interface/widgets/replay/replay_event_sidebar.cpp
+++ /dev/null
@@ -1,284 +0,0 @@
-#include "replay_event_sidebar.h"
-
-#include "replay_event_index.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-namespace
-{
-enum ItemDataRole
-{
- EventIndexRole = Qt::UserRole + 1,
- TimeLabelRole,
- KindRole,
- ActorRole,
- BadgeRole,
- DetailRole
-};
-
-QColor accentForKind(ReplayEventKind kind, const QPalette &palette)
-{
- switch (kind) {
- case ReplayEventKind::Turn:
- return QColor(0x4c, 0xaf, 0x50);
- case ReplayEventKind::Phase:
- return QColor(0x42, 0xa5, 0xf5);
- case ReplayEventKind::Chat:
- return QColor(0xff, 0xb3, 0x00);
- case ReplayEventKind::System:
- return palette.mid().color();
- case ReplayEventKind::Action:
- default:
- return palette.highlight().color();
- }
-}
-
-class ReplayEventDelegate : public QStyledItemDelegate
-{
-public:
- explicit ReplayEventDelegate(QObject *parent) : QStyledItemDelegate(parent)
- {
- }
-
- void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
- {
- painter->save();
- painter->setRenderHint(QPainter::Antialiasing, true);
-
- const QPalette &palette = option.palette;
- const bool selected = option.state.testFlag(QStyle::State_Selected);
- const bool hovered = option.state.testFlag(QStyle::State_MouseOver);
- const auto kind = static_cast(index.data(KindRole).toInt());
- const QColor accent = accentForKind(kind, palette);
-
- const QRect card = option.rect.adjusted(4, 2, -4, -2);
- QColor cardBackground = selected ? palette.highlight().color() : palette.base().color();
- if (!selected && hovered) {
- cardBackground = palette.alternateBase().color();
- }
-
- painter->setPen(Qt::NoPen);
- painter->setBrush(cardBackground);
- painter->drawRoundedRect(card, 6, 6);
-
- QColor borderColor = selected ? palette.highlightedText().color() : palette.mid().color();
- borderColor.setAlpha(selected ? 100 : 130);
- painter->setBrush(Qt::NoBrush);
- painter->setPen(QPen(borderColor, 1));
- painter->drawRoundedRect(card.adjusted(0, 0, -1, -1), 6, 6);
-
- QColor accentColor = accent;
- if (selected) {
- accentColor = palette.highlightedText().color();
- accentColor.setAlpha(210);
- }
- painter->setPen(Qt::NoPen);
- painter->setBrush(accentColor);
- painter->drawRoundedRect(QRect(card.left() + 7, card.top() + 7, 3, card.height() - 14), 1.5, 1.5);
-
- const QColor textColor = selected ? palette.highlightedText().color() : palette.text().color();
- QColor mutedColor = textColor;
- mutedColor.setAlpha(170);
-
- QFont timeFont = option.font;
- timeFont.setBold(true);
- timeFont.setPointSizeF(qMax(7.0, timeFont.pointSizeF() * 0.9));
-
- QFont actorFont = option.font;
- actorFont.setBold(true);
-
- QFont badgeFont = option.font;
- badgeFont.setBold(true);
- badgeFont.setPointSizeF(qMax(6.5, badgeFont.pointSizeF() * 0.72));
-
- QFont detailFont = option.font;
- detailFont.setPointSizeF(qMax(7.0, detailFont.pointSizeF() * 0.95));
-
- const QFontMetrics timeMetrics(timeFont);
- const QFontMetrics actorMetrics(actorFont);
- const QFontMetrics badgeMetrics(badgeFont);
- const QFontMetrics detailMetrics(detailFont);
-
- const int contentLeft = card.left() + 15;
- const int contentRight = card.right() - 10;
- const int topRowTop = card.top() + 6;
- const int topRowHeight = qMax(timeMetrics.height(), badgeMetrics.height()) + 2;
-
- const QString timeText = index.data(TimeLabelRole).toString();
- const QString actorText = index.data(ActorRole).toString();
- const QString badgeText = index.data(BadgeRole).toString();
- const QString detailText = index.data(DetailRole).toString();
-
- const int timeWidth = timeMetrics.horizontalAdvance(timeText);
- painter->setFont(timeFont);
- painter->setPen(mutedColor);
- painter->drawText(QRect(contentLeft, topRowTop, timeWidth + 2, topRowHeight), Qt::AlignLeft | Qt::AlignVCenter,
- timeText);
-
- int actorRight = contentRight;
- if (!badgeText.isEmpty()) {
- const int badgeWidth = badgeMetrics.horizontalAdvance(badgeText) + 10;
- const int badgeHeight = badgeMetrics.height() + 2;
- const QRect badgeRect(contentRight - badgeWidth, topRowTop + (topRowHeight - badgeHeight) / 2, badgeWidth,
- badgeHeight);
-
- QColor badgeBackground = accentColor;
- badgeBackground.setAlpha(selected ? 45 : 32);
- painter->setPen(Qt::NoPen);
- painter->setBrush(badgeBackground);
- painter->drawRoundedRect(badgeRect, 3, 3);
-
- painter->setFont(badgeFont);
- painter->setPen(accentColor);
- painter->drawText(badgeRect, Qt::AlignCenter, badgeText);
-
- actorRight = badgeRect.left() - 8;
- }
-
- const int actorLeft = contentLeft + timeWidth + 10;
- if (actorRight > actorLeft) {
- const QString elidedActor =
- actorMetrics.elidedText(actorText, Qt::ElideRight, qMax(0, actorRight - actorLeft));
- painter->setFont(actorFont);
- painter->setPen(textColor);
- painter->drawText(QRect(actorLeft, topRowTop, actorRight - actorLeft, topRowHeight),
- Qt::AlignLeft | Qt::AlignVCenter, elidedActor);
- }
-
- const int detailTop = topRowTop + topRowHeight + 2;
- const int detailHeight = qMax(detailMetrics.height(), card.bottom() - detailTop);
- const QString elidedDetail =
- detailMetrics.elidedText(detailText, Qt::ElideRight, qMax(0, contentRight - contentLeft));
- painter->setFont(detailFont);
- painter->setPen(textColor);
- painter->drawText(QRect(contentLeft, detailTop, contentRight - contentLeft, detailHeight),
- Qt::AlignLeft | Qt::AlignVCenter, elidedDetail);
-
- painter->restore();
- }
-
- QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex & /*index*/) const override
- {
- const QFontMetrics metrics(option.font);
- return {0, qMax(58, metrics.height() * 3 + 10)};
- }
-};
-} // namespace
-
-ReplayEventSidebar::ReplayEventSidebar(QWidget *parent, const GameReplay *replay, const QList &timeline)
- : QWidget(parent), eventList(new QListWidget(this)), emptyLabel(new QLabel(this))
-{
- setObjectName("replayEventSidebar");
-
- eventList->setObjectName("replayEventList");
- eventList->setItemDelegate(new ReplayEventDelegate(eventList));
- eventList->setFrameShape(QFrame::NoFrame);
- eventList->setFocusPolicy(Qt::NoFocus);
- eventList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- eventList->setSelectionMode(QAbstractItemView::SingleSelection);
- eventList->setMouseTracking(true);
- eventList->setUniformItemSizes(true);
- eventList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
-
- emptyLabel->setAlignment(Qt::AlignCenter);
- emptyLabel->setWordWrap(true);
- emptyLabel->setMargin(16);
- emptyLabel->setForegroundRole(QPalette::PlaceholderText);
-
- auto *layout = QtUtils::makeLayout(this);
- layout->addWidget(eventList);
- layout->addWidget(emptyLabel);
-
- const QList entries = buildReplayEventEntries(replay, timeline);
- eventIndices.reserve(entries.size());
-
- for (const ReplayEventEntry &entry : entries) {
- auto *item = new QListWidgetItem(eventList);
- item->setData(EventIndexRole, entry.eventIndex);
- item->setData(TimeLabelRole, entry.timeLabel);
- item->setData(KindRole, static_cast(entry.kind));
- item->setData(ActorRole, entry.actor);
- item->setData(BadgeRole, entry.badge);
- item->setData(DetailRole, entry.detail);
- const QString tooltip = entry.actor.isEmpty()
- ? tr("%1\n%2").arg(entry.timeLabel, entry.detail)
- : tr("%1 · %2\n%3").arg(entry.timeLabel, entry.actor, entry.detail);
- item->setToolTip(tooltip);
- item->setData(Qt::AccessibleTextRole, entry.actor.isEmpty()
- ? tr("%1: %2").arg(entry.timeLabel, entry.detail)
- : tr("%1, %2: %3").arg(entry.timeLabel, entry.actor, entry.detail));
- eventIndices.append(entry.eventIndex);
- }
-
- const bool hasEvents = !entries.isEmpty();
- eventList->setVisible(hasEvents);
- emptyLabel->setVisible(!hasEvents);
- emptyLabel->setText(tr("No game events in this replay."));
-
- connect(eventList, &QListWidget::itemClicked, this, [this](QListWidgetItem *item) {
- if (!item) {
- return;
- }
-
- setCurrentRow(eventList->row(item));
- emit eventSelected(item->data(EventIndexRole).toInt());
- });
-
- if (hasEvents) {
- setCurrentRow(-1);
- }
-}
-
-void ReplayEventSidebar::setCurrentEvent(int eventIndex)
-{
- if (eventIndices.isEmpty()) {
- setCurrentRow(-1);
- return;
- }
-
- // The rows hold a subset of the replay's events, in playback order, so the last row at or before eventIndex is the
- // one the game state has reached. An eventIndex of -1 (nothing replayed yet) selects no row.
- const auto it = std::upper_bound(eventIndices.cbegin(), eventIndices.cend(), eventIndex);
- const int row = static_cast(it - eventIndices.cbegin()) - 1;
- setCurrentRow(row);
-}
-
-void ReplayEventSidebar::setCurrentRow(int row)
-{
- if (row < 0 || row >= eventList->count()) {
- row = -1;
- }
-
- if (row >= 0) {
- // The view's current index also moves on mouse press and can be deselected by a ctrl-click,
- // so the cached row alone cannot tell whether the view is already in the desired state.
- const QListWidgetItem *item = eventList->item(row);
- if (row == eventList->currentRow() && item && item->isSelected()) {
- return;
- }
- } else if (row == currentRow) {
- return;
- }
-
- currentRow = row;
- eventList->setCurrentRow(currentRow);
-
- if (currentRow >= 0) {
- QListWidgetItem *item = eventList->item(currentRow);
- if (!eventList->viewport()->rect().contains(eventList->visualItemRect(item))) {
- eventList->scrollToItem(item, QAbstractItemView::PositionAtCenter);
- }
- }
-}
diff --git a/cockatrice/src/interface/widgets/replay/replay_event_sidebar.h b/cockatrice/src/interface/widgets/replay/replay_event_sidebar.h
deleted file mode 100644
index ee163356e..000000000
--- a/cockatrice/src/interface/widgets/replay/replay_event_sidebar.h
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifndef COCKATRICE_REPLAY_EVENT_SIDEBAR_H
-#define COCKATRICE_REPLAY_EVENT_SIDEBAR_H
-
-#include
-#include
-#include
-
-class GameReplay;
-class QLabel;
-class QListWidget;
-
-/**
- * @brief A scrollable list of replay events that can be used to seek the replay.
- *
- * The sidebar copies the event data it needs at construction time. It does not keep a reference to the replay or to
- * the replay manager.
- */
-class ReplayEventSidebar : public QWidget
-{
- Q_OBJECT
-
-public:
- /**
- * @param parent The parent widget
- * @param replay The replay to build the event list from
- * @param timeline Event index to time in milliseconds, as used by ReplayManager
- */
- ReplayEventSidebar(QWidget *parent, const GameReplay *replay, const QList &timeline);
-
-public slots:
- /**
- * @brief Highlights the row of the event the game state has reached.
- *
- * @param eventIndex Index into the replay's event list, or -1 when nothing has been replayed yet
- */
- void setCurrentEvent(int eventIndex);
-
-signals:
- void eventSelected(int eventIndex);
-
-private:
- void setCurrentRow(int row);
-
- QListWidget *eventList;
- QLabel *emptyLabel;
- QList eventIndices; ///< Replay event index of each row, ascending
- int currentRow = -1;
-};
-
-#endif // COCKATRICE_REPLAY_EVENT_SIDEBAR_H
diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.h b/cockatrice/src/interface/widgets/replay/replay_manager.h
index e3745778e..34bae5f9a 100644
--- a/cockatrice/src/interface/widgets/replay/replay_manager.h
+++ b/cockatrice/src/interface/widgets/replay/replay_manager.h
@@ -66,16 +66,6 @@ public:
return replayTimeline;
}
- /**
- * @brief Index of the last event the game state has processed.
- *
- * @return The event index, or -1 while no event has been replayed yet
- */
- [[nodiscard]] int getLastProcessedEventIndex() const
- {
- return currentEvent - 1;
- }
-
void setTimeScaleFactor(qreal _timeScaleFactor);
void setSkipEmptySections(bool value);
diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_widget.cpp
index e911bc0f9..3f0bf35d9 100644
--- a/cockatrice/src/interface/widgets/replay/replay_widget.cpp
+++ b/cockatrice/src/interface/widgets/replay/replay_widget.cpp
@@ -19,11 +19,6 @@
class GameReplay;
-int ReplayWidget::getLastProcessedEventIndex() const
-{
- return replayManager->getLastProcessedEventIndex();
-}
-
ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay)
: QWidget(parent), replayPlayButton(nullptr), replayFastForwardButton(nullptr), aReplaySkipForward(nullptr),
aReplaySkipBackward(nullptr), aReplaySkipForwardBig(nullptr), aReplaySkipBackwardBig(nullptr)
diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.h b/cockatrice/src/interface/widgets/replay/replay_widget.h
index cbb676288..5d23b358d 100644
--- a/cockatrice/src/interface/widgets/replay/replay_widget.h
+++ b/cockatrice/src/interface/widgets/replay/replay_widget.h
@@ -38,11 +38,6 @@ public:
[[nodiscard]] const QList &getReplayTimeline() const;
- /**
- * @brief Index of the last replay event the game state has processed, or -1 before the first event.
- */
- [[nodiscard]] int getLastProcessedEventIndex() const;
-
public slots:
void seekToEvent(int eventIndex);
diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp
index 468095adb..2702a527d 100644
--- a/cockatrice/src/interface/widgets/server/game_selector.cpp
+++ b/cockatrice/src/interface/widgets/server/game_selector.cpp
@@ -1,7 +1,7 @@
#include "game_selector.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../interface/widgets/dialogs/dlg_create_game.h"
#include "../interface/widgets/dialogs/dlg_filter_games.h"
#include "../interface/widgets/tabs/tab_room.h"
@@ -130,10 +130,10 @@ GameSelector::GameSelector(AbstractClient *_client,
}
filterButton = new QPushButton;
- filterButton->setIcon(loadThemedIcon(QStringLiteral("icons/search")));
+ filterButton->setIcon(themePixmap(QStringLiteral("icons/search")));
connect(filterButton, &QPushButton::clicked, this, &GameSelector::actSetFilter);
clearFilterButton = new QPushButton;
- clearFilterButton->setIcon(loadThemedIcon(QStringLiteral("icons/clearsearch")));
+ clearFilterButton->setIcon(themePixmap(QStringLiteral("icons/clearsearch")));
bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults();
clearFilterButton->setEnabled(!filtersSetToDefault);
connect(clearFilterButton, &QPushButton::clicked, this, &GameSelector::actClearFilter);
@@ -266,13 +266,9 @@ void GameSelector::actCreate()
return;
}
- // Heap-allocate the dialog: if this selector (or its room tab) is destroyed while the
- // dialog is open, e.g. on server disconnect, Qt deletes its children. A stack-allocated
- // child would then be deleted by Qt and crash.
- auto *dlg = new DlgCreateGame(room, room->getGameTypes(), this);
- dlg->setAttribute(Qt::WA_DeleteOnClose);
- connect(dlg, &QDialog::finished, this, &GameSelector::updateTitle);
- dlg->open();
+ DlgCreateGame dlg(room, room->getGameTypes(), this);
+ dlg.exec();
+ updateTitle();
}
void GameSelector::checkResponse(const Response &response)
@@ -366,9 +362,8 @@ void GameSelector::customContextMenu(const QPoint &point)
const ServerInfo_Game &gameInfo = gameListModel->getGame(index.data(Qt::UserRole).toInt());
const QMap &gameTypes = gameListModel->getGameTypes().value(gameInfo.room_id());
- auto *dlg = new DlgCreateGame(gameInfo, gameTypes, this);
- dlg->setAttribute(Qt::WA_DeleteOnClose);
- dlg->open();
+ DlgCreateGame dlg(gameInfo, gameTypes, this);
+ dlg.exec();
});
QAction copyLink(tr("Cop&y game link"));
diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp
index d33526bd1..8fed95833 100644
--- a/cockatrice/src/interface/widgets/server/games_model.cpp
+++ b/cockatrice/src/interface/widgets/server/games_model.cpp
@@ -1,7 +1,6 @@
#include "games_model.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
#include "../interface/pixel_map_generator.h"
#include "libcockatrice/protocol/pb/serverinfo_user.pb.h"
#include "user/user_list_proxy.h"
@@ -86,8 +85,7 @@ const QString GamesModel::getGameCreatedString(const int secs)
}
GamesModel::GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent)
- : QAbstractTableModel(parent), rooms(_rooms), gameTypes(_gameTypes),
- lockIcon(loadThemedIcon(QStringLiteral("icons/lock")))
+ : QAbstractTableModel(parent), rooms(_rooms), gameTypes(_gameTypes)
{
}
@@ -193,9 +191,7 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const
return result.join(", ");
}
case Qt::DecorationRole: {
- // One engine per model: it is palette- and theme-aware and
- // invalidates itself, so a repaint must not build a new one.
- return gameentry.with_password() ? lockIcon : QVariant();
+ return gameentry.with_password() ? QIcon(LockPixmapGenerator::generatePixmap(13)) : QVariant();
case Qt::TextAlignmentRole:
return Qt::AlignLeft;
default:
diff --git a/cockatrice/src/interface/widgets/server/games_model.h b/cockatrice/src/interface/widgets/server/games_model.h
index 62862417d..9bf54b83a 100644
--- a/cockatrice/src/interface/widgets/server/games_model.h
+++ b/cockatrice/src/interface/widgets/server/games_model.h
@@ -5,7 +5,6 @@
#include "game_type_map.h"
#include
-#include
#include
#include
#include
@@ -33,7 +32,6 @@ private:
QList gameList; /**< List of games currently displayed. */
QMap rooms; /**< Map of room IDs to room names. */
QMap gameTypes; /**< Map of room IDs to available game types. */
- QIcon lockIcon; /**< Themed lock for password-protected games. */
static const int NUM_COLS = 8; /**< Number of columns in the table. */
diff --git a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp
index c1db6138d..c131c3cbf 100644
--- a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp
+++ b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp
@@ -1,6 +1,6 @@
#include "remote_replay_list_tree_widget.h"
-#include "../../../theme_icon.h"
+#include "../../../pixel_map_generator.h"
#include "libcockatrice/protocol/pb/response.pb.h"
#include "libcockatrice/protocol/pb/serverinfo_replay_match.pb.h"
@@ -50,7 +50,7 @@ RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client,
QFileIconProvider fip;
dirIcon = fip.icon(QFileIconProvider::Folder);
fileIcon = fip.icon(QFileIconProvider::File);
- lockIcon = loadThemedIcon(QStringLiteral("icons/lock"));
+ lockIcon = themePixmap(QStringLiteral("icons/lock"));
}
RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel()
diff --git a/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp
index e90603858..79370f512 100644
--- a/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp
+++ b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp
@@ -1,7 +1,7 @@
#include "deck_editor_settings_page.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../dialogs/path_safety_warning.h"
#include "update/card_spoiler/spoiler_background_updater.h"
@@ -80,19 +80,19 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
}
aAdd = new QAction(this);
- aAdd->setIcon(loadThemedIcon(QStringLiteral("icons/increment")));
+ aAdd->setIcon(themePixmap(QStringLiteral("icons/increment")));
connect(aAdd, &QAction::triggered, this, &DeckEditorSettingsPage::actAddURL);
aEdit = new QAction(this);
- aEdit->setIcon(loadThemedIcon(QStringLiteral("icons/pencil")));
+ aEdit->setIcon(themePixmap(QStringLiteral("icons/pencil")));
connect(aEdit, &QAction::triggered, this, &DeckEditorSettingsPage::actEditURL);
aRemove = new QAction(this);
- aRemove->setIcon(loadThemedIcon(QStringLiteral("icons/decrement")));
+ aRemove->setIcon(themePixmap(QStringLiteral("icons/decrement")));
connect(aRemove, &QAction::triggered, this, &DeckEditorSettingsPage::actRemoveURL);
aRateLimit = new QAction(this);
- aRateLimit->setIcon(loadThemedIcon(QStringLiteral("icons/settings")));
+ aRateLimit->setIcon(themePixmap(QStringLiteral("icons/cogwheel")));
connect(aRateLimit, &QAction::triggered, this, &DeckEditorSettingsPage::actAdjustRateLimit);
auto *urlToolBar = new QToolBar;
diff --git a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp
index 09dd9e9b0..6128a7830 100644
--- a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp
+++ b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp
@@ -1,7 +1,7 @@
#include "messages_settings_page.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../interface/widgets/utility/get_text_with_max.h"
#include "libcockatrice/utility/macros.h"
@@ -146,15 +146,15 @@ MessagesSettingsPage::MessagesSettingsPage()
}
aAdd = new QAction(this);
- aAdd->setIcon(loadThemedIcon(QStringLiteral("icons/increment")));
+ aAdd->setIcon(themePixmap(QStringLiteral("icons/increment")));
connect(aAdd, &QAction::triggered, this, &MessagesSettingsPage::actAdd);
aEdit = new QAction(this);
- aEdit->setIcon(loadThemedIcon(QStringLiteral("icons/pencil")));
+ aEdit->setIcon(themePixmap(QStringLiteral("icons/pencil")));
connect(aEdit, &QAction::triggered, this, &MessagesSettingsPage::actEdit);
aRemove = new QAction(this);
- aRemove->setIcon(loadThemedIcon(QStringLiteral("icons/decrement")));
+ aRemove->setIcon(themePixmap(QStringLiteral("icons/decrement")));
connect(aRemove, &QAction::triggered, this, &MessagesSettingsPage::actRemove);
auto *messageToolBar = new QToolBar;
diff --git a/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp
index 83c2ec585..a77c0c2c3 100644
--- a/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp
+++ b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp
@@ -3,7 +3,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcut_treeview.h"
#include "../../../client/settings/shortcuts_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../interface/widgets/utility/custom_line_edit.h"
#include "../interface/widgets/utility/sequence_edit.h"
@@ -58,8 +58,8 @@ ShortcutSettingsPage::ShortcutSettingsPage()
btnResetAll = new QPushButton(this);
btnClearAll = new QPushButton(this);
- btnResetAll->setIcon(loadThemedIcon(QStringLiteral("icons/reload")));
- btnClearAll->setIcon(loadThemedIcon(QStringLiteral("icons/clearsearch")));
+ btnResetAll->setIcon(themePixmap(QStringLiteral("icons/update")));
+ btnClearAll->setIcon(themePixmap(QStringLiteral("icons/clearsearch")));
// layout
auto *_editLayout = new QGridLayout;
diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
index ae3a9baef..9f7369435 100644
--- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
+++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
@@ -1,7 +1,7 @@
#include "tab_archidekt.h"
#include "../../../../../client/settings/cache_settings.h"
-#include "../../../../theme_icon.h"
+#include "../../../../pixel_map_generator.h"
#include "../../../cards/additional_info/mana_symbol_widget.h"
#include "../../../cards/card_size_widget.h"
#include "../../../quick_settings/settings_button_widget.h"
@@ -282,7 +282,7 @@ void TabArchidekt::setupFilterWidgets()
// Format filter (collapsible)
formatButton = new SettingsButtonWidget(secondaryToolbar);
formatButton->setButtonText(tr("Formats"));
- formatButton->setButtonIcon(loadThemedIcon(QStringLiteral("icons/scales")));
+ formatButton->setButtonIcon(themePixmap(QStringLiteral("icons/scale_balanced")));
QWidget *formatContainer = new QWidget(secondaryToolbar);
QGridLayout *formatLayout = new QGridLayout(formatContainer);
diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp
index a0e3f2491..9adda008e 100644
--- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp
+++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp
@@ -1,7 +1,7 @@
#include "commander_bracket_widget.h"
#include "../../../../../client/settings/cache_settings.h"
-#include "../../../../theme_icon.h"
+#include "../../../../pixel_map_generator.h"
#include "commander_bracket_service.h"
#include "libcockatrice/deck_list/deck_list.h"
@@ -49,7 +49,7 @@ CommanderBracketWidget::CommanderBracketWidget(QWidget *parent) : QWidget(parent
bracketInfoButton->setEnabled(false);
bracketRefreshButton = new QToolButton(this);
- bracketRefreshButton->setIcon(loadThemedIcon(QStringLiteral("icons/reload")));
+ bracketRefreshButton->setIcon(themePixmap(QStringLiteral("icons/reload")));
bracketRefreshButton->setAutoRaise(true);
connect(bracketRefreshButton, &QToolButton::clicked, this, &CommanderBracketWidget::requestBracketEstimate);
diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp
index 8af9d5e6e..3dfdc956b 100644
--- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp
+++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp
@@ -2,7 +2,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../deck_loader/deck_loader.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../cards/additional_info/deck_color_identity.h"
#include "../deck_share/deck_share_utils.h"
#include "../deck_share/share_bar_widget.h"
@@ -187,19 +187,19 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor,
// Left side actions
aOpenLocalDeck = new QAction(this);
- aOpenLocalDeck->setIcon(loadThemedIcon(QStringLiteral("icons/pencil")));
+ aOpenLocalDeck->setIcon(themePixmap(QStringLiteral("icons/pencil")));
connect(aOpenLocalDeck, &QAction::triggered, this, &TabDeckStorage::actOpenLocalDeck);
aRenameLocal = new QAction(this);
- aRenameLocal->setIcon(loadThemedIcon(QStringLiteral("icons/rename")));
+ aRenameLocal->setIcon(themePixmap(QStringLiteral("icons/rename")));
connect(aRenameLocal, &QAction::triggered, this, &TabDeckStorage::actRenameLocal);
aUpload = new QAction(this);
- aUpload->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_right")));
+ aUpload->setIcon(themePixmap(QStringLiteral("icons/arrow_right_green")));
connect(aUpload, &QAction::triggered, this, &TabDeckStorage::actUpload);
aNewLocalFolder = new QAction(this);
aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
connect(aNewLocalFolder, &QAction::triggered, this, &TabDeckStorage::actNewLocalFolder);
aDeleteLocalDeck = new QAction(this);
- aDeleteLocalDeck->setIcon(loadThemedIcon(QStringLiteral("icons/remove_row")));
+ aDeleteLocalDeck->setIcon(themePixmap(QStringLiteral("icons/remove_row")));
connect(aDeleteLocalDeck, &QAction::triggered, this, &TabDeckStorage::actDeleteLocalDeck);
aOpenDecksFolder = new QAction(this);
@@ -208,24 +208,24 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor,
// Right side actions
aOpenRemoteDeck = new QAction(this);
- aOpenRemoteDeck->setIcon(loadThemedIcon(QStringLiteral("icons/pencil")));
+ aOpenRemoteDeck->setIcon(themePixmap(QStringLiteral("icons/pencil")));
connect(aOpenRemoteDeck, &QAction::triggered, this, &TabDeckStorage::actOpenRemoteDeck);
aDownload = new QAction(this);
- aDownload->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_left")));
+ aDownload->setIcon(themePixmap(QStringLiteral("icons/arrow_left_green")));
connect(aDownload, &QAction::triggered, this, &TabDeckStorage::actDownload);
aNewFolder = new QAction(this);
aNewFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
connect(aNewFolder, &QAction::triggered, this, &TabDeckStorage::actNewFolder);
aDeleteRemoteDeck = new QAction(this);
- aDeleteRemoteDeck->setIcon(loadThemedIcon(QStringLiteral("icons/remove_row")));
+ aDeleteRemoteDeck->setIcon(themePixmap(QStringLiteral("icons/remove_row")));
connect(aDeleteRemoteDeck, &QAction::triggered, this, &TabDeckStorage::actDeleteRemoteDeck);
aShareDecks = new QAction(this);
- aShareDecks->setIcon(loadThemedIcon(QStringLiteral("icons/share")));
+ aShareDecks->setIcon(themePixmap(QStringLiteral("icons/share")));
connect(aShareDecks, &QAction::triggered, this, &TabDeckStorage::actShareDecks);
aPublishDeck = new QAction(this);
- aPublishDeck->setIcon(loadThemedIcon(QStringLiteral("icons/lock")));
+ aPublishDeck->setIcon(QPixmap("theme:icons/lock"));
connect(aPublishDeck, &QAction::triggered, this, &TabDeckStorage::actPublishDeck);
// Add actions to toolbars
diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp
index 01d021907..34d6f8a51 100644
--- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp
+++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp
@@ -27,7 +27,6 @@
#include "../interface/widgets/utility/line_edit_completer.h"
#include "../main.h"
#include "../menus/tearoff_menu.h"
-#include "../replay/replay_event_sidebar.h"
#include "../replay/replay_widget.h"
#include "../utility/visibility_change_listener.h"
#include "card/card_completer_proxy_model.h"
@@ -99,7 +98,7 @@ class Message;
} // namespace google
TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
- : Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr), replayDock(nullptr), historyDock(nullptr)
+ : Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr)
{
setObjectName("TabGame");
// THIS CTOR IS USED ON REPLAY
@@ -116,7 +115,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
addDockWidget(Qt::RightDockWidgetArea, cardInfoDock);
addDockWidget(Qt::RightDockWidgetArea, playerListDock);
addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock);
- addDockWidget(Qt::LeftDockWidgetArea, historyDock);
addDockWidget(Qt::BottomDockWidgetArea, replayDock);
mainWidget = new QStackedWidget(this);
@@ -160,7 +158,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
createPlayAreaWidget();
createDeckViewContainerWidget();
replayDock = nullptr;
- historyDock = nullptr;
addDockWidget(Qt::RightDockWidgetArea, cardInfoDock);
addDockWidget(Qt::RightDockWidgetArea, playerListDock);
@@ -356,9 +353,6 @@ void TabGame::retranslateUi()
}
cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString()));
messageLayoutDock->setWindowTitle(tr("Messages") + (messageLayoutDock->isWindow() ? tabText : QString()));
- if (historyDock) {
- historyDock->setWindowTitle(tr("Game History") + (historyDock->isWindow() ? tabText : QString()));
- }
if (replayDock) {
replayDock->setWindowTitle(tr("Replay Timeline") + (replayDock->isWindow() ? tabText : QString()));
}
@@ -435,9 +429,6 @@ void TabGame::retranslateUi()
dockToActions[messageLayoutDock].menu->setTitle(tr("Messages"));
dockToActions[playerListDock].menu->setTitle(tr("Player List"));
- if (historyDock) {
- dockToActions[historyDock].menu->setTitle(tr("Game History"));
- }
if (replayDock) {
dockToActions[replayDock].menu->setTitle(tr("Replay Timeline"));
}
@@ -570,10 +561,8 @@ void TabGame::adminLockChanged(bool lock)
void TabGame::actGameInfo()
{
- // Heap-allocated so that destroying this tab while the dialog is open cannot delete a stack object.
- auto *dlg = new DlgCreateGame(game->getGameMetaInfo()->proto(), game->getGameMetaInfo()->getRoomGameTypes(), this);
- dlg->setAttribute(Qt::WA_DeleteOnClose);
- dlg->open();
+ DlgCreateGame dlg(game->getGameMetaInfo()->proto(), game->getGameMetaInfo()->getRoomGameTypes(), this);
+ dlg.exec();
}
void TabGame::actCopyGameLink()
@@ -1302,10 +1291,6 @@ void TabGame::createViewMenuItems()
registerDockWidget(viewMenu, messageLayoutDock, {250, 200});
registerDockWidget(viewMenu, playerListDock, {250, 50});
- if (historyDock) {
- registerDockWidget(viewMenu, historyDock, {280, 400});
- }
-
if (replayDock) {
registerDockWidget(viewMenu, replayDock, {900, 100});
}
@@ -1375,12 +1360,6 @@ void TabGame::actResetLayout()
addDockWidget(Qt::RightDockWidgetArea, playerListDock);
addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock);
- if (historyDock) {
- historyDock->setVisible(true);
- historyDock->setFloating(false);
- addDockWidget(Qt::LeftDockWidgetArea, historyDock);
- }
-
if (replayDock) {
replayDock->setVisible(true);
replayDock->setFloating(false);
@@ -1389,9 +1368,6 @@ void TabGame::actResetLayout()
cardInfoDock->resize(250, 360);
messageLayoutDock->resize(250, 200);
playerListDock->resize(250, 50);
- if (historyDock) {
- historyDock->resize(280, 400);
- }
replayDock->resize(900, 100);
} else {
cardInfoDock->resize(250, 360);
@@ -1446,22 +1422,6 @@ void TabGame::createReplayDock(GameReplay *replay)
[this](const auto &event, auto options) {
game->getGameEventHandler()->processGameEventContainer(event, nullptr, options);
});
-
- auto *historySidebar = new ReplayEventSidebar(nullptr, replay, replayWidget->getReplayTimeline());
- historyDock = new QDockWidget(this);
- historyDock->setObjectName("historyDock");
- historyDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
- QDockWidget::DockWidgetMovable);
- historyDock->setWidget(historySidebar);
- historyDock->setFloating(false);
-
- connect(historySidebar, &ReplayEventSidebar::eventSelected, replayWidget, &ReplayWidget::seekToEvent);
- // The sidebar highlights events rather than times, so ask the manager which event the game state has reached.
- connect(replayWidget, &ReplayWidget::timeChanged, historySidebar,
- [this, historySidebar] { historySidebar->setCurrentEvent(replayWidget->getLastProcessedEventIndex()); });
-
- // No event has been replayed yet when the sidebar is connected, so apply the initial state explicitly.
- historySidebar->setCurrentEvent(replayWidget->getLastProcessedEventIndex());
}
void TabGame::createDeckViewContainerWidget(bool bReplay)
diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h
index 142c02999..412529dc2 100644
--- a/cockatrice/src/interface/widgets/tabs/tab_game.h
+++ b/cockatrice/src/interface/widgets/tabs/tab_game.h
@@ -50,7 +50,6 @@ class AbstractClient;
class GameView;
class GameScene;
class ReplayWidget;
-class ReplayEventSidebar;
class CardInfoFrameWidget;
class QLabel;
class QPushButton;
@@ -90,7 +89,7 @@ private:
QMap deckViewContainers;
QVBoxLayout *deckViewContainerLayout;
QWidget *gamePlayAreaWidget, *deckViewContainerWidget;
- QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock, *historyDock;
+ QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock;
QAction *playersSeparator;
QMenu *gameMenu, *viewMenu;
TearOffMenu *phasesMenu;
diff --git a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp
index dfde08f8e..dc14b6e4a 100644
--- a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp
+++ b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp
@@ -2,7 +2,6 @@
#include "../../../client/settings/cache_settings.h"
#include "../../deck_loader/deck_loader.h"
-#include "../../theme_icon.h"
#include "../cards/card_size_widget.h"
#include "../general/layout_containers/flow_widget.h"
#include "../interface/theme_manager.h"
@@ -64,8 +63,8 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c
headerLayout->addStretch(1);
// Filter/toolbar row, matching the Visual Deck Storage: color identity filter
- // first, the search bar stretching in the middle, and the quick-settings button
- // at the end. The card size slider lives inside its popup.
+ // first, the search bar stretching in the middle, and the quick settings
+ // cogwheel at the end. The card size slider lives inside the cogwheel popup.
emptyLabel = new QLabel(tr("This user has not published any decks."), this);
emptyLabel->setAlignment(Qt::AlignCenter);
emptyLabel->setVisible(false);
@@ -80,7 +79,7 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c
colorIdentityFilter = new DeckPreviewColorIdentityFilterWidget(this);
searchWidget = new VisualDeckStorageSearchWidget(this);
refreshButton = new QToolButton(this);
- refreshButton->setIcon(loadThemedIcon(QStringLiteral("icons/reload")));
+ refreshButton->setIcon(QPixmap("theme:icons/reload"));
refreshButton->setFixedSize(32, 32);
quickSettingsWidget = new VisualDeckStorageQuickSettingsWidget(this);
quickSettingsWidget->setPublicDecksMode(true);
diff --git a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp
index bd2a69017..d37bff3d8 100644
--- a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp
+++ b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp
@@ -1,7 +1,7 @@
#include "tab_replays.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../interface/widgets/server/remote/remote_replay_list_tree_widget.h"
#include "libcockatrice/protocol/pb/commands.pb.h"
#include "libcockatrice/protocol/pb/serverinfo_replay.pb.h"
@@ -145,17 +145,17 @@ QGroupBox *TabReplays::createLeftLayout()
// Left side actions
aOpenLocalReplay = new QAction(this);
- aOpenLocalReplay->setIcon(loadThemedIcon(QStringLiteral("icons/view")));
+ aOpenLocalReplay->setIcon(themePixmap(QStringLiteral("icons/view")));
connect(aOpenLocalReplay, &QAction::triggered, this, &TabReplays::actOpenLocalReplay);
connect(localDirView, &QTreeView::doubleClicked, this, &TabReplays::actOpenLocalReplay);
aRenameLocal = new QAction(this);
- aRenameLocal->setIcon(loadThemedIcon(QStringLiteral("icons/rename")));
+ aRenameLocal->setIcon(themePixmap(QStringLiteral("icons/rename")));
connect(aRenameLocal, &QAction::triggered, this, &TabReplays::actRenameLocal);
aNewLocalFolder = new QAction(this);
aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
connect(aNewLocalFolder, &QAction::triggered, this, &TabReplays::actNewLocalFolder);
aDeleteLocalReplay = new QAction(this);
- aDeleteLocalReplay->setIcon(loadThemedIcon(QStringLiteral("icons/remove_row")));
+ aDeleteLocalReplay->setIcon(themePixmap(QStringLiteral("icons/remove_row")));
connect(aDeleteLocalReplay, &QAction::triggered, this, &TabReplays::actDeleteLocalReplay);
aOpenReplaysFolder = new QAction(this);
@@ -207,24 +207,24 @@ QGroupBox *TabReplays::createRightLayout()
// Right side actions
aOpenRemoteReplay = new QAction(this);
- aOpenRemoteReplay->setIcon(loadThemedIcon(QStringLiteral("icons/view")));
+ aOpenRemoteReplay->setIcon(themePixmap(QStringLiteral("icons/view")));
connect(aOpenRemoteReplay, &QAction::triggered, this, &TabReplays::actOpenRemoteReplay);
connect(serverDirView, &QTreeView::doubleClicked, this, &TabReplays::actOpenRemoteReplay);
aDownload = new QAction(this);
- aDownload->setIcon(loadThemedIcon(QStringLiteral("icons/arrow_left")));
+ aDownload->setIcon(themePixmap(QStringLiteral("icons/arrow_left_green")));
connect(aDownload, &QAction::triggered, this, &TabReplays::actDownload);
aKeep = new QAction(this);
- aKeep->setIcon(loadThemedIcon(QStringLiteral("icons/lock")));
+ aKeep->setIcon(themePixmap(QStringLiteral("icons/lock")));
connect(aKeep, &QAction::triggered, this, &TabReplays::actKeepRemoteReplay);
aDeleteRemoteReplay = new QAction(this);
- aDeleteRemoteReplay->setIcon(loadThemedIcon(QStringLiteral("icons/remove_row")));
+ aDeleteRemoteReplay->setIcon(themePixmap(QStringLiteral("icons/remove_row")));
connect(aDeleteRemoteReplay, &QAction::triggered, this, &TabReplays::actDeleteRemoteReplay);
aGetReplayCode = new QAction(this);
- aGetReplayCode->setIcon(loadThemedIcon(QStringLiteral("icons/share")));
+ aGetReplayCode->setIcon(themePixmap(QStringLiteral("icons/share")));
connect(aGetReplayCode, &QAction::triggered, this, &TabReplays::actGetReplayCode);
aSubmitReplayCode = new QAction(this);
- aSubmitReplayCode->setIcon(loadThemedIcon(QStringLiteral("icons/search")));
+ aSubmitReplayCode->setIcon(themePixmap(QStringLiteral("icons/search")));
connect(aSubmitReplayCode, &QAction::triggered, this, &TabReplays::actSubmitReplayCode);
// Add actions to toolbars
diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.cpp b/cockatrice/src/interface/widgets/tabs/tab_room.cpp
index 329732c64..f63b77445 100644
--- a/cockatrice/src/interface/widgets/tabs/tab_room.cpp
+++ b/cockatrice/src/interface/widgets/tabs/tab_room.cpp
@@ -2,7 +2,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../interface/widgets/dialogs/dlg_settings.h"
#include "../interface/widgets/server/chat_view/chat_view.h"
#include "../interface/widgets/server/game_link.h"
@@ -198,7 +198,7 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
connect(aOpenChatSettings, &QAction::triggered, this, &TabRoom::actOpenChatSettings);
auto *chatSettingsButton = new QToolButton;
- chatSettingsButton->setIcon(loadThemedIcon(QStringLiteral("icons/settings")));
+ chatSettingsButton->setIcon(themePixmap(QStringLiteral("icons/settings")));
chatSettingsButton->setMenu(chatSettingsMenu);
chatSettingsButton->setPopupMode(QToolButton::InstantPopup);
diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp
index 45c320852..cf71dd7ed 100644
--- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp
+++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp
@@ -7,7 +7,6 @@
#include "../../intents/contexts/context_join_room.h"
#include "../../intents/intent.h"
#include "../../intents/intent_join_server_game.h"
-#include "../../theme_icon.h"
#include "../interface/pixel_map_generator.h"
#include "../interface/widgets/server/game_link.h"
#include "../interface/widgets/server/user/user_list_manager.h"
@@ -1361,7 +1360,7 @@ void TabSupervisor::tabUserEvent(bool globalEvent)
auto *tab = static_cast(sender());
if (tab != currentWidget()) {
tab->setContentsChanged(true);
- setTabIcon(indexOf(tab), loadThemedIcon(QStringLiteral("icons/tab_changed")));
+ setTabIcon(indexOf(tab), themePixmap(QStringLiteral("icons/tab_changed")));
}
if (globalEvent && SettingsCache::instance().userInterface().getNotificationsEnabled()) {
QApplication::alert(this);
diff --git a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp
index b09e6da1e..44e6581e9 100644
--- a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp
+++ b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp
@@ -2,7 +2,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include
#include
@@ -24,8 +24,8 @@ SequenceEdit::SequenceEdit(const QString &_shortcutName, QWidget *parent) : QWid
defaultButton = new QPushButton("", this);
lineEdit->setMinimumWidth(70);
- clearButton->setIcon(loadThemedIcon(QStringLiteral("icons/clearsearch")));
- defaultButton->setIcon(loadThemedIcon(QStringLiteral("icons/reload")));
+ clearButton->setIcon(themePixmap(QStringLiteral("icons/clearsearch")));
+ defaultButton->setIcon(themePixmap(QStringLiteral("icons/update")));
auto *layout = QtUtils::makeLayout(this, 1);
layout->addWidget(lineEdit);
diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
index 46330ac46..464d62e4a 100644
--- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
+++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
@@ -15,23 +15,9 @@
#include
#include
-namespace
-{
-
-/** @brief Remove the filters that apply to the given color and attribute from the given model. */
-static void removeFilters(FilterTreeModel *filterModel, QChar color, CardFilter::Attr attr)
-{
- for (const CardFilter *filter : filterModel->getFiltersOfType(attr)) {
- if (filter->term() == color) {
- filterModel->removeFilter(filter);
- }
- }
-}
-} // namespace
-
/**
- * This widget provides a graphical control element for the CardFilter::Attr::AttrColor and
- * CardFilter::Attr::AttrColorIdentity filters applied to the filter model.
+ * This widget provides a graphical control element for the CardFilter::Attr::AttrColor filters applied to the filter
+ * model.
* @param parent The Qt Widget that this widget will parent to
* @param _filterModel The filter model that this widget will manipulate
*/
@@ -73,25 +59,11 @@ void VisualDatabaseDisplayColorFilterWidget::retranslateUi()
modeComboBox->blockSignals(true);
modeComboBox->clear();
- modeComboBox->addItem(tr("Includes"), QVariant::fromValue(FilterMode::Includes));
- modeComboBox->addItem(tr("Excludes"), QVariant::fromValue(FilterMode::Excludes));
- modeComboBox->addItem(tr("Include / Exclude"), QVariant::fromValue(FilterMode::IncludeExclude));
modeComboBox->addItem(tr("Exact match"), QVariant::fromValue(FilterMode::ExactMatch));
- modeComboBox->addItem(tr("Color identity"), QVariant::fromValue(FilterMode::ColorIdentity));
-
- modeComboBox->setItemData(0, tr("Cards containing any of the selected colors"), Qt::ToolTipRole);
- modeComboBox->setItemData(1, tr("Cards containing none of the selected colors"), Qt::ToolTipRole);
- modeComboBox->setItemData(2, tr("Cards containing any of the selected colors and none of the unselected colors"),
- Qt::ToolTipRole);
- modeComboBox->setItemData(3, tr("Cards being exactly of the selected colors"), Qt::ToolTipRole);
- modeComboBox->setItemData(4, tr("Cards whose color identity fits within the selected colors"), Qt::ToolTipRole);
-
- if (currentMode == FilterMode::Custom) {
- addCustomFilterModeItem();
- }
+ modeComboBox->addItem(tr("Includes"), QVariant::fromValue(FilterMode::Includes));
+ modeComboBox->addItem(tr("Include / Exclude"), QVariant::fromValue(FilterMode::IncludeExclude));
modeComboBox->setToolTip(tr("How selected and unselected colors are combined in the filter"));
- modeComboBox->setAccessibleName(tr("Color filter mode"));
// Restore current mode
const int index = modeComboBox->findData(QVariant::fromValue(currentMode));
@@ -102,28 +74,6 @@ void VisualDatabaseDisplayColorFilterWidget::retranslateUi()
modeComboBox->blockSignals(false);
}
-void VisualDatabaseDisplayColorFilterWidget::setFilterMode(FilterMode filterMode)
-{
- int index = modeComboBox->findData(QVariant::fromValue(filterMode));
-
- if (index < 0 && filterMode == FilterMode::Custom) {
- index = modeComboBox->count();
- addCustomFilterModeItem();
- }
-
- if (index >= 0) {
- modeComboBox->setCurrentIndex(index);
- }
-}
-
-void VisualDatabaseDisplayColorFilterWidget::addCustomFilterModeItem()
-{
- int customIndex = modeComboBox->count();
- modeComboBox->addItem(tr("Custom"), QVariant::fromValue(FilterMode::Custom));
- modeComboBox->setItemData(customIndex, tr("Cards matching an unknown combination of color filters"),
- Qt::ToolTipRole);
-}
-
void VisualDatabaseDisplayColorFilterWidget::handleColorToggled(QChar color, bool active)
{
if (active) {
@@ -135,24 +85,74 @@ void VisualDatabaseDisplayColorFilterWidget::handleColorToggled(QChar color, boo
void VisualDatabaseDisplayColorFilterWidget::addFilter(QChar color)
{
- FilterModeInfo info = getFilterModeInfo(currentMode);
+ QString colorString = color;
+ QString typeStr;
- removeFilters(filterModel, color, info.attr);
+ // Remove previous filters
- if (info.includeType != CardFilter::Type::TypeEnd) {
- filterModel->addFilter(new CardFilter(color, info.includeType, info.attr));
+ QList allColorFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrColor);
+ QList matchingFilters;
+
+ for (const CardFilter *filter : allColorFilters) {
+ if (filter->term() == color) {
+ matchingFilters.append(filter);
+ }
+ }
+
+ for (const CardFilter *filter : matchingFilters) {
+ filterModel->removeFilter(filter);
+ }
+
+ // Add actual filter
+
+ switch (currentMode) {
+ case FilterMode::ExactMatch:
+ filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrColor));
+ break;
+
+ case FilterMode::Includes:
+ filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrColor));
+ break;
+
+ case FilterMode::IncludeExclude:
+ filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrColor));
+ break;
}
}
void VisualDatabaseDisplayColorFilterWidget::removeFilter(QChar color)
{
- FilterModeInfo info = getFilterModeInfo(currentMode);
+ QString colorString = color;
- removeFilters(filterModel, color, info.attr);
+ // Remove inclusion filters
+ QList allColorFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrColor);
+ QList matchingFilters;
- // Add exclusion filters if the mode demands it.
- if (info.excludeType != CardFilter::Type::TypeEnd) {
- filterModel->addFilter(new CardFilter(color, info.excludeType, info.attr));
+ for (const CardFilter *filter : allColorFilters) {
+ if (filter->term() == color) {
+ matchingFilters.append(filter);
+ }
+ }
+
+ for (const CardFilter *filter : matchingFilters) {
+ filterModel->removeFilter(filter);
+ }
+
+ // Add exclusion filters if the mode demands it
+ switch (currentMode) {
+ case FilterMode::ExactMatch:
+ filterModel->addFilter(
+ new CardFilter(colorString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor));
+ break;
+
+ case FilterMode::IncludeExclude:
+ filterModel->addFilter(
+ new CardFilter(colorString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor));
+ break;
+
+ case FilterMode::Includes:
+ // No exclusion in Includes mode
+ break;
}
}
@@ -169,7 +169,6 @@ void VisualDatabaseDisplayColorFilterWidget::updateFilterMode()
filterModel->filterTree()->blockSignals(true);
filterModel->clearFiltersOfType(CardFilter::Attr::AttrColor);
- filterModel->clearFiltersOfType(CardFilter::Attr::AttrColorIdentity);
const QList manaSymbolWidgets = findChildren();
@@ -212,53 +211,52 @@ QList VisualDatabaseDisplayColorFilterWidget::getActiveColors()
void VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel()
{
- QList filters;
- filters.append(filterModel->getFiltersOfType(CardFilter::Attr::AttrColor));
- filters.append(filterModel->getFiltersOfType(CardFilter::Attr::AttrColorIdentity));
+ QList allColorFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrColor);
- // Re-derive the mode from the filters in the model, so that a loaded filter set selects the mode that
- // produced it (or Custom when none could have).
- currentMode = deriveFilterMode(filters, currentMode);
+ QList manaSymbolWidgets = findChildren();
- // Update the combobox. Add a Custom item only if we have a custom filter mode.
- int customIndex = modeComboBox->findData(QVariant::fromValue(FilterMode::Custom));
- modeComboBox->blockSignals(true);
- if (currentMode == FilterMode::Custom) {
- if (customIndex < 0) {
- customIndex = modeComboBox->count();
- addCustomFilterModeItem();
- }
- modeComboBox->setCurrentIndex(customIndex);
- } else {
- if (customIndex >= 0) {
- modeComboBox->removeItem(customIndex);
- }
- int index = modeComboBox->findData(QVariant::fromValue(currentMode));
- if (index >= 0) {
- modeComboBox->setCurrentIndex(index);
- }
- }
- modeComboBox->blockSignals(false);
-
- // Update the mana symbol widgets by looking for the current mode's include or exclude filters.
- const FilterModeInfo modeInfo = getFilterModeInfo(currentMode);
-
- const bool useExclude =
- modeInfo.includeType == CardFilter::Type::TypeEnd && modeInfo.excludeType != CardFilter::Type::TypeEnd;
- const CardFilter::Type filterType = useExclude ? modeInfo.excludeType : modeInfo.includeType;
-
- filters = filterModel->getFiltersOfType(modeInfo.attr);
-
- for (ManaSymbolWidget *manaSymbolWidget : findChildren()) {
+ for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) {
bool found = false;
- for (const CardFilter *filter : filters) {
- if (filter->type() == filterType && manaSymbolWidget->getSymbolChar() == filter->term()) {
+ for (const CardFilter *filter : allColorFilters) {
+ if (manaSymbolWidget->getSymbolChar() == filter->term()) {
+ switch (currentMode) {
+ case FilterMode::ExactMatch:
+ switch (filter->type()) {
+ case CardFilter::Type::TypeAnd:
+ setManaSymbolActive(filter->term().at(0), true);
+ break;
+ default:
+ setManaSymbolActive(filter->term().at(0), false);
+ break;
+ }
+ break;
+ case FilterMode::Includes:
+ switch (filter->type()) {
+ case CardFilter::Type::TypeOr:
+ setManaSymbolActive(filter->term().at(0), true);
+ break;
+ default:
+ setManaSymbolActive(filter->term().at(0), false);
+ break;
+ }
+ break;
+ case FilterMode::IncludeExclude:
+ switch (filter->type()) {
+ case CardFilter::Type::TypeOr:
+ setManaSymbolActive(filter->term().at(0), true);
+ break;
+ default:
+ setManaSymbolActive(filter->term().at(0), false);
+ break;
+ }
+ break;
+ }
found = true;
- break;
}
}
- manaSymbolWidget->setColorActive(useExclude ? !found : found);
- manaSymbolWidget->setDisabled(currentMode == FilterMode::Custom);
+ if (!found) {
+ setManaSymbolActive(manaSymbolWidget->getSymbolChar(), false);
+ }
}
}
diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h
index 561c92d09..184f1f96c 100644
--- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h
+++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h
@@ -10,8 +10,8 @@
#include
#include
#include
+#include
#include
-#include
#include
#include
@@ -38,6 +38,15 @@ private:
int circleDiameter;
};
+enum class FilterMode
+{
+ ExactMatch, // Only selected colors are included, all others are excluded.
+ Includes, // Include selected colors (OR condition).
+ IncludeExclude // Include selected colors (OR) and exclude unselected colors (AND NOT).
+};
+
+Q_DECLARE_METATYPE(FilterMode)
+
class VisualDatabaseDisplayColorFilterWidget : public QWidget
{
Q_OBJECT
@@ -45,13 +54,11 @@ class VisualDatabaseDisplayColorFilterWidget : public QWidget
public:
explicit VisualDatabaseDisplayColorFilterWidget(QWidget *parent, FilterTreeModel *filterModel);
void retranslateUi();
- void setFilterMode(FilterMode filterMode);
signals:
void filterModeChanged(FilterMode filterMode);
private slots:
- void addCustomFilterModeItem();
void handleColorToggled(QChar color, bool active);
void addFilter(QChar color);
void removeFilter(QChar color);
diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp
index ecb949b67..5063cce14 100644
--- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp
+++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp
@@ -1,6 +1,6 @@
#include "visual_database_display_filter_toolbar_widget.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../deck_editor/card_database_view.h"
#include "../quick_settings/settings_button_widget.h"
#include "card_database_display_model.h"
@@ -45,13 +45,13 @@ VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidg
sortGroupBox = new QGroupBox(this);
filterGroupBox = new QGroupBox(this);
- auto scalePixmap = [](const QString &prefix) { return loadThemedIcon(prefix).pixmap({20, 20}); };
+ auto scalePixmap = [](const QString &fileName) { return QIcon(QPixmap(fileName)).pixmap({20, 20}); };
sortByLabel = new QLabel(this);
- sortByLabel->setPixmap(scalePixmap("icons/sort_arrow_down"));
+ sortByLabel->setPixmap(scalePixmap("theme:icons/sort_arrow_down"));
filterByLabel = new QLabel(this);
- filterByLabel->setPixmap(scalePixmap("icons/filter"));
+ filterByLabel->setPixmap(scalePixmap("theme:icons/filter"));
sortColumnCombo = new QComboBox(this);
sortColumnCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents);
@@ -89,22 +89,22 @@ VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidg
});
quickFilterSaveLoadWidget = new SettingsButtonWidget(this);
- quickFilterSaveLoadWidget->setButtonIcon(loadThemedIcon(QStringLiteral("icons/floppy_disk")));
+ quickFilterSaveLoadWidget->setButtonIcon(themePixmap(QStringLiteral("icons/floppy_disk")));
quickFilterNameWidget = new SettingsButtonWidget(this);
- quickFilterNameWidget->setButtonIcon(loadThemedIcon(QStringLiteral("icons/pen_to_square")));
+ quickFilterNameWidget->setButtonIcon(themePixmap(QStringLiteral("icons/pen_to_square")));
quickFilterMainTypeWidget = new SettingsButtonWidget(this);
- quickFilterMainTypeWidget->setButtonIcon(loadThemedIcon(QStringLiteral("icons/circle_half_stroke")));
+ quickFilterMainTypeWidget->setButtonIcon(themePixmap(QStringLiteral("icons/circle_half_stroke")));
quickFilterSubTypeWidget = new SettingsButtonWidget(this);
- quickFilterSubTypeWidget->setButtonIcon(loadThemedIcon(QStringLiteral("icons/dragon")));
+ quickFilterSubTypeWidget->setButtonIcon(themePixmap(QStringLiteral("icons/dragon")));
quickFilterSetWidget = new SettingsButtonWidget(this);
- quickFilterSetWidget->setButtonIcon(loadThemedIcon(QStringLiteral("icons/scroll")));
+ quickFilterSetWidget->setButtonIcon(themePixmap(QStringLiteral("icons/scroll")));
quickFilterFormatLegalityWidget = new SettingsButtonWidget(this);
- quickFilterFormatLegalityWidget->setButtonIcon(loadThemedIcon(QStringLiteral("icons/scales")));
+ quickFilterFormatLegalityWidget->setButtonIcon(themePixmap(QStringLiteral("icons/scale_balanced")));
retranslateUi();
}
diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp
index 6d9b28619..6037418ad 100644
--- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp
+++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp
@@ -4,9 +4,7 @@
#include "../../../filters/filter_tree_model.h"
#include "../../../filters/syntax_help.h"
#include "../../key_signals.h"
-#include "../../theme_icon.h"
-#include "../cards/card_info_picture_widget.h"
-#include "../cards/card_info_picture_with_text_overlay_widget.h"
+#include "../../pixel_map_generator.h"
#include "../cards/card_size_widget.h"
#include "../deck_editor/card_database_view.h"
#include "../general/layout_containers/flow_widget.h"
@@ -81,8 +79,8 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
searchEdit->setObjectName("searchEdit");
searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)"));
searchEdit->setClearButtonEnabled(true);
- searchEdit->addAction(loadThemedIcon(QStringLiteral("icons/search")), QLineEdit::LeadingPosition);
- auto help = searchEdit->addAction(loadThemedIcon(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
+ searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
+ auto help = searchEdit->addAction(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); });
setFocusProxy(searchEdit);
@@ -156,11 +154,10 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
clearFilterWidget = new QToolButton();
clearFilterWidget->setFixedSize(32, 32);
- clearFilterWidget->setIcon(loadThemedIcon(QStringLiteral("icons/delete")));
+ clearFilterWidget->setIcon(themePixmap(QStringLiteral("icons/delete")));
connect(clearFilterWidget, &QToolButton::clicked, this, [this] {
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
- colorFilterWidget->setFilterMode(FilterMode::Includes);
filterModel->clear();
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp
index f0c054c3a..63ef07835 100644
--- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp
+++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp
@@ -1,6 +1,6 @@
#include "visual_deck_display_options_widget.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../deck_editor/deck_editor_deck_dock_widget.h"
#include "../quick_settings/settings_button_widget.h"
#include "../tabs/visual_deck_editor/tab_deck_editor_visual.h"
@@ -65,7 +65,7 @@ VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent)
sortByLabel = new QLabel(this);
sortCriteriaButton = new SettingsButtonWidget(this);
- sortCriteriaButton->setButtonIcon(loadThemedIcon(QStringLiteral("icons/sort_arrow_down")));
+ sortCriteriaButton->setButtonIcon(themePixmap(QStringLiteral("icons/sort_arrow_down")));
sortLabel = new QLabel(sortCriteriaButton);
sortLabel->setWordWrap(true);
@@ -119,11 +119,11 @@ void VisualDeckDisplayOptionsWidget::updateDisplayTypeButton()
switch (currentDisplayType) {
case DisplayType::Flat:
displayTypeButton->setButtonText(tr("Toggle Layout: Flat"));
- displayTypeButton->setButtonIcon(loadThemedIcon(QStringLiteral("icons/scroll")));
+ displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scroll")));
break;
case DisplayType::Overlap:
displayTypeButton->setButtonText(tr("Toggle Layout: Overlap"));
- displayTypeButton->setButtonIcon(loadThemedIcon(QStringLiteral("icons/scales")));
+ displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scales")));
break;
}
}
diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp
index 0e0b5e4b4..506ffa2b4 100644
--- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp
+++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp
@@ -1,7 +1,7 @@
#include "visual_deck_editor_widget.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "../cards/card_size_widget.h"
#include "../cards/deck_card_zone_display_widget.h"
#include "../general/layout_containers/flow_widget.h"
@@ -144,7 +144,7 @@ void VisualDeckEditorWidget::initializeSearchBarAndCompleter()
// Search button functionality
searchPushButton = new CompactPushButton(searchContainer);
- searchPushButton->setButtonIcon(loadThemedIcon(QStringLiteral("icons/search")));
+ searchPushButton->setButtonIcon(themePixmap(QStringLiteral("icons/search")));
connect(searchPushButton, &QPushButton::clicked, this, [this] { addCardFromSearch(); });
searchLayout->addWidget(searchPushButton);
diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
index 10a3bf33a..f2669f55d 100644
--- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
+++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
@@ -1,7 +1,7 @@
#include "visual_deck_storage_search_widget.h"
#include "../../../filters/syntax_help.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include
#include
@@ -27,9 +27,9 @@ VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(QWidget *parent) :
searchBar = new QLineEdit(this);
searchBar->setPlaceholderText(tr("Search by filename (or search expression)"));
searchBar->setClearButtonEnabled(true);
- searchBar->addAction(loadThemedIcon(QStringLiteral("icons/search")), QLineEdit::LeadingPosition);
+ searchBar->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
- auto help = searchBar->addAction(loadThemedIcon(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
+ auto help = searchBar->addAction(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
connect(help, &QAction::triggered, this, [this] { createDeckSearchSyntaxHelpWindow(searchBar); });
layout->addWidget(searchBar);
diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
index bbf68adb9..4e1d6293f 100644
--- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
+++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
@@ -1,7 +1,7 @@
#include "visual_deck_storage_widget.h"
#include "../../../client/settings/cache_settings.h"
-#include "../../theme_icon.h"
+#include "../../pixel_map_generator.h"
#include "deck_preview/deck_preview_color_identity_filter_widget.h"
#include "deck_preview/deck_preview_widget.h"
#include "libcockatrice/card/database/card_database.h"
@@ -62,12 +62,12 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
searchWidget = new VisualDeckStorageSearchWidget(this);
refreshButton = new QToolButton(this);
- refreshButton->setIcon(loadThemedIcon(QStringLiteral("icons/reload")));
+ refreshButton->setIcon(themePixmap(QStringLiteral("icons/reload")));
refreshButton->setFixedSize(32, 32);
connect(refreshButton, &QPushButton::clicked, this, &VisualDeckStorageWidget::refreshIfPossible);
shareButton = new QToolButton(this);
- shareButton->setIcon(loadThemedIcon(QStringLiteral("icons/share")));
+ shareButton->setIcon(themePixmap(QStringLiteral("icons/share")));
shareButton->setFixedSize(32, 32);
shareButton->setVisible(false);
connect(shareButton, &QPushButton::clicked, this, &VisualDeckStorageWidget::shareRequested);
diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp
index 715505926..79a802234 100644
--- a/cockatrice/src/interface/window_main.cpp
+++ b/cockatrice/src/interface/window_main.cpp
@@ -50,7 +50,6 @@
#include "latency_tracker.h"
#include "logger.h"
#include "pixel_map_generator.h"
-#include "theme_icon.h"
#include "version_string.h"
#include "widgets/dialogs/dlg_connect.h"
#include "widgets/server/handle_public_servers.h"
@@ -388,10 +387,10 @@ void MainWindow::retranslateUi()
aRegister->setText(tr("&Register to server..."));
aForgotPassword->setText(tr("&Restore password..."));
aSettings->setText(tr("&Settings..."));
- aSettings->setIcon(loadThemedIcon(QStringLiteral("icons/settings")));
+ aSettings->setIcon(themePixmap(QStringLiteral("icons/settings")));
settingsButton->setToolTip(tr("Settings"));
settingsButton->setAccessibleName(tr("Settings"));
- settingsButton->setIcon(loadThemedIcon(QStringLiteral("icons/settings")));
+ settingsButton->setIcon(themePixmap(QStringLiteral("icons/cogwheel")));
aExit->setText(tr("&Exit"));
#if defined(__APPLE__) /* For OSX */
diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp
index 36f0cdeb8..90bfd3f44 100644
--- a/libcockatrice_card/libcockatrice/card/card_info.cpp
+++ b/libcockatrice_card/libcockatrice/card/card_info.cpp
@@ -316,10 +316,6 @@ void CardInfo::setColors(const QString &value)
{
setProperty(Mtg::Colors, value);
}
-QString CardInfo::getColorIdentity() const
-{
- return getProperty(Mtg::ColorIdentity);
-}
QString CardInfo::getLoyalty() const
{
return getProperty(Mtg::Loyalty);
diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h
index 075b5b264..5452ef98b 100644
--- a/libcockatrice_card/libcockatrice/card/card_info.h
+++ b/libcockatrice_card/libcockatrice/card/card_info.h
@@ -469,7 +469,6 @@ public:
[[nodiscard]] QString getCmc() const;
[[nodiscard]] QString getColors() const;
void setColors(const QString &value);
- [[nodiscard]] QString getColorIdentity() const;
[[nodiscard]] QString getLoyalty() const;
[[nodiscard]] QString getMainCardType() const;
[[nodiscard]] QString getManaCost() const;
diff --git a/libcockatrice_filters/CMakeLists.txt b/libcockatrice_filters/CMakeLists.txt
index 1b4303fc9..070c04637 100644
--- a/libcockatrice_filters/CMakeLists.txt
+++ b/libcockatrice_filters/CMakeLists.txt
@@ -1,10 +1,10 @@
-set(HEADERS libcockatrice/filters/filter_card.h libcockatrice/filters/filter_mode.h
- libcockatrice/filters/filter_string.h libcockatrice/filters/filter_tree.h
+set(HEADERS libcockatrice/filters/filter_card.h libcockatrice/filters/filter_string.h
+ libcockatrice/filters/filter_tree.h
)
add_library(
- libcockatrice_filters STATIC ${HEADERS} libcockatrice/filters/filter_card.cpp libcockatrice/filters/filter_mode.cpp
- libcockatrice/filters/filter_string.cpp libcockatrice/filters/filter_tree.cpp
+ libcockatrice_filters STATIC ${HEADERS} libcockatrice/filters/filter_card.cpp libcockatrice/filters/filter_string.cpp
+ libcockatrice/filters/filter_tree.cpp
)
add_dependencies(libcockatrice_filters libcockatrice_card)
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp
index 8af39c098..26cddd53c 100644
--- a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp
+++ b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp
@@ -69,8 +69,6 @@ const QString CardFilter::attrName(Attr a)
return tr("Type");
case AttrColor:
return tr("Color");
- case AttrColorIdentity:
- return tr("Color Identity");
case AttrText:
return tr("Text");
case AttrSet:
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.h b/libcockatrice_filters/libcockatrice/filters/filter_card.h
index bc613fc9c..dbeffd18f 100644
--- a/libcockatrice_filters/libcockatrice/filters/filter_card.h
+++ b/libcockatrice_filters/libcockatrice/filters/filter_card.h
@@ -32,7 +32,6 @@ public:
{
AttrCmc = 0,
AttrColor,
- AttrColorIdentity,
AttrLoyalty,
AttrManaCost,
AttrName,
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_mode.cpp b/libcockatrice_filters/libcockatrice/filters/filter_mode.cpp
deleted file mode 100644
index 356ccf608..000000000
--- a/libcockatrice_filters/libcockatrice/filters/filter_mode.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * @file filter_mode.cpp
- * @ingroup Filters
- */
-
-#include "libcockatrice/filters/filter_mode.h"
-
-#include
-
-FilterModeInfo getFilterModeInfo(FilterMode filterMode)
-{
- switch (filterMode) {
- case FilterMode::Includes:
- return {CardFilter::Type::TypeOr, CardFilter::Type::TypeEnd, CardFilter::Attr::AttrColor};
- case FilterMode::Excludes:
- return {CardFilter::Type::TypeAndNot, CardFilter::Type::TypeEnd, CardFilter::Attr::AttrColor};
- case FilterMode::IncludeExclude:
- return {CardFilter::Type::TypeOr, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor};
- case FilterMode::ExactMatch:
- return {CardFilter::Type::TypeAnd, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor};
- case FilterMode::ColorIdentity:
- return {CardFilter::Type::TypeEnd, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColorIdentity};
- case FilterMode::Custom:
- case FilterMode::End:
- break;
- }
- return {CardFilter::Type::TypeEnd, CardFilter::Type::TypeEnd, CardFilter::Attr::AttrEnd};
-}
-
-FilterMode deriveFilterMode(const QList &filters, FilterMode currentMode)
-{
- constexpr int modeCount = static_cast(FilterMode::End);
- bool modeImpossible[modeCount] = {};
- const QString colors = "WUBRG";
-
- for (int i = 0; i < modeCount; ++i) {
- const FilterModeInfo modeInfo = getFilterModeInfo(static_cast(i));
-
- // Remove the modes that cannot create one of the filters.
- for (const CardFilter *filter : filters) {
- if (filter->attr() != modeInfo.attr ||
- (filter->type() != modeInfo.includeType && filter->type() != modeInfo.excludeType)) {
- modeImpossible[i] = true;
- break;
- }
- }
-
- // Modes that have both an include and an exclude filter should always have a filter for each color.
- if (modeInfo.includeType != CardFilter::Type::TypeEnd && modeInfo.excludeType != CardFilter::Type::TypeEnd &&
- !modeImpossible[i]) {
- for (const QChar &color : colors) {
- int count = 0;
- for (const CardFilter *filter : filters) {
- count += filter->term() == color;
- }
- if (count != 1) {
- modeImpossible[i] = true;
- break;
- }
- }
- }
- }
-
- // Keep the current mode if it can produce the filters, so that a live mode is never swapped for another.
- // Switching to and from the custom mode is always possible.
- if (!modeImpossible[static_cast(currentMode)] && currentMode != FilterMode::Custom) {
- return currentMode;
- }
-
- for (int i = 0; i < modeCount; ++i) {
- if (!modeImpossible[i] || i == static_cast(FilterMode::Custom)) {
- return static_cast(i);
- }
- }
-
- return FilterMode::Custom;
-}
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_mode.h b/libcockatrice_filters/libcockatrice/filters/filter_mode.h
deleted file mode 100644
index f7662d662..000000000
--- a/libcockatrice_filters/libcockatrice/filters/filter_mode.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * @file filter_mode.h
- * @ingroup Filters
- */
-
-#ifndef LIBCOCKATRICE_FILTERS_FILTER_MODE_H
-#define LIBCOCKATRICE_FILTERS_FILTER_MODE_H
-
-#include "libcockatrice/filters/filter_card.h"
-
-#include
-#include
-#include
-
-/**
- * @brief The way selected and unselected colors are combined into card filters.
- */
-enum class FilterMode
-{
- Includes = 0, // Include selected colors (OR condition).
- Excludes, // Exclude selected colors (AND NOT).
- IncludeExclude, // Include selected colors (OR) and exclude unselected colors (AND NOT).
- ExactMatch, // Only selected colors are included, all others are excluded.
- ColorIdentity, // Keep cards whose color identity is a subset of the selected colors (stored as AND NOT for each
- // unselected color).
- Custom, // The color filters do not match any of the above modes. Not included in the combo box.
- End
-};
-
-Q_DECLARE_METATYPE(FilterMode)
-
-/**
- * @brief The filters a single color contributes in a given mode.
- */
-struct FilterModeInfo
-{
- // When a color symbol is enabled, add filters of this type. Skipped if equal to CardFilter::Type::TypeEnd.
- CardFilter::Type includeType;
-
- // When a color symbol is disabled, add filters of this type. Skipped if equal to CardFilter::Type::TypeEnd.
- CardFilter::Type excludeType;
-
- // The attribute to filter for when adding the filters.
- CardFilter::Attr attr;
-};
-
-/** @brief Return the filters the given mode contributes for one color. */
-FilterModeInfo getFilterModeInfo(FilterMode filterMode);
-
-/**
- * @brief Determine the mode that produced the given color filters.
- *
- * Returns @p currentMode unchanged when that mode can still produce @p filters, so that a live mode is never
- * replaced by an equally valid one. Otherwise returns the first mode that can produce them, falling back to
- * FilterMode::Custom when none can.
- *
- * @param filters The filters on CardFilter::Attr::AttrColor and CardFilter::Attr::AttrColorIdentity.
- * @param currentMode The mode currently selected in the UI.
- */
-FilterMode deriveFilterMode(const QList &filters, FilterMode currentMode);
-
-#endif // LIBCOCKATRICE_FILTERS_FILTER_MODE_H
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp
index 1bcb1646b..081ada824 100644
--- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp
+++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp
@@ -409,7 +409,7 @@ static void setupParserRules()
const bool identity = sv.tokens[0].empty() || sv.tokens[0][0] != 'i';
if (sv.tokens[1][0] == ':') {
return [=](const CardData &x) {
- const QString match = identity ? x->getColors() : x->getColorIdentity();
+ const QString match = identity ? x->getColors() : x->getProperty("coloridentity");
if (parts.contains("m") && match.length() < 2) {
return false;
}
@@ -427,7 +427,7 @@ static void setupParserRules()
}
return [=](const CardData &x) {
- const QString match = identity ? x->getColors() : x->getColorIdentity();
+ const QString match = identity ? x->getColors() : x->getProperty("colorIdentity");
if (parts.contains("m") && match.length() < 2) {
return false;
}
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp
index 817c7298d..f12f87181 100644
--- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp
+++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp
@@ -176,24 +176,23 @@ FilterItem::FilterItem(QString trm, FilterItemList *parent) : p(parent), term(st
lowercaseTerm = term.toLower();
switch (attr()) {
- case CardFilter::AttrColor:
- case CardFilter::AttrColorIdentity: {
- QString converted = term.trimmed().toLower();
+ case CardFilter::AttrColor: {
+ QString converted = term.trimmed();
- converted.replace("green", "g");
- converted.replace("grn", "g");
- converted.replace("blue", "u");
- converted.replace("blu", "u");
- converted.replace("black", "b");
- converted.replace("blk", "b");
- converted.replace("red", "r");
- converted.replace("white", "w");
- converted.replace("wht", "w");
- converted.replace("colorless", "c");
- converted.replace("colourless", "c");
- converted.replace("none", "c");
+ converted.replace("green", "g", Qt::CaseInsensitive);
+ converted.replace("grn", "g", Qt::CaseInsensitive);
+ converted.replace("blue", "u", Qt::CaseInsensitive);
+ converted.replace("blu", "u", Qt::CaseInsensitive);
+ converted.replace("black", "b", Qt::CaseInsensitive);
+ converted.replace("blk", "b", Qt::CaseInsensitive);
+ converted.replace("red", "r", Qt::CaseInsensitive);
+ converted.replace("white", "w", Qt::CaseInsensitive);
+ converted.replace("wht", "w", Qt::CaseInsensitive);
+ converted.replace("colorless", "c", Qt::CaseInsensitive);
+ converted.replace("colourless", "c", Qt::CaseInsensitive);
+ converted.replace("none", "c", Qt::CaseInsensitive);
- converted.replace(QString(" "), QString(""));
+ converted.replace(QString(" "), QString(""), Qt::CaseInsensitive);
convertedColorTerm = converted;
break;
@@ -359,17 +358,12 @@ bool FilterItem::acceptSubType(const CardInfoPtr &info) const
return subType.contains(term, Qt::CaseInsensitive);
}
-/**
- * @brief Return true when every letter of the given term appears in the given colors string.
- * If the term is equal to "c", then only return true if the color string is empty.
- *
- * @param term The letters which should be included, or "c"
- * @param colors The string to check for letters in
- */
-static bool matchesColor(const QString &term, const QString &colors)
+bool FilterItem::acceptColor(const CardInfoPtr &info) const
{
+ const QString colors = info->getColors();
+
// Colorless card filter
- if (term == "c" && colors.length() < 1) {
+ if (convertedColorTerm.toLower() == "c" && colors.length() < 1) {
return true;
}
@@ -378,23 +372,13 @@ static bool matchesColor(const QString &term, const QString &colors)
* then we should match all of them to the card's colors
*/
int match_count = 0;
- for (const QChar &color : term) {
+ for (const QChar &color : convertedColorTerm) {
if (colors.contains(color, Qt::CaseInsensitive)) {
match_count++;
}
}
- return match_count == term.length();
-}
-
-bool FilterItem::acceptColor(const CardInfoPtr &info) const
-{
- return matchesColor(convertedColorTerm, info->getColors());
-}
-
-bool FilterItem::acceptColorIdentity(const CardInfoPtr &info) const
-{
- return matchesColor(convertedColorTerm, info->getColorIdentity());
+ return match_count == convertedColorTerm.length();
}
bool FilterItem::acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const
@@ -556,8 +540,6 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr &info,
return acceptType(info);
case CardFilter::AttrColor:
return acceptColor(info);
- case CardFilter::AttrColorIdentity:
- return acceptColorIdentity(info);
case CardFilter::AttrText:
return acceptText(info, searchLanguage);
case CardFilter::AttrSet:
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h
index f1a5803e5..85a57304d 100644
--- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h
+++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h
@@ -244,7 +244,6 @@ public:
[[nodiscard]] bool acceptMainType(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptSubType(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptColor(const CardInfoPtr &info) const;
- [[nodiscard]] bool acceptColorIdentity(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const;
[[nodiscard]] bool acceptSet(const CardInfoPtr &info) const;
[[nodiscard]] bool acceptManaCost(const CardInfoPtr &info) const;
diff --git a/libcockatrice_utility/CMakeLists.txt b/libcockatrice_utility/CMakeLists.txt
index 98ad40e69..8d2cb5b92 100644
--- a/libcockatrice_utility/CMakeLists.txt
+++ b/libcockatrice_utility/CMakeLists.txt
@@ -8,7 +8,6 @@ set(UTILITY_SOURCES
libcockatrice/utility/passwordhasher.cpp
libcockatrice/utility/report_categories.cpp
libcockatrice/utility/server_rate_limiter.cpp
- libcockatrice/utility/svg_recolor.cpp
libcockatrice/utility/translation_loader.cpp
libcockatrice/utility/user_data_backup.cpp
libcockatrice/utility/warning_categories.cpp
@@ -35,7 +34,6 @@ set(UTILITY_HEADERS
libcockatrice/utility/zone_names.h
libcockatrice/utility/days_years_between.h
libcockatrice/utility/server_rate_limiter.h
- libcockatrice/utility/svg_recolor.h
libcockatrice/utility/translation_loader.h
libcockatrice/utility/user_data_backup.h
libcockatrice/utility/warning_categories.h
diff --git a/libcockatrice_utility/libcockatrice/utility/svg_recolor.cpp b/libcockatrice_utility/libcockatrice/utility/svg_recolor.cpp
deleted file mode 100644
index 041e289fe..000000000
--- a/libcockatrice_utility/libcockatrice/utility/svg_recolor.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-#include "svg_recolor.h"
-
-#include
-
-namespace
-{
-constexpr QByteArrayView SENTINEL = "currentColor";
-
-/// Case-insensitive byte search for the sentinel, starting at @p from.
-/// QByteArray::indexOf() only matches case-sensitively for QByteArrayView, and
-/// its char overload cannot match a word.
-qsizetype findSentinel(const QByteArray &bytes, qsizetype from)
-{
- // QByteArrayView::sliced() is a view, so the scan does not allocate a copy
- // at every byte position (QByteArray::sliced() would).
- const QByteArrayView view(bytes);
- for (qsizetype i = from; i + SENTINEL.size() <= view.size(); ++i) {
- if (view.sliced(i, SENTINEL.size()).compare(SENTINEL, Qt::CaseInsensitive) == 0) {
- return i;
- }
- }
- return -1;
-}
-} // namespace
-
-QByteArray resolveSvgColorSentinel(const QByteArray &svg, const QByteArray &color)
-{
- // The sentinel and the replacement are both ASCII, so stay in bytes: a
- // QString round trip would decode the whole document (and mangle bytes that
- // are not valid UTF-8) even on the no-op path.
- qsizetype pos = findSentinel(svg, 0);
- if (pos < 0) {
- return svg;
- }
-
- QByteArray resolved = svg;
- while (pos >= 0) {
- resolved.replace(pos, SENTINEL.size(), color);
- // Skip past the inserted color: a caller's color never contains the
- // sentinel, and this keeps the scan linear.
- pos = findSentinel(resolved, pos + color.size());
- }
- return resolved;
-}
diff --git a/libcockatrice_utility/libcockatrice/utility/svg_recolor.h b/libcockatrice_utility/libcockatrice/utility/svg_recolor.h
deleted file mode 100644
index 56d609b87..000000000
--- a/libcockatrice_utility/libcockatrice/utility/svg_recolor.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @file svg_recolor.h
- * @ingroup Core
- */
-
-#ifndef SVG_RECOLOR_H
-#define SVG_RECOLOR_H
-
-#include
-
-/**
- * @brief Resolves the `currentColor` sentinel in authored SVG data.
- *
- * Qt's SVG renderer has no CSS cascade or ambient color context, so the
- * sentinel is not resolved by Qt. Callers pass the concrete color they want
- * (for example a QPalette role serialized to #RRGGBB) and receive SVG bytes
- * that are safe to hand to QSvgRenderer.
- *
- * Matching is a plain, case-insensitive substring search, not a CSS parse: an
- * identifier that merely starts with the sentinel (for example
- * `id="currentColorStop"`) is rewritten too. That is acceptable for the assets
- * this project authors, and keeps the resolver dependency-free.
- *
- * @param svg SVG source bytes, expected to be UTF-8. Invalid UTF-8 is preserved
- * byte-for-byte, since the resolver never decodes the document.
- * @param color Concrete SVG color (for example "#ff00ff"), as ASCII bytes.
- * @return The recolored SVG source; the input is left untouched.
- */
-QByteArray resolveSvgColorSentinel(const QByteArray &svg, const QByteArray &color);
-
-#endif // SVG_RECOLOR_H
diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt
index 4ca2acd07..f68f51f23 100644
--- a/oracle/CMakeLists.txt
+++ b/oracle/CMakeLists.txt
@@ -31,7 +31,6 @@ set(oracle_SOURCES
../cockatrice/src/client/network/update/client/release_channel.cpp
../cockatrice/src/interface/pixel_map_generator.cpp
../cockatrice/src/interface/theme_config.cpp
- ../cockatrice/src/interface/theme_icon.cpp
../cockatrice/src/interface/theme_manager.cpp
${VERSION_STRING_CPP}
)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 84b9f02bf..5b7e06c20 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -177,7 +177,6 @@ cockatrice_add_test(NAME test_age_formatting SOURCES test_age_formatting.cpp LIB
cockatrice_add_test(NAME user_data_backup_test SOURCES user_data_backup_test.cpp LIBS libcockatrice_utility)
cockatrice_add_test(NAME password_hash_test SOURCES password_hash_test.cpp LIBS libcockatrice_utility)
cockatrice_add_test(NAME report_categories_test SOURCES report_categories_test.cpp LIBS libcockatrice_utility)
-cockatrice_add_test(NAME svg_recolor_test SOURCES svg_recolor_test.cpp LIBS libcockatrice_utility)
cockatrice_add_test(NAME warning_categories_test SOURCES warning_categories_test.cpp LIBS libcockatrice_utility)
cockatrice_add_test(
NAME
@@ -566,7 +565,6 @@ add_subdirectory(card_zone_algorithms)
add_subdirectory(carddatabase)
add_subdirectory(deck_list_model)
add_subdirectory(deck_list_zones)
-add_subdirectory(icon_assets)
add_subdirectory(loading_from_clipboard)
add_subdirectory(model_contract)
add_subdirectory(movecard_tests)
diff --git a/tests/carddatabase/CMakeLists.txt b/tests/carddatabase/CMakeLists.txt
index 012081fd2..7d83640a1 100644
--- a/tests/carddatabase/CMakeLists.txt
+++ b/tests/carddatabase/CMakeLists.txt
@@ -21,12 +21,10 @@ cockatrice_add_test(
)
# ------------------------
-# Guard to match condition for libcockatrice_filters in the root CMakeLists.txt
+# Filter String Test
+# (guard must match the condition for libcockatrice_filters in the root CMakeLists.txt)
# ------------------------
if(WITH_ORACLE OR WITH_CLIENT)
- # ------------------------
- # Filter String Test
- # ------------------------
cockatrice_add_test(
NAME
filter_string_test
@@ -38,34 +36,6 @@ if(WITH_ORACLE OR WITH_CLIENT)
libcockatrice_filters
)
- # ------------------------
- # Filter Tree Test
- # ------------------------
- cockatrice_add_test(
- NAME
- filter_tree_test
- SOURCES
- ${VERSION_STRING_CPP}
- filter_tree_test.cpp
- mocks.cpp
- LIBS
- libcockatrice_filters
- )
-
- # ------------------------
- # Filter Mode Test
- # ------------------------
- cockatrice_add_test(
- NAME
- filter_mode_test
- SOURCES
- ${VERSION_STRING_CPP}
- filter_mode_test.cpp
- mocks.cpp
- LIBS
- libcockatrice_filters
- )
-
# ------------------------
# Visual Display Model Test
# ------------------------
diff --git a/tests/carddatabase/carddatabase_test.cpp b/tests/carddatabase/carddatabase_test.cpp
index 350d9be7e..b54205f30 100644
--- a/tests/carddatabase/carddatabase_test.cpp
+++ b/tests/carddatabase/carddatabase_test.cpp
@@ -1,4 +1,3 @@
-#include "../support/scoped_file_size_limit.h"
#include "libcockatrice/card/database/card_database.h"
#include "libcockatrice/card/database/card_database_loader.h"
#include "libcockatrice/card/database/card_database_querier.h"
@@ -37,6 +36,12 @@
#include
#include
+#ifdef Q_OS_UNIX
+#include
+#include
+#include
+#endif
+
namespace
{
@@ -242,14 +247,21 @@ TEST(CardDatabaseTest, Xml4ShortWriteKeepsInstalledFile)
ASSERT_EQ(installed.write("installed database"), 18);
installed.close();
- // A real filesystem write failure: the soft file-size cap makes the writer's own
- // flush fail without filling a disk. The cap is scoped rather than forked, because a
- // death-test child under RLIMIT_FSIZE is killed by SIGSEGV on macOS.
- test_support::ScopedFileSizeLimit fileSizeCap(128);
- ASSERT_TRUE(fileSizeCap.isValid());
-
- CockatriceXml4Parser writer(&controller);
- EXPECT_FALSE(writer.saveToFile({}, sets, cards, fileName));
+ // A real filesystem write failure, isolated in the death-test child so the
+ // process-wide file limit never affects other cases or the parent runner.
+ ASSERT_EXIT(
+ {
+ std::signal(SIGXFSZ, SIG_IGN);
+ struct rlimit limit;
+ limit.rlim_cur = 128;
+ limit.rlim_max = 128;
+ if (setrlimit(RLIMIT_FSIZE, &limit) != 0) {
+ _exit(2);
+ }
+ CockatriceXml4Parser writer(&controller);
+ _exit(writer.saveToFile({}, sets, cards, fileName) ? 1 : 0);
+ },
+ ::testing::ExitedWithCode(0), "");
ASSERT_TRUE(installed.open(QIODevice::ReadOnly));
EXPECT_EQ(installed.readAll(), "installed database");
diff --git a/tests/carddatabase/filter_mode_test.cpp b/tests/carddatabase/filter_mode_test.cpp
deleted file mode 100644
index 3fec7e25d..000000000
--- a/tests/carddatabase/filter_mode_test.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-#include "libcockatrice/filters/filter_mode.h"
-
-#include
-
-class FilterModeTest : public ::testing::Test
-{
-protected:
- ~FilterModeTest() override
- {
- for (CardFilter *filter : owned) {
- delete filter;
- }
- }
-
- /** @brief Create a filter owned by the fixture and append it to the set under test. */
- const CardFilter *
- add(const QString &term, CardFilter::Type type, CardFilter::Attr attr = CardFilter::Attr::AttrColor)
- {
- owned.append(new CardFilter(term, type, attr));
- filters.append(owned.last());
- return owned.last();
- }
-
- QList owned;
- QList filters;
-};
-
-TEST(FilterModeInfoTest, EachModeDescribesItsOwnFilters)
-{
- EXPECT_EQ(getFilterModeInfo(FilterMode::Includes).includeType, CardFilter::Type::TypeOr);
- EXPECT_EQ(getFilterModeInfo(FilterMode::Includes).excludeType, CardFilter::Type::TypeEnd);
- EXPECT_EQ(getFilterModeInfo(FilterMode::Includes).attr, CardFilter::Attr::AttrColor);
-
- EXPECT_EQ(getFilterModeInfo(FilterMode::Excludes).includeType, CardFilter::Type::TypeAndNot);
- EXPECT_EQ(getFilterModeInfo(FilterMode::Excludes).excludeType, CardFilter::Type::TypeEnd);
-
- EXPECT_EQ(getFilterModeInfo(FilterMode::IncludeExclude).includeType, CardFilter::Type::TypeOr);
- EXPECT_EQ(getFilterModeInfo(FilterMode::IncludeExclude).excludeType, CardFilter::Type::TypeAndNot);
-
- EXPECT_EQ(getFilterModeInfo(FilterMode::ExactMatch).includeType, CardFilter::Type::TypeAnd);
- EXPECT_EQ(getFilterModeInfo(FilterMode::ExactMatch).excludeType, CardFilter::Type::TypeAndNot);
-
- // Color identity only excludes: the selected colors must be the identity, so nothing is included positively.
- EXPECT_EQ(getFilterModeInfo(FilterMode::ColorIdentity).includeType, CardFilter::Type::TypeEnd);
- EXPECT_EQ(getFilterModeInfo(FilterMode::ColorIdentity).excludeType, CardFilter::Type::TypeAndNot);
- EXPECT_EQ(getFilterModeInfo(FilterMode::ColorIdentity).attr, CardFilter::Attr::AttrColorIdentity);
-
- EXPECT_EQ(getFilterModeInfo(FilterMode::Custom).attr, CardFilter::Attr::AttrEnd);
-}
-
-TEST_F(FilterModeTest, EmptyFilterSetKeepsACompatibleMode)
-{
- // Includes and Color identity both allow an empty set, so the live mode is not swapped for another.
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::Includes), FilterMode::Includes);
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::ColorIdentity), FilterMode::ColorIdentity);
-}
-
-// Include / Exclude and Exact match always hold one filter per color, so an empty set cannot have come from
-// them and falls back to the first mode that can describe it.
-TEST_F(FilterModeTest, EmptyFilterSetFallsBackWhenTheModeNeedsOneFilterPerColor)
-{
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::IncludeExclude), FilterMode::Includes);
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::ExactMatch), FilterMode::Includes);
-}
-
-TEST_F(FilterModeTest, IncludesFromOrFilters)
-{
- add("W", CardFilter::Type::TypeOr);
-
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::Includes), FilterMode::Includes);
- // A single OR filter cannot come from Excludes, so the first mode that can produce it is selected.
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::Excludes), FilterMode::Includes);
-}
-
-TEST_F(FilterModeTest, ExcludesFromAndNotFilters)
-{
- add("W", CardFilter::Type::TypeAndNot);
-
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::Excludes), FilterMode::Excludes);
- // Includes needs an OR or an end filter, so an AND NOT filter selects Excludes.
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::Includes), FilterMode::Excludes);
-}
-
-TEST_F(FilterModeTest, IncludeExcludeNeedsOneFilterPerColor)
-{
- add("W", CardFilter::Type::TypeOr);
- for (const QChar color : QString("UBRG")) {
- add(QString(color), CardFilter::Type::TypeAndNot);
- }
-
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::IncludeExclude), FilterMode::IncludeExclude);
-}
-
-TEST_F(FilterModeTest, IncludeExcludeRejectsAnIncompleteColorSet)
-{
- add("W", CardFilter::Type::TypeOr);
- add("U", CardFilter::Type::TypeAndNot);
-
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::IncludeExclude), FilterMode::Custom);
-}
-
-TEST_F(FilterModeTest, ExactMatchFromAndAndAndNotFilters)
-{
- add("W", CardFilter::Type::TypeAnd);
- for (const QChar color : QString("UBRG")) {
- add(QString(color), CardFilter::Type::TypeAndNot);
- }
-
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::ExactMatch), FilterMode::ExactMatch);
-}
-
-TEST_F(FilterModeTest, ColorIdentityFromIdentityAndNotFilters)
-{
- for (const QChar color : QString("BRG")) {
- add(QString(color), CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColorIdentity);
- }
-
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::ColorIdentity), FilterMode::ColorIdentity);
-}
-
-TEST_F(FilterModeTest, MixedAttributesAreCustom)
-{
- add("W", CardFilter::Type::TypeOr, CardFilter::Attr::AttrColor);
- add("U", CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColorIdentity);
-
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::Includes), FilterMode::Custom);
-}
-
-TEST_F(FilterModeTest, CustomLeavesAsSoonAsAModeCanExplainTheFilters)
-{
- add("W", CardFilter::Type::TypeOr);
-
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::Custom), FilterMode::Includes);
-}
-
-// Exact match with no color selected stores one AND NOT filter per color. That is a legitimate Exact match
-// state, so the live mode is kept; a mode that cannot produce the set re-derives to Excludes, which produces
-// the identical filters.
-TEST_F(FilterModeTest, FullyExcludedColorSetIsExactMatchOrExcludes)
-{
- for (const QChar color : QString("WUBRG")) {
- add(QString(color), CardFilter::Type::TypeAndNot);
- }
-
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::ExactMatch), FilterMode::ExactMatch);
- EXPECT_EQ(deriveFilterMode(filters, FilterMode::Includes), FilterMode::Excludes);
-}
diff --git a/tests/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp
index 931cb7e0c..e561db590 100644
--- a/tests/carddatabase/filter_string_test.cpp
+++ b/tests/carddatabase/filter_string_test.cpp
@@ -123,25 +123,6 @@ CardInfoPtr greenIdentityCard()
return CardInfo::newInstance("Identity Cat", "", false, {{"colors", "G"}, {"coloridentity", "G"}}, {}, {}, {}, {});
}
-// A card whose color identity differs from its colors, so that a query reading the wrong one is visible.
-CardInfoPtr splitIdentityCard()
-{
- return CardInfo::newInstance("Split Identity", "", false, {{"colors", "G"}, {"coloridentity", "W"}}, {}, {}, {},
- {});
-}
-
-// "ci!" (exactly these color identities) used to read the camelCase "colorIdentity" property instead of the
-// lowercase "coloridentity" key, so it matched the empty string and rejected every card.
-TEST_F(CardQuery, ColorIdentityExactReadsTheColorIdentityProperty)
-{
- const CardData card = splitIdentityCard();
- ASSERT_TRUE(FilterString("ci!w").check(card));
- ASSERT_FALSE(FilterString("ci!g").check(card));
- // "c!" still reads colors, not identity.
- ASSERT_TRUE(FilterString("c!g").check(card));
- ASSERT_FALSE(FilterString("c!w").check(card));
-}
-
// Whitespace belongs around the separator, never inside an operator token: "c i: g" must not
// be read as the color-identity operator "ci" (it is a generic "c" AND a field query on "i").
TEST_F(CardQuery, ColorIdentityOperatorIsNotSplitByWhitespace)
diff --git a/tests/carddatabase/filter_tree_test.cpp b/tests/carddatabase/filter_tree_test.cpp
deleted file mode 100644
index 8d3706071..000000000
--- a/tests/carddatabase/filter_tree_test.cpp
+++ /dev/null
@@ -1,204 +0,0 @@
-
-#include "libcockatrice/filters/filter_tree.h"
-
-#include
-#include
-#include
-
-namespace
-{
-class FilterTreeTest : public ::testing::Test
-{
-protected:
- void SetUp() override
- {
- language = CardSearchLanguage{};
-
- island = CardInfo::newInstance("Island", "", false, {{"colors", ""}, {"coloridentity", "U"}}, {}, {}, {}, {});
- artifact =
- CardInfo::newInstance("Artifact", "", false, {{"colors", ""}, {"coloridentity", ""}}, {}, {}, {}, {});
- truth = CardInfo::newInstance("Truth", "", false, {{"colors", "U"}, {"coloridentity", "U"}}, {}, {}, {}, {});
- notDead =
- CardInfo::newInstance("Not Dead", "", false, {{"colors", "B"}, {"coloridentity", "B"}}, {}, {}, {}, {});
- wallOfDenial = CardInfo::newInstance("Wall of Denial", "", false, {{"colors", "WU"}, {"coloridentity", "WU"}},
- {}, {}, {}, {});
- maelstromAngel = CardInfo::newInstance("Maelstrom Archangel", "", false,
- {{"colors", "WUBRG"}, {"coloridentity", "WUBRG"}}, {}, {}, {}, {});
- }
-
- CardSearchLanguage language;
-
- CardData island; // c/u
- CardData artifact; // c
- CardData truth; // u
- CardData notDead; // b
- CardData wallOfDenial; // wu
- CardData maelstromAngel; // wubrg
-};
-} // namespace
-
-// =====================================================================================================================
-// acceptColor
-// =====================================================================================================================
-
-TEST_F(FilterTreeTest, Color)
-{
- FilterTree filterTree;
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "wu");
- ASSERT_FALSE(filterTree.acceptsCard(island, language));
- ASSERT_FALSE(filterTree.acceptsCard(artifact, language));
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
- ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
- ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
-}
-
-TEST_F(FilterTreeTest, ColorColorless)
-{
- FilterTree filterTree;
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "c");
- ASSERT_TRUE(filterTree.acceptsCard(island, language));
- ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
- ASSERT_FALSE(filterTree.acceptsCard(wallOfDenial, language));
- ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
-}
-
-TEST_F(FilterTreeTest, ColorNot)
-{
- FilterTree filterTree;
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAndNot, "b");
- ASSERT_TRUE(filterTree.acceptsCard(island, language));
- ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
- ASSERT_TRUE(filterTree.acceptsCard(truth, language));
- ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
- ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
- ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
-}
-
-TEST_F(FilterTreeTest, ColorAliases)
-{
- FilterTree filterTree;
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "green");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "grn");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "blue");
- ASSERT_TRUE(filterTree.acceptsCard(truth, language));
- ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "blu");
- ASSERT_TRUE(filterTree.acceptsCard(truth, language));
- ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "black");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(notDead, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "blk");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(notDead, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "red");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "white");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "wht");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "colorless");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "colourless");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
- filterTree.clear();
-
- filterTree.termNode(CardFilter::Attr::AttrColor, CardFilter::Type::TypeAnd, "none");
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
- filterTree.clear();
-}
-
-// =====================================================================================================================
-// acceptColorIdentity
-// =====================================================================================================================
-
-TEST_F(FilterTreeTest, ColorIdentity)
-{
- FilterTree filterTree;
- filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAnd, "wu");
- ASSERT_FALSE(filterTree.acceptsCard(island, language));
- ASSERT_FALSE(filterTree.acceptsCard(artifact, language));
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
- ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
- ASSERT_TRUE(filterTree.acceptsCard(maelstromAngel, language));
-}
-
-TEST_F(FilterTreeTest, ColorIdentityColorless)
-{
- FilterTree filterTree;
- filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAnd, "c");
- ASSERT_FALSE(filterTree.acceptsCard(island, language));
- ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
- ASSERT_FALSE(filterTree.acceptsCard(truth, language));
- ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
- ASSERT_FALSE(filterTree.acceptsCard(wallOfDenial, language));
- ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
-}
-
-TEST_F(FilterTreeTest, ColorIdentityNot)
-{
- FilterTree filterTree;
- filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAndNot, "b");
- ASSERT_TRUE(filterTree.acceptsCard(island, language));
- ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
- ASSERT_TRUE(filterTree.acceptsCard(truth, language));
- ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
- ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
- ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
-}
-
-TEST_F(FilterTreeTest, ColorIdentityMultiple)
-{
- // Simulate a WU commander.
- FilterTree filterTree;
- filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAndNot, "b");
- filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAndNot, "r");
- filterTree.termNode(CardFilter::Attr::AttrColorIdentity, CardFilter::Type::TypeAndNot, "g");
- ASSERT_TRUE(filterTree.acceptsCard(island, language));
- ASSERT_TRUE(filterTree.acceptsCard(artifact, language));
- ASSERT_TRUE(filterTree.acceptsCard(truth, language));
- ASSERT_FALSE(filterTree.acceptsCard(notDead, language));
- ASSERT_TRUE(filterTree.acceptsCard(wallOfDenial, language));
- ASSERT_FALSE(filterTree.acceptsCard(maelstromAngel, language));
-}
-
-int main(int argc, char **argv)
-{
- ::testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
-}
diff --git a/tests/icon_assets/CMakeLists.txt b/tests/icon_assets/CMakeLists.txt
deleted file mode 100644
index da9776f8e..000000000
--- a/tests/icon_assets/CMakeLists.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-cockatrice_add_test(
- NAME icon_assets_test SOURCES icon_assets_test.cpp DEFINITIONS COCKATRICE_SOURCE_DIR="${CMAKE_SOURCE_DIR}"
-)
diff --git a/tests/icon_assets/icon_assets_test.cpp b/tests/icon_assets/icon_assets_test.cpp
deleted file mode 100644
index f0b06d5cc..000000000
--- a/tests/icon_assets/icon_assets_test.cpp
+++ /dev/null
@@ -1,241 +0,0 @@
-#include
-#include
-#include
-#include