diff --git a/.ci/compile.sh b/.ci/compile.sh index 1db0cacd1..9be8ba741 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -175,9 +175,9 @@ fi if [[ $MAKE_SANITIZE ]]; then flags+=("-DENABLE_SANITIZERS=ON") else - # Every leg states the flag explicitly rather than relying on the CMake - # default, so a build directory reused from a sanitized configuration can - # never leak libasan/libubsan into a shipped binary. + # The release-package step re-runs cmake in the same build dir, so an + # ENABLE_SANITIZERS=ON left over from the debug step would otherwise leak + # into the shipped binaries (libasan/libubsan Depends in the DEB). flags+=("-DENABLE_SANITIZERS=OFF") fi if [[ $MAKE_COVERAGE ]]; then diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index d72cb881e..df054ea26 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -33,18 +33,10 @@ on: - 'CMakeLists.txt' - 'vcpkg.json' - 'vcpkg' # needed to match submodule bumps (gitlink) - # The legs the pull-request matrix leaves out run here instead, so the legacy distros and - # the slow macOS runner stay covered without gating a pull request on them. Tags still - # build every leg, because a tag is what ships. - schedule: - - cron: '23 3 * * *' # Cancel earlier, unfinished runs of this workflow on the same branch (unless on tag --> release) concurrency: - # The event name is part of the group because a nightly run and a push run on master share - # refs/heads/master but must not cancel each other: the nightly would drop the push's vcpkg - # import, and a merge landing during the nightly would drop that night's coverage. - group: "${{ github.workflow }} @ ${{ github.ref_name }} (${{ github.event_name }})" + group: "${{ github.workflow }} @ ${{ github.ref_name }}" cancel-in-progress: ${{ github.ref_type != 'tag' }} jobs: @@ -149,7 +141,6 @@ jobs: allow-failure: yes package: skip # We are packaged in Arch already - debug_test: yes # see the note on Fedora 44 below - distro: Servatrice_Debian version: 12 @@ -158,47 +149,59 @@ jobs: server_only: yes test: skip + - distro: Debian + version: 12 + + package: DEB + test: skip # Running tests on all distros is superfluous + - distro: Debian version: 13 package: DEB + - distro: Fedora + version: 43 + + package: RPM + test: skip # Running tests on all distros is superfluous + - distro: Fedora version: 44 package: RPM - # 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 + + - distro: Ubuntu + version: 24.04 + + package: DEB + test: skip # Running tests on all distros is superfluous - distro: Ubuntu version: 26.04 package: DEB - test: skip # The sanitizer leg below runs this distro's suite - # The one leg that runs the suite under AddressSanitizer and - # 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 - # carrying the package build here meant 18 minutes of Release compile queued - # behind the instrumented one in a single job. The leg above publishes this - # distro's DEB. + # The one leg that runs the suite under AddressSanitizer and + # UndefinedBehaviorSanitizer. Debian 13 and Fedora 44 keep covering the + # uninstrumented suite, so nothing is lost by instrumenting this one. + sanitize: yes + - distro: Ubuntu version: 26.04 - variant: sanitize # separate image and ccache, same distro + variant: cover # separate image and ccache, same distro + # The one leg that runs instrumented for line coverage and publishes the report as + # a workflow artifact. It is a separate leg rather than a second flag on the + # sanitizer leg because the two instruments answer different questions: the + # sanitizer leg reports what the run did wrong, the coverage leg reports what the + # run never touched at all. Combining them would also slow both down. Packaging is + # skipped because the leg above already publishes the DEB for this distro. + coverage: yes package: skip - sanitize: yes name: ${{ matrix.distro }} ${{ matrix.version }}${{ matrix.variant && format(' {0}', matrix.variant) || '' }} needs: configure - # The nightly legs live in build-linux-nightly below. A leg is in exactly one of the two - # matrices, so nothing that reviews a change waits for a distro nobody is changing for. - if: github.event_name != 'schedule' runs-on: ubuntu-latest # https://github.com/actions/runner-images continue-on-error: ${{ matrix.allow-failure == 'yes' }} timeout-minutes: 70 @@ -209,18 +212,8 @@ jobs: CMAKE_GENERATOR: 'Ninja' NAME: ${{ matrix.distro }}${{ matrix.version }} IMAGE_SUFFIX: ${{ matrix.variant }} - # Which build the suite runs in, decided once per leg here so the steps' if: conditions - # and their shells cannot drift apart. A step's own env is not visible to its if:, which - # is why these live at job level rather than on the release step. - # DEBUG_TEST the instrumented legs (symbols and instrumentation matter) and the two - # newest distro toolchains, which only fail on a warning in Debug. - # RUN_TESTS every other leg that tests runs the suite against the release build it may - # 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') }} - # Shared with build-linux-nightly so the two leg sets run the same steps. - steps: &linux-steps + steps: - name: "Checkout" uses: actions/checkout@v7 @@ -235,11 +228,8 @@ jobs: shell: bash run: source .ci/docker.sh --build - # The instrumented legs and the two newest distro toolchains build and test Debug; every - # other leg runs its suite against the release build below, so testing and packaging cost - # one compile instead of two. - name: "Build debug and test" - if: env.DEBUG_TEST == 'true' + if: matrix.test != 'skip' shell: bash env: sanitize: '${{ matrix.sanitize }}' @@ -279,23 +269,15 @@ jobs: path: coverage/ if-no-files-found: warn - # One build serves both purposes: the suite runs against the same configuration that - # gets packaged, so a packaging leg no longer pays for a full Debug compile it then - # throws away. macOS and Windows have always tested their release build. - # - # Instrumented and debug-test legs reach this step only when they also package; their - # suite already ran in the debug step above, so the tests are not repeated here. - - name: "Build release, test and package" + - name: "Build release package" id: build - if: matrix.package != 'skip' || env.RUN_TESTS == 'true' + if: matrix.package != 'skip' shell: bash env: SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}' package: '${{ matrix.package }}' server_only: '${{ matrix.server_only }}' - # Only a leg that packages builds the shipping configuration; a release build that - # only runs tests stays free of the crash reporting SDK. - WITH_CRASH_REPORTING: ${{ matrix.package != 'skip' && 'ON' || '' }} + WITH_CRASH_REPORTING: 'ON' # Tag builds only: artifacts from master pushes and PR branches must not report to # Sentry. Forks get no secrets on pull_request, so they keep the mock fallback either way. # configure.outputs.tag is set for ANY tag, so beta (Development) tags get the DSN @@ -305,7 +287,6 @@ jobs: source .ci/docker.sh args=() [[ $server_only == yes ]] && args+=(--no-client) - [[ $RUN_TESTS == true ]] && args+=(--test) [[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE") args+=(--ccache "$CCACHE_SIZE") args+=(--cmake-generator "$CMAKE_GENERATOR") @@ -315,14 +296,7 @@ jobs: # configures, and empty keeps the mock fallback. export RUN_ARGS="${RUN_ARGS:-} -e COCKATRICE_SENTRY_DSN" - # --package takes an optional type, so the flag and its argument have to travel - # together or the type would become the next flag. - package_args=() - [[ -n $package && $package != skip ]] && package_args+=(--package "$package") - - # compile.sh runs the tests before the package target, so a failing suite stops the - # leg before anything is built into an installer. - RUN --server --release "${package_args[@]}" "${args[@]}" + RUN --server --release --package "$package" "${args[@]}" - name: "Install sentry-cli" if: matrix.package != 'skip' && needs.configure.outputs.tag != null @@ -424,72 +398,30 @@ jobs: GH_TOKEN: ${{ github.token }} run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice - build-linux-nightly: - strategy: - fail-fast: false - matrix: - include: - - distro: Debian - version: 12 # previous stable, still covered by the nightly - - package: DEB - test: skip # Running tests on all distros is superfluous - - - distro: Fedora - version: 43 # previous release, still covered by the nightly - - package: RPM - test: skip # Running tests on all distros is superfluous - - - distro: Ubuntu - version: 24.04 # previous LTS, still covered by the nightly - - package: DEB - test: skip # Running tests on all distros is superfluous - - # The one leg that runs instrumented for line coverage and publishes the report as - # a workflow artifact. It stays a separate leg rather than a second flag on the - # sanitizer leg because the two instruments answer different questions: the - # sanitizer leg reports what the run did wrong, the coverage leg reports what the - # run never touched at all. Combining them would also slow both down. - # - # It moved here from build-linux: gcovr.cfg sets no fail_under, so the leg cannot - # fail a build, and nothing consumes the report per change, which made it an - # artifact nobody read on every pull request. On the default branch it becomes a - # trend instead. Image and ccache keys come from distro, version and variant, so - # they are unchanged and the warm cache that leg built still matches. - - distro: Ubuntu - version: 26.04 - variant: cover # separate image and ccache, same distro - - coverage: yes - package: skip - # A report must not redden the nightly run: vcpkg-cache-import only imports the - # archives of a run whose conclusion is success. - allow-failure: yes - - name: ${{ matrix.distro }} ${{ matrix.version }}${{ matrix.variant && format(' {0}', matrix.variant) || '' }} - needs: configure - # A nightly and a tag build cover the legs a pull request or a merge does not wait for: - # the previous release of each distro, and the one instrument that only reports. - if: github.event_name == 'schedule' || github.ref_type == 'tag' - runs-on: ubuntu-latest # https://github.com/actions/runner-images - continue-on-error: ${{ matrix.allow-failure == 'yes' }} - timeout-minutes: 70 - env: - CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} # directory for caching docker image and ccache - CCACHE_EVICTION_AGE: 7d - CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy - CMAKE_GENERATOR: 'Ninja' - NAME: ${{ matrix.distro }}${{ matrix.version }} - IMAGE_SUFFIX: ${{ matrix.variant }} - steps: *linux-steps - build-vcpkg: strategy: fail-fast: false matrix: include: + - os: macOS + target: 13 # EOL 2025-09-15 + runner: macos-15-intel # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md + # macos-26-intel is very slow and fails in CPack during DMG config if not increasing Finder timeout + + ccache_eviction_age: 7d + cmake_generator: Ninja + make_package: 1 + override_target: 13 + package_suffix: "-macOS13_Intel" + qt_version: 6.11.* + qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets + soc: Intel + type: Release + use_ccache: 1 + vcpkg_scope: 'vcpkg/macos13-x64-xcode26.3/' + vcpkg_upload: 1 # only one leg per vcpkg_scope hands its archives over + xcode: "26.3" + - os: macOS target: 14 # EOL 2026-?? runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md @@ -565,16 +497,13 @@ jobs: name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} needs: [configure, vcpkg-baseline] - # The nightly leg lives in build-vcpkg-nightly below, next to the Linux nightly legs. - if: github.event_name != 'schedule' runs-on: ${{ matrix.runner }} timeout-minutes: 100 env: CCACHE_DIR: ${{ github.workspace }}/.cache/ CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy - # Shared with build-vcpkg-nightly so the two leg sets run the same steps. - steps: &vcpkg-steps + steps: - name: "Checkout" uses: actions/checkout@v7 with: @@ -1049,42 +978,6 @@ jobs: GH_TOKEN: ${{ github.token }} run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice - build-vcpkg-nightly: - strategy: - fail-fast: false - matrix: - include: - - os: macOS - target: 13 # EOL 2025-09-15 - runner: macos-15-intel # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md - # macos-26-intel is very slow and fails in CPack during DMG config if not increasing Finder timeout - - ccache_eviction_age: 7d - cmake_generator: Ninja - make_package: 1 - override_target: 13 - package_suffix: "-macOS13_Intel" - qt_version: 6.11.* - qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets - soc: Intel - type: Release - use_ccache: 1 - vcpkg_scope: 'vcpkg/macos13-x64-xcode26.3/' - vcpkg_upload: 1 # only one leg per vcpkg_scope hands its archives over - xcode: "26.3" - - name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} - needs: configure - # The EOL target on the slowest runner is too slow to gate a change, so it runs in the - # nightly and on tags only, where the vcpkg cache import also has to accept schedule runs. - if: github.event_name == 'schedule' || github.ref_type == 'tag' - runs-on: ${{ matrix.runner }} - timeout-minutes: 100 - env: - CCACHE_DIR: ${{ github.workspace }}/.cache/ - CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy - steps: *vcpkg-steps - prune-caches: name: Prune caches # Only master writes caches at all, so only master may clean them up too @@ -1103,9 +996,8 @@ jobs: shell: bash env: GH_TOKEN: ${{ github.token }} - # Must list every vcpkg_scope of the build-vcpkg and build-vcpkg-nightly - # matrices above, or the entries of a scope that is still in use get deleted - # on the next run. + # Must list every vcpkg_scope of the build-vcpkg matrix above, or the entries of + # a scope that is still in use get deleted on the next run. LIVE_VCPKG_SCOPES: 'vcpkg/macos13-x64-xcode26.3/ vcpkg/macos14-arm64-xcode26.6/ vcpkg/windows10-x64-vs2026/' run: | gh api --paginate "repos/$GITHUB_REPOSITORY/actions/caches?per_page=100" \ diff --git a/.github/workflows/vcpkg-cache-import.yml b/.github/workflows/vcpkg-cache-import.yml index 0a377a8fd..3cc7b2203 100644 --- a/.github/workflows/vcpkg-cache-import.yml +++ b/.github/workflows/vcpkg-cache-import.yml @@ -4,14 +4,12 @@ name: Import vcpkg cache # rebuilds protobuf, openssl and abseil from source and saves them into the shared # cache, where every later run can restore them. # -# Only master pushes and the nightly schedule run on master are imported here: the -# collect step, keys.txt and the zip contents all come from the workflow run being -# imported, so importing from an unmerged pull request would let anyone who can push -# a branch — or a bot actor such as Dependabot, whose PRs are same-repo — promote -# arbitrary binaries into the scope that master and tag builds restore and link into -# signed releases. Forks build untrusted code and must never reach that scope either. -# A scheduled run is the default branch at the commit a master push already built, so -# it carries the same trust as a push. +# Only master pushes are imported here: the collect step, keys.txt and the zip +# contents all come from the workflow run being imported, so importing from an +# unmerged pull request would let anyone who can push a branch — or a bot actor +# such as Dependabot, whose PRs are same-repo — promote arbitrary binaries into +# the scope that master and tag builds restore and link into signed releases. +# Forks build untrusted code and must never reach that scope either. # # repository_dispatch is not used on purpose: it would also resolve to the default branch, # but it counts as a low-trust trigger and may only write there when it declares a @@ -40,7 +38,7 @@ jobs: # the shared scope even if they somehow target the default branch. if: >- github.event.workflow_run.conclusion == 'success' && - (github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'schedule') && + github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'master' && github.event.workflow_run.head_repository.full_name == github.repository && !endsWith(github.event.workflow_run.actor.login, '[bot]') && diff --git a/.gitmodules b/.gitmodules index e69c8e686..732fec7ca 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "doxygen-awesome-css"] path = doc/doxygen/theme url = https://github.com/jothepro/doxygen-awesome-css.git -[submodule "external/qttesting"] - path = external/qttesting - url = https://github.com/Kitware/QtTesting.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 563f174e8..677ef0c12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,6 @@ option(WITH_CLIENT "Build Cockatrice client" ON) option(WITH_ORACLE "Build Cockatrice card database tool (Oracle)" ON) # Compile Servatrice option(WITH_SERVER "Build Cockatrice server (Servatrice)" OFF) -# Compile GUI tests (QtTesting widget-level playback) -option(WITH_GUI_TEST "Build the QtTesting-based GUI test harness" OFF) # Compile tests option(TEST "Build tests" OFF) # Build with AddressSanitizer and UndefinedBehaviorSanitizer @@ -31,14 +29,6 @@ option(ENABLE_COVERAGE "Build instrumented for GCC line coverage" OFF) # Use vcpkg regardless of OS option(USE_VCPKG "Use vcpkg regardless of OS" OFF) -# The GUI test harness lives in a submodule; a checkout without it must still configure. -if(WITH_GUI_TEST AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/qttesting/CMakeLists.txt") - message(WARNING "WITH_GUI_TEST is ON but external/qttesting is missing, so GUI tests are disabled.\n" - "Run 'git submodule update --init external/qttesting' to enable them." - ) - set(WITH_GUI_TEST OFF) -endif() - # Default to "Release" build type # User-provided value for CMAKE_BUILD_TYPE must be checked before the PROJECT() call if(DEFINED CMAKE_BUILD_TYPE) @@ -473,34 +463,6 @@ if(WITH_ORACLE) set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) endif() -if(WITH_GUI_TEST) - set(QtTesting_QT_VERSION "6") - # QtTesting registers its own unit tests when BUILD_TESTING is on. They target the - # Qt version the harness was written for and fail on Qt 6.11 (toolbutton - # long-press recording), and they are not Cockatrice's tests, so suppress them - # for the duration of the subdirectory instead of painting ctest red. - if(DEFINED BUILD_TESTING) - set(_cockatrice_saved_build_testing ${BUILD_TESTING}) - endif() - set(BUILD_TESTING - OFF - CACHE BOOL "Build the testing tree." FORCE - ) - add_subdirectory(external/qttesting) - if(DEFINED _cockatrice_saved_build_testing) - set(BUILD_TESTING - ${_cockatrice_saved_build_testing} - CACHE BOOL "Build the testing tree." FORCE - ) - else() - unset(BUILD_TESTING CACHE) - endif() - # cl.exe has no warnings-as-errors here, and it rejects -Wno-error with D8021. - if(NOT MSVC) - target_compile_options(qttesting PRIVATE -Wno-error) - endif() -endif() - if(TEST) include(CTest) add_subdirectory(tests) diff --git a/Testing/README.md b/Testing/README.md deleted file mode 100644 index cb990ec29..000000000 --- a/Testing/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# GUI tests - -Widget-level playback tests for the Cockatrice client, driven by -[QtTesting](https://github.com/Kitware/QtTesting) (`external/qttesting`, a submodule). -Each `*_test.xml` file is a list of events that QtTesting replays against the running -application; a test passes when every event is handled without error. - -## Running - -```bash -git submodule update --init external/qttesting # once -cmake -S . -B build/debug -DCMAKE_BUILD_TYPE=Debug -DWITH_SERVER=0 -DWITH_GUI_TEST=ON -cmake --build build/debug -j 24 - -./Testing/run_test.sh --all # headless, offscreen -./Testing/run_test.sh Testing/smoke_test.xml # one file -./Testing/run_test.sh --visual Testing/smoke_test.xml # shows the window -``` - -`WITH_GUI_TEST` is on by default; a checkout without the submodule configures with a -warning and no harness. `COCKATRICE_BIN` points the runner at another build, and -`TEST_TIMEOUT` (seconds, default 180) bounds a single test — a failing event inside a -modal dialog can otherwise leave the process alive. The client also hard-exits a few -seconds after playback returns, whatever the result. - -The tests are **not** part of `ctest`: they need a card database and a developer's -settings, so they stay a local loop for now. - -## Caveats - -- **Not hermetic.** The client reads the local settings and card database, so a test can - pass on one machine and fail on another. Avoid asserting things that restored - workspace state or user preferences decide (for example the number of open tabs). -- **English labels only.** Menu entries are addressed by their (translated) text, so the - tests expect the source language. Widgets are addressed by objectName paths, which are - stable across translations. -- Actions that open a dialog are followed by a `pause` event: playback continues in the - dialog's own event loop, and the pause gives the UI time to build itself. -- Widget paths include Qt-internal names where a QTabWidget is involved - (`qt_tabwidget_stackedwidget`), and the deck views in a game are indexed - (`tabbedDeckView_0`, `tabbedDeckView_1`). - -## Writing a test - -A test is a `` list. Widgets are named by their path from the main window, e.g. -`MainWindow/tabSupervisor/stackedWidget/TabGame/phasesMenu`. Useful commands: - -| Command | Arguments | Effect | -|---------|-----------|--------| -| `activate` | action or menu name | triggers a QAction (menus are opened first) | -| `key` | Qt key code | presses that key on the focused widget | -| `pause` | milliseconds | waits, letting the UI settle | -| `clickCard` | `card,zone` | double click the card found in that zone (plays it from hand) | -| `flipCard` | `card,zone` | right-click → *Turn Over* | -| `cardAction` | `card,zone,label` | right-click → first menu entry whose label contains `label` (submenus included) | -| `moveCard` | `card,srcZone,dstZone` | drags the card onto the destination zone | -| `drawArrow` | `srcCard,srcZone,dstCard,dstZone` | right-drag an arrow between two cards | -| `createToken` | `name,color,pt` | creates a token as the active local player | -| `selectItem` | `item label` | selects the tree/list entry with that label | -| `typeText` | `text` | types text one key event at a time | -| `expectProperty` | `property,value` | fails playback unless the widget property matches | - -The card, item and property commands are `pqCockatriceEventPlayer` -(`cockatrice/src/game_graphics/pq_cockatrice_event_player.h`). They resolve cards by name -and zone through the `QGraphicsScene` and send real mouse events at the card's current -viewport position, so a test does not depend on the window size or screen resolution. diff --git a/Testing/card_actions_test.xml b/Testing/card_actions_test.xml deleted file mode 100644 index 8d6de4c2b..000000000 --- a/Testing/card_actions_test.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Testing/deck_editor_open_test.xml b/Testing/deck_editor_open_test.xml deleted file mode 100644 index 437c4bd23..000000000 --- a/Testing/deck_editor_open_test.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/Testing/deck_editor_search_test.xml b/Testing/deck_editor_search_test.xml deleted file mode 100644 index 0b9f88fa1..000000000 --- a/Testing/deck_editor_search_test.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Testing/dialog_lifecycle_test.xml b/Testing/dialog_lifecycle_test.xml deleted file mode 100644 index 00510b802..000000000 --- a/Testing/dialog_lifecycle_test.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/Testing/game_mechanics_test.xml b/Testing/game_mechanics_test.xml deleted file mode 100644 index efef78707..000000000 --- a/Testing/game_mechanics_test.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Testing/integration_test.xml b/Testing/integration_test.xml deleted file mode 100644 index 5fc7a971a..000000000 --- a/Testing/integration_test.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Testing/local_game_lifecycle_test.xml b/Testing/local_game_lifecycle_test.xml deleted file mode 100644 index bdc964921..000000000 --- a/Testing/local_game_lifecycle_test.xml +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Testing/menu_open_close_test.xml b/Testing/menu_open_close_test.xml deleted file mode 100644 index d894b47b5..000000000 --- a/Testing/menu_open_close_test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Testing/nonmodal_manage_sets_test.xml b/Testing/nonmodal_manage_sets_test.xml deleted file mode 100644 index 5fe2bfc0c..000000000 --- a/Testing/nonmodal_manage_sets_test.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Testing/nonmodal_tip_test.xml b/Testing/nonmodal_tip_test.xml deleted file mode 100644 index 0f4ffc825..000000000 --- a/Testing/nonmodal_tip_test.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Testing/nonmodal_view_log_test.xml b/Testing/nonmodal_view_log_test.xml deleted file mode 100644 index 3369f1fd5..000000000 --- a/Testing/nonmodal_view_log_test.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Testing/run_test.sh b/Testing/run_test.sh deleted file mode 100755 index 8ff3fe028..000000000 --- a/Testing/run_test.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/bash -# Run QtTesting XML test files against the Cockatrice build. -# -# Usage: -# ./Testing/run_test.sh [--visual] [--all | ...] -# -# Options: -# --visual Run with native display (shows the GUI window). -# Default is headless (QT_QPA_PLATFORM=offscreen). -# --all Run every *.xml file in Testing/. -# -# Environment: -# COCKATRICE_BIN Path to the client binary (default: build/debug/cockatrice/cockatrice, -# or the .app bundle binary on macOS). -# TEST_TIMEOUT Seconds a single test may run before it is killed (default: 180). -# Uses timeout, or gtimeout from coreutils on macOS. -# -# Requires the debug build to exist at build/debug/cockatrice/cockatrice. - -set -euo pipefail -shopt -s nullglob - -REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" -if [ -n "${COCKATRICE_BIN:-}" ]; then - COCKATRICE="$COCKATRICE_BIN" -elif [ "$(uname -s)" = "Darwin" ]; then - # A macOS build produces an app bundle, not a bare executable. - COCKATRICE="$REPO_ROOT/build/debug/cockatrice/cockatrice.app/Contents/MacOS/cockatrice" -else - COCKATRICE="$REPO_ROOT/build/debug/cockatrice/cockatrice" -fi -TEST_DIR="$REPO_ROOT/Testing" -TEST_TIMEOUT="${TEST_TIMEOUT:-180}" - -# Stock macOS has no `timeout`; coreutils provides `gtimeout`. Without either the -# runner still works, but a hung test is not killed. -TIMEOUT=() -if command -v timeout >/dev/null 2>&1; then - TIMEOUT=(timeout --foreground "$TEST_TIMEOUT") -elif command -v gtimeout >/dev/null 2>&1; then - TIMEOUT=(gtimeout --foreground "$TEST_TIMEOUT") -else - echo "Warning: neither timeout nor gtimeout found; tests are not time-limited." >&2 -fi - -if [ ! -f "$COCKATRICE" ]; then - echo "Error: $COCKATRICE not found. Build first:" >&2 - echo " cmake --build build/debug" >&2 - exit 1 -fi - -VISUAL=0 -if [ "${1:-}" = "--visual" ]; then - VISUAL=1 - shift -fi - -if [ "${1:-}" = "--all" ]; then - shift - TEST_FILES=("$TEST_DIR"/*.xml) - if [ ${#TEST_FILES[@]} -eq 0 ]; then - echo "No *.xml files found in $TEST_DIR" >&2 - exit 1 - fi -elif [ $# -ge 1 ]; then - TEST_FILES=("$@") -else - echo "Usage: $0 [--visual] [--all | ...]" >&2 - echo " Run a single test: $0 Testing/smoke_test.xml" >&2 - echo " Run all tests: $0 --all" >&2 - echo " Run visually: $0 --visual Testing/smoke_test.xml" >&2 - echo " Run all visually: $0 --visual --all" >&2 - exit 1 -fi - -PASS=0 -FAIL=0 -FAILED_TESTS=() - -run_one() { - local file="$1" - - local -a cmd=() - if [ "${#TIMEOUT[@]}" -gt 0 ]; then - cmd=("${TIMEOUT[@]}") - fi - cmd+=("$COCKATRICE" --no-connect --test "$file") - - if [ "$VISUAL" -eq 1 ]; then - "${cmd[@]}" - else - env QT_QPA_PLATFORM=offscreen "${cmd[@]}" - fi -} - -for f in "${TEST_FILES[@]}"; do - name="$(basename "$f")" - printf "%-40s " "$name" - if run_one "$f"; then - echo "PASS" - PASS=$((PASS + 1)) - else - echo "FAIL" - FAIL=$((FAIL + 1)) - FAILED_TESTS+=("$name") - fi -done - -TOTAL=$((PASS + FAIL)) -echo "" -echo "Results: $PASS/$TOTAL passed, $FAIL failed" -if [ "$FAIL" -gt 0 ]; then - echo "Failed:" - for t in "${FAILED_TESTS[@]}"; do - echo " $t" - done - exit 1 -fi diff --git a/Testing/settings_dialog_test.xml b/Testing/settings_dialog_test.xml deleted file mode 100644 index 2dd9dda0a..000000000 --- a/Testing/settings_dialog_test.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Testing/settings_navigation_test.xml b/Testing/settings_navigation_test.xml deleted file mode 100644 index c112966e0..000000000 --- a/Testing/settings_navigation_test.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Testing/settings_search_test.xml b/Testing/settings_search_test.xml deleted file mode 100644 index ba60251f0..000000000 --- a/Testing/settings_search_test.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Testing/smoke_test.xml b/Testing/smoke_test.xml deleted file mode 100644 index a31edbdd4..000000000 --- a/Testing/smoke_test.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 9a3172aa8..642d998a0 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -154,7 +154,6 @@ set(cockatrice_SOURCES src/interface/palette_editor/palette_generator.cpp src/interface/palette_editor/quick_setup_panel.cpp src/interface/palette_editor/palette_grid_widget.cpp - src/interface/palette_editor/font_grid_widget.cpp src/interface/palette_editor/palette_editor_dialog.cpp src/interface/widgets/cards/additional_info/color_identity_widget.cpp src/interface/widgets/cards/additional_info/deck_color_identity.cpp @@ -450,8 +449,6 @@ set(cockatrice_SOURCES src/interface/intents/intent_open_shared_deck.h src/interface/intents/url_parser.cpp src/interface/intents/url_parser.h - src/interface/widgets/server/user/user_games_model.cpp - src/interface/widgets/server/user/user_games_model.h src/interface/widgets/server/user/user_info_popup.cpp src/interface/widgets/server/user/user_info_popup.h ) @@ -567,14 +564,6 @@ target_link_libraries( cockatrice_target_enable_crash_reporting(cockatrice) -if(WITH_GUI_TEST) - # These two translate between XML test files and the QtTesting event pipeline; - # they need the QtTesting headers, so they only exist with the harness. - target_sources(cockatrice PRIVATE src/game_graphics/pq_cockatrice_event_player.cpp src/interface/recording.cpp) - target_link_libraries(cockatrice PRIVATE QtTesting) - target_compile_definitions(cockatrice PRIVATE WITH_GUI_TEST) -endif() - if(UNIX) if(APPLE) set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME}") diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index e4b845af9..7ac0d9ca4 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -10,7 +10,6 @@ # or set = false to disable logging #main = true -#recording = true #qt_translator = true #window_main.* = true #release_channel = true diff --git a/cockatrice/src/client/network/interfaces/deck_analysis_url.h b/cockatrice/src/client/network/interfaces/deck_analysis_url.h deleted file mode 100644 index 425056270..000000000 --- a/cockatrice/src/client/network/interfaces/deck_analysis_url.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file deck_analysis_url.h - * @ingroup ApiInterfaces - */ - -#ifndef DECK_ANALYSIS_URL_H -#define DECK_ANALYSIS_URL_H - -#include -#include - -/** - * Validates a deck address returned by a deck analysis service. - * - * Only the expected host (optionally "www."-prefixed), HTTPS, default ports and - * URLs without user information are accepted. Legacy HTTP addresses for the same - * service are upgraded to HTTPS in place instead of being rejected. - * - * Relative addresses must be resolved against the response URL by the caller. - * - * @return true when @p url names a deck on @p serviceHost; false otherwise. - */ -inline bool normalizeAnalysisUrl(QUrl &url, const QString &serviceHost) -{ - const QString host = url.host().toLower(); - const bool legacyHttp = url.scheme() == QStringLiteral("http"); - const int defaultPort = legacyHttp ? 80 : 443; - if (!url.isValid() || (!legacyHttp && url.scheme() != QStringLiteral("https")) || !url.userInfo().isEmpty() || - !url.path().startsWith('/') || (url.port() != -1 && url.port() != defaultPort) || - (host != serviceHost && host != QStringLiteral("www.") + serviceHost)) { - return false; - } - // Both services accept HTTPS. Older public metadata may still identify the - // same deck with HTTP; retain its host/path/query while using HTTPS to open it. - if (legacyHttp) { - url.setScheme(QStringLiteral("https")); - url.setPort(-1); - } - return true; -} - -#endif diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp index 22be120c4..508f0bdc9 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp @@ -1,6 +1,5 @@ #include "deck_stats_interface.h" -#include "deck_analysis_url.h" #include "libcockatrice/card/card_info.h" #include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/deck_list/tree/inner_deck_list_node.h" @@ -11,14 +10,12 @@ #include #include #include -#include #include #include #include #include -#include #include -#include +#include #include #include #include @@ -32,56 +29,30 @@ DeckStatsInterface::DeckStatsInterface(QObject *parent) : QObject(parent) connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished); } -void DeckStatsInterface::showError(const QString &message) -{ - // The notice belongs to the originating editor, not this one-shot request. - // Retire the request before opening UI: opening a window may reenter Qt. - QWidget *owner = qobject_cast(parent()); - auto *notice = new QMessageBox(QMessageBox::Critical, tr("Deck analysis"), message, QMessageBox::Ok, owner); - notice->setTextFormat(Qt::PlainText); - notice->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); - notice->setAttribute(Qt::WA_DeleteOnClose); - deleteLater(); - notice->open(); -} - void DeckStatsInterface::queryFinished(QNetworkReply *reply) { - if (replyHandled) { - return; - } - replyHandled = true; - // All terminal paths release the reply before showing UI or invoking an - // external URL handler, either of which may destroy the originating editor. - reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - showError(reply->errorString()); + QMessageBox::critical(nullptr, tr("Error"), reply->errorString()); + reply->deleteLater(); + deleteLater(); return; } - const QString data = QString::fromUtf8(reply->readAll()); - static const QRegularExpression rx( - R"(]*\bproperty\s*=\s*(['"])og:url\1)[^>]*\bcontent\s*=\s*(['"])(.*?)\2)", - QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption); - const auto match = rx.match(data); + QString data(reply->readAll()); + reply->deleteLater(); + + static const QRegularExpression rx("url().resolved(QUrl(address, QUrl::StrictMode)); - if (address.trimmed().isEmpty() || !normalizeAnalysisUrl(deckUrl, QStringLiteral("deckstats.net"))) { - showError(tr("The reply from Deckstats contained an invalid deck address.")); - return; - } + QString deckUrl = match.captured(1); + QDesktopServices::openUrl(deckUrl); - QPointer guard(this); deleteLater(); - if (!QDesktopServices::openUrl(deckUrl) && guard) { - showError(tr("Could not open your browser. Open this address to see the analyzed deck:\n%1") - .arg(deckUrl.toDisplayString())); - } } void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data) @@ -89,8 +60,12 @@ void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray DeckList deckWithoutTokens; copyDeckWithoutTokens(deck, deckWithoutTokens); - data.append("deck=" + QUrl::toPercentEncoding(deckWithoutTokens.writeToString_Plain())); - data.append("&decktitle=" + QUrl::toPercentEncoding(deck.getName())); + QUrl params; + QUrlQuery urlQuery; + urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain()); + urlQuery.addQueryItem("decktitle", deck.getName()); + params.setQuery(urlQuery); + data.append(params.query(QUrl::EncodeReserved).toUtf8()); } void DeckStatsInterface::analyzeDeck(const DeckList &deck) @@ -109,9 +84,7 @@ void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList { auto copyIfNotAToken = [&destination](const auto node, const auto card) { CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName()); - // Missing local card data is not evidence that a card is a token. Keep - // its original name and quantity so the service analyzes the whole deck. - if ((!dbCard || !dbCard->getIsToken()) && node->getName() != DECK_ZONE_MAYBEBOARD) { + if (dbCard && !dbCard->getIsToken() && node->getName() != DECK_ZONE_MAYBEBOARD) { DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1); addedCard->setNumber(card->getNumber()); } diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.h b/cockatrice/src/client/network/interfaces/deck_stats_interface.h index b70646403..e7791075c 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.h +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.h @@ -11,7 +11,6 @@ #include class QByteArray; -class QString; class QNetworkAccessManager; class QNetworkReply; class DeckList; @@ -21,8 +20,6 @@ class DeckStatsInterface : public QObject Q_OBJECT private: QNetworkAccessManager *manager; - bool replyHandled = false; - void showError(const QString &message); /** * Deckstats doesn't recognize token cards, and instead tries to find the diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp index a57877b6a..34dd03765 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp @@ -1,6 +1,5 @@ #include "tapped_out_interface.h" -#include "deck_analysis_url.h" #include "libcockatrice/card/card_info.h" #include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/deck_list/tree/inner_deck_list_node.h" @@ -14,17 +13,15 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include +#include #include -#include #include #include #include @@ -38,67 +35,54 @@ TappedOutInterface::TappedOutInterface(QObject *parent) : QObject(parent) connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished); } -void TappedOutInterface::showError(const QString &message) -{ - // The notice belongs to the originating editor, not this one-shot request. - // Retire the request before opening UI: opening a window may reenter Qt. - QWidget *owner = qobject_cast(parent()); - auto *notice = new QMessageBox(QMessageBox::Critical, tr("Deck analysis"), message, QMessageBox::Ok, owner); - notice->setTextFormat(Qt::PlainText); - notice->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); - notice->setAttribute(Qt::WA_DeleteOnClose); - deleteLater(); - notice->open(); -} - void TappedOutInterface::queryFinished(QNetworkReply *reply) { - if (replyHandled) { - return; - } - replyHandled = true; - // All terminal paths release the reply before showing UI or invoking an - // external URL handler, either of which may destroy the originating editor. - reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - showError(reply->errorString()); - return; - } - - const int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (httpStatus >= 200 && httpStatus < 400 && reply->hasRawHeader("Location")) { - const QByteArray location = reply->rawHeader("Location"); - QUrl deckUrl = reply->url().resolved(QUrl::fromEncoded(location, QUrl::StrictMode)); - if (location.trimmed().isEmpty() || !normalizeAnalysisUrl(deckUrl, QStringLiteral("tappedout.net"))) { - showError(tr("The reply from TappedOut contained an invalid deck address.")); - return; - } - QPointer guard(this); + QMessageBox::critical(nullptr, tr("Error"), reply->errorString()); + reply->deleteLater(); deleteLater(); - if (!QDesktopServices::openUrl(deckUrl) && guard) { - showError(tr("Could not open your browser. Open this address to see the analyzed deck:\n%1") - .arg(deckUrl.toDisplayString())); - } return; } - const QString data = QString::fromUtf8(reply->readAll()); - QStringList errorMessageList = {tr("Unable to analyze the deck on TappedOut.")}; - static const QRegularExpression errorsRx("
(.*?)", - QRegularExpression::DotMatchesEverythingOption); - const auto match = errorsRx.match(data); - if (match.hasMatch()) { - static const QRegularExpression itemRx("
  • (.*?)
  • ", QRegularExpression::DotMatchesEverythingOption); - auto items = itemRx.globalMatch(match.captured(1)); - while (items.hasNext()) { - const QString error = QTextDocumentFragment::fromHtml(items.next().captured(1)).toPlainText().simplified(); - if (!error.isEmpty()) { - errorMessageList.append(error); + int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (reply->hasRawHeader("Location")) { + /* + * If the reply contains a "Location" header, a relative URL to the deck on TappedOut + * can be extracted from the header. The http status is a 302 "redirect". + */ + QString deckUrl = reply->rawHeader("Location"); + qCInfo(TappedOutInterfaceLog) << "Tappedout: good reply, http status" << httpStatus << "location" << deckUrl; + QDesktopServices::openUrl("https://tappedout.net" + deckUrl); + } else { + /* + * Otherwise, the deck has not been parsed correctly. Error messages can be extracted + * from the html. Css pseudo selector for errors: $("div.alert-danger > ul > li") + */ + QString data(reply->readAll()); + QStringList errorMessageList = {tr("Unable to analyze the deck.")}; + + static const QRegularExpression rx("
    (.*?)"); + auto match = rx.match(data); + if (match.hasMatch()) { + QString errors = match.captured(1); + static const QRegularExpression rx2("
  • (.*?)
  • "); + static const QRegularExpression rxremove("<[^>]*>"); + auto matchIterator = rx2.globalMatch(errors); + while (matchIterator.hasNext()) { + auto match2 = matchIterator.next(); + errorMessageList.append(match2.captured(1).remove(rxremove).simplified()); } } + + QString errorMessage = errorMessageList.join("\n"); + qCWarning(TappedOutInterfaceLog) << "Tappedout: bad reply, http status" << httpStatus << "size" << data.size() + << "message" << errorMessage; + + QMessageBox::critical(nullptr, tr("Error"), errorMessage); } - qCWarning(TappedOutInterfaceLog) << "TappedOut analysis failed, HTTP status" << httpStatus; - showError(errorMessageList.join('\n')); + + reply->deleteLater(); + deleteLater(); } void TappedOutInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data) @@ -106,9 +90,13 @@ void TappedOutInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray DeckList mainboard, sideboard; copyDeckSplitMainAndSide(deck, mainboard, sideboard); - data.append("name=" + QUrl::toPercentEncoding(deck.getName())); - data.append("&mainboard=" + QUrl::toPercentEncoding(mainboard.writeToString_Plain(false, true))); - data.append("&sideboard=" + QUrl::toPercentEncoding(sideboard.writeToString_Plain(false, true))); + QUrl params; + QUrlQuery urlQuery; + urlQuery.addQueryItem("name", deck.getName()); + urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true)); + urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true)); + params.setQuery(urlQuery); + data.append(params.query(QUrl::EncodeReserved).toUtf8()); } void TappedOutInterface::analyzeDeck(const DeckList &deck) @@ -129,9 +117,7 @@ void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckLi { auto copyMainOrSide = [&mainboard, &sideboard](const auto node, const auto card) { CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName()); - // Exclude confirmed tokens, but preserve cards missing from the local - // database, including newly released cards in an imported deck. - if ((dbCard && dbCard->getIsToken()) || node->getName() == DECK_ZONE_MAYBEBOARD) { + if (!dbCard || dbCard->getIsToken() || node->getName() == DECK_ZONE_MAYBEBOARD) { return; } diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.h b/cockatrice/src/client/network/interfaces/tapped_out_interface.h index 27e8c2273..615991b07 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.h +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.h @@ -14,7 +14,6 @@ inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface"); class QByteArray; -class QString; class QNetworkAccessManager; class QNetworkReply; class DeckList; @@ -30,8 +29,6 @@ class TappedOutInterface : public QObject Q_OBJECT private: QNetworkAccessManager *manager; - bool replyHandled = false; - void showError(const QString &message); void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard); private slots: diff --git a/cockatrice/src/client/settings/shortcut_treeview.cpp b/cockatrice/src/client/settings/shortcut_treeview.cpp index 2501798cb..4a5bafb01 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.cpp +++ b/cockatrice/src/client/settings/shortcut_treeview.cpp @@ -1,6 +1,5 @@ #include "shortcut_treeview.h" -#include "../../interface/theme_manager.h" #include "cache_settings.h" #include "shortcuts_settings.h" @@ -79,18 +78,6 @@ ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent) connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, &ShortcutTreeView::refreshShortcuts); - - // Group headers store their font on the item; re-apply the role family on - // font changes (setFont emits itemChanged, which repaints the rows). - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { - QFont groupFont = themeManager->appFont(AppFont::InterfaceDefault); - groupFont.setBold(true); - for (int row = 0; row < shortcutsModel->rowCount(); ++row) { - if (auto *groupItem = shortcutsModel->item(row)) { - groupItem->setFont(groupFont); - } - } - }); } void ShortcutTreeView::populateShortcutsModel() @@ -106,8 +93,7 @@ void ShortcutTreeView::populateShortcutsModel() curParent = parentItems.value(group); } else { curParent = new QStandardItem(group); - // Family comes from the InterfaceDefault theme role; bold stays caller-side. - QFont font = themeManager->appFont(AppFont::InterfaceDefault); + static QFont font = curParent->font(); font.setBold(true); curParent->setFont(font); parentItems.insert(group, curParent); diff --git a/cockatrice/src/filters/filter_tree_model.cpp b/cockatrice/src/filters/filter_tree_model.cpp index 66fe39821..9b55ce2fd 100644 --- a/cockatrice/src/filters/filter_tree_model.cpp +++ b/cockatrice/src/filters/filter_tree_model.cpp @@ -1,7 +1,5 @@ #include "filter_tree_model.h" -#include "../interface/theme_manager.h" - #include #include #include @@ -41,12 +39,6 @@ FilterTreeModel::FilterTreeModel(QObject *parent) : QAbstractItemModel(parent) connect(fTree, &FilterTree::postInsertRow, this, &FilterTreeModel::proxyEndInsertRow); connect(fTree, &FilterTree::preRemoveRow, this, &FilterTreeModel::proxyBeginRemoveRow); connect(fTree, &FilterTree::postRemoveRow, this, &FilterTreeModel::proxyEndRemoveRow); - // FontRole data reads the DeckList role live; rebroadcast so attached - // views re-query instead of waiting for an incidental repaint. - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { - emit layoutAboutToBeChanged(); - emit layoutChanged(); - }); } FilterTreeModel::~FilterTreeModel() @@ -210,8 +202,7 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::FontRole: if (!node->isLeaf()) { - // Family comes from the DeckList theme role; bold stays caller-side. - QFont f = themeManager->appFont(AppFont::DeckList); + QFont f; f.setBold(true); return f; } diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp index 84e062cd7..0e8b6ced3 100644 --- a/cockatrice/src/game_graphics/board/abstract_card_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp @@ -3,7 +3,6 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/card_localization.h" #include "../../interface/card_picture_loader/card_picture_loader.h" -#include "../../interface/theme_manager.h" #include "../card_dimensions.h" #include "../game_scene.h" #include "../z_values.h" @@ -155,7 +154,7 @@ void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &transla painter->rotate(angle); painter->translate(-translatedSize.width() / 2, -translatedSize.height() / 2); - QFont f = themeManager->appFont(AppFont::CardText); + QFont f; f.setPixelSize(fontSize * scale); painter->setFont(f); diff --git a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp index 3a740aa98..50e80d359 100644 --- a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp @@ -1,7 +1,5 @@ #include "abstract_graphics_item.h" -#include "../../interface/theme_manager.h" - #include #include #include @@ -22,7 +20,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number, painter->save(); QString numStr = QString::number(number); - QFont font = themeManager->cardTextFont(); + QFont font("Serif"); font.setPixelSize(fontSize); font.setWeight(QFont::Bold); diff --git a/cockatrice/src/game_graphics/board/counter_general.cpp b/cockatrice/src/game_graphics/board/counter_general.cpp index 23618e6bf..4a8bbd6a4 100644 --- a/cockatrice/src/game_graphics/board/counter_general.cpp +++ b/cockatrice/src/game_graphics/board/counter_general.cpp @@ -1,7 +1,6 @@ #include "counter_general.h" #include "../../interface/pixel_map_generator.h" -#include "../../interface/theme_manager.h" #include "abstract_graphics_item.h" #include @@ -42,7 +41,7 @@ void GeneralCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * / painter->drawPixmap(QPoint(0, 0), pixmap); if (value) { - QFont f = themeManager->cardTextFont(); + QFont f("Serif"); f.setPixelSize(qMax((int)(radius * scaleFactor), 10)); f.setWeight(QFont::Bold); painter->setPen(Qt::black); diff --git a/cockatrice/src/game_graphics/deckview/deck_view.cpp b/cockatrice/src/game_graphics/deckview/deck_view.cpp index f62d33335..6e0b0eaaa 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view.cpp @@ -240,8 +240,8 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); painter->setPen(QColor(Qt::white)); - // Family comes from the DeckList theme role; pixel sizes/weights unchanged. - QFont f = themeManager->deckListFont(); + QFont f("Serif"); + f.setStyleHint(QFont::Serif); f.setPixelSize(24); f.setWeight(QFont::Bold); painter->setFont(f); @@ -292,8 +292,8 @@ QList> DeckViewCardContainer::getRowsAndCols() const int DeckViewCardContainer::getCardTypeTextWidth() const { - // Family comes from the DeckList theme role; pixel size/weight unchanged. - QFont f = themeManager->deckListFont(); + QFont f("Serif"); + f.setStyleHint(QFont::Serif); f.setPixelSize(16); f.setWeight(QFont::Bold); QFontMetrics fm(f); @@ -366,15 +366,6 @@ void DeckViewCardContainer::setWidth(qreal _width) DeckViewScene::DeckViewScene(QObject *parent) : QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0) { - // Zone headers read the DeckList role at paint time and cache their - // rendering, so touch every item to invalidate the caches and pick up the - // new family on font changes (containers cannot connect this themselves: - // QGraphicsItem is not a QObject). - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { - for (auto *item : items()) { - item->update(); - } - }); } DeckViewScene::~DeckViewScene() diff --git a/cockatrice/src/game_graphics/deckview/deck_view_container.cpp b/cockatrice/src/game_graphics/deckview/deck_view_container.cpp index fe63d7a6f..3bef9e59e 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view_container.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view_container.cpp @@ -71,8 +71,6 @@ void ToggleButton::setState(bool _state) DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) : QWidget(nullptr), visualDeckStorageWidget(nullptr), parentGame(parent), playerId(_playerId) { - setObjectName("deckViewContainer"); - loadLocalButton = new QPushButton; loadRemoteButton = new QPushButton; loadFromClipboardButton = new QPushButton; @@ -82,14 +80,6 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) forceStartGameButton = new QPushButton; sideboardLockButton = new ToggleButton; - loadLocalButton->setObjectName("loadLocalButton"); - loadRemoteButton->setObjectName("loadRemoteButton"); - loadFromClipboardButton->setObjectName("loadFromClipboardButton"); - readyStartButton->setObjectName("readyStartButton"); - forceStartGameButton->setObjectName("forceStartGameButton"); - unloadDeckButton->setObjectName("unloadDeckButton"); - sideboardLockButton->setObjectName("sideboardLockButton"); - connect(loadLocalButton, &QPushButton::clicked, this, &DeckViewContainer::loadLocalDeck); connect(loadRemoteButton, &QPushButton::clicked, this, &DeckViewContainer::loadRemoteDeck); connect(loadFromClipboardButton, &QPushButton::clicked, this, &DeckViewContainer::loadFromClipboard); diff --git a/cockatrice/src/game_graphics/deckview/tabbed_deck_view_container.cpp b/cockatrice/src/game_graphics/deckview/tabbed_deck_view_container.cpp index 75ede0ca8..90cbed9a1 100644 --- a/cockatrice/src/game_graphics/deckview/tabbed_deck_view_container.cpp +++ b/cockatrice/src/game_graphics/deckview/tabbed_deck_view_container.cpp @@ -14,7 +14,6 @@ class TabGame; TabbedDeckViewContainer::TabbedDeckViewContainer(int _playerId, TabGame *parent) : QTabWidget(nullptr), playerId(_playerId), parentGame(parent) { - setObjectName(QString("tabbedDeckView_%1").arg(_playerId)); setTabsClosable(true); connect(this, &QTabWidget::tabCloseRequested, this, &TabbedDeckViewContainer::closeTab); diff --git a/cockatrice/src/game_graphics/game_scene.cpp b/cockatrice/src/game_graphics/game_scene.cpp index 1f780b795..db1040006 100644 --- a/cockatrice/src/game_graphics/game_scene.cpp +++ b/cockatrice/src/game_graphics/game_scene.cpp @@ -1,7 +1,6 @@ #include "game_scene.h" #include "../client/settings/cache_settings.h" -#include "../interface/theme_manager.h" #include "animated_item.h" #include "board/abstract_card_item.h" #include "board/arrow_item.h" @@ -65,14 +64,6 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) addItem(phasesToolbar); connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::minPlayersForMultiColumnLayoutChanged, this, &GameScene::rearrange); - // CardText items (P/T boxes, counters, player names) read the role at - // paint time and cache their rendering, so touch every item to invalidate - // the caches and pick up the new family on font changes. - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { - for (auto *item : items()) { - item->update(); - } - }); rearrange(); } diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 7a5aa073a..cb59b3d7d 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -2,14 +2,12 @@ #include "../client/settings/cache_settings.h" #include "../client/settings/shortcuts_settings.h" -#include "../interface/theme_manager.h" #include "game_scene.h" #include #include #include #include -#include #include #include #include @@ -26,21 +24,6 @@ #include #include -namespace -{ -QFont deckOverlayFont(int pixelSize, bool bold) -{ - // The drag/total counters and the tally columns are in-game overlays, not - // deck list text: keep the monospace family the stylesheets used to set so - // digit widths stay stable as counts tick and `white-space: pre` lines up. - QFont font(QStringLiteral("monospace")); - font.setStyleHint(QFont::Monospace); - font.setPixelSize(pixelSize); - font.setBold(bold); - return font; -} -} // namespace - // QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. // This subclass disables that behavior so dragCountLabel can appear above it. class SelectionRubberBand : public QRubberBand @@ -65,7 +48,6 @@ protected: GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, parent), rubberBand(0) { - setObjectName("gameView"); setBackgroundBrush(QBrush(QColor(0, 0, 0))); setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing); setViewportUpdateMode(BoundingRectViewportUpdate); @@ -93,14 +75,15 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this); const QString baseProperties = "color: white; " + "font-family: monospace; " "background-color: rgba(0, 0, 0, 160); " "border-radius: 3px; " "padding: 1px 2px; " "white-space: pre;"; - const QString dragCountLabelStyle = baseProperties; - const QString totalCountLabelStyle = baseProperties; - const QString subtypeTallyLabelStyle = baseProperties; + const QString dragCountLabelStyle = baseProperties + "font-size: 14px; font-weight: bold;"; + const QString totalCountLabelStyle = baseProperties + "font-size: 16px; font-weight: bold;"; + const QString subtypeTallyLabelStyle = baseProperties + "font-size: 12px;"; dragCountLabel = new QLabel(this); dragCountLabel->setStyleSheet(dragCountLabelStyle); @@ -117,27 +100,6 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par tallyLayout->setContentsMargins(2, 2, 2, 2); tallyLayout->setSpacing(2); tallyContainer->hide(); - - applyDeckOverlayFonts(); -} - -void GameView::applyDeckOverlayFonts() -{ - dragCountLabel->setFont(deckOverlayFont(14, true)); - totalCountLabel->setFont(deckOverlayFont(16, true)); - tallyContainer->setFont(deckOverlayFont(12, false)); - for (int i = 0; i < tallyLayout->count(); ++i) { - if (auto *label = qobject_cast(tallyLayout->itemAt(i)->widget())) { - // Count column (odd positions) keeps the bold emphasis. - const bool count = i % 2 == 1; - label->setFont(deckOverlayFont(count ? 14 : 12, count)); - } - } - - // The container keeps the size computed from the previous fonts, so force - // rebuildTallyLabels() to re-measure it instead of reusing cachedTallyRows. - cachedTallyRows.clear(); - updateTotalSelectionCount(); } void GameView::resizeEvent(QResizeEvent *event) @@ -257,8 +219,9 @@ QSize GameView::rebuildTallyLabels(const QList &entries) { clearTallyLabels(); - const QString nameStyle = QStringLiteral("color: white; background: transparent;"); - const QString countStyle = QStringLiteral("color: white; background: transparent;"); + const QString nameStyle = QStringLiteral("color: white; font-size: 12px; background: transparent;"); + const QString countStyle = + QStringLiteral("color: white; font-size: 14px; font-weight: bold; background: transparent;"); int totalHeight = 0; int maxNameWidth = 0; @@ -268,13 +231,11 @@ QSize GameView::rebuildTallyLabels(const QList &entries) for (const TallyRow &entry : entries) { auto *nameLabel = new QLabel(entry.name, tallyContainer); nameLabel->setStyleSheet(nameStyle); - nameLabel->setFont(deckOverlayFont(12, false)); nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); tallyLayout->addWidget(nameLabel, row, 0); auto *countLabel = new QLabel(entry.value, tallyContainer); countLabel->setStyleSheet(countStyle); - countLabel->setFont(deckOverlayFont(14, true)); countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); tallyLayout->addWidget(countLabel, row, 1); diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index 99a73a0a8..e57267734 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -50,7 +50,6 @@ private slots: void refreshShortcuts(); void updateTotalSelectionCount(const QSize &viewSize = QSize()); void setFocusDisabled(bool disabled); - void applyDeckOverlayFonts(); public slots: void updateSceneRect(const QRectF &rect); diff --git a/cockatrice/src/game_graphics/player/player_target.cpp b/cockatrice/src/game_graphics/player/player_target.cpp index 0d10ba4ab..1cd97e7b5 100644 --- a/cockatrice/src/game_graphics/player/player_target.cpp +++ b/cockatrice/src/game_graphics/player/player_target.cpp @@ -2,7 +2,6 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/pixel_map_generator.h" -#include "../../interface/theme_manager.h" #include "../board/abstract_counter.h" #include "../board/abstract_graphics_item.h" #include "../board/arrow_target.h" @@ -77,7 +76,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* QRectF translatedRect = path.controlPointRect(); QSize translatedSize = translatedRect.size().toSize(); - QFont font = themeManager->cardTextFont(); + QFont font("Serif"); font.setWeight(QFont::Bold); font.setPixelSize(qMax(qRound(translatedSize.height() / 1.3), 9)); painter->setFont(font); @@ -227,7 +226,7 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o name = name.mid(0, 10) + "..."; } - QFont font = themeManager->appFont(AppFont::CardText); + QFont font; font.setPixelSize(qMax(qRound(translatedNameRect.height() / 1.5), 9)); painter->setFont(font); painter->setPen(Qt::white); diff --git a/cockatrice/src/game_graphics/pq_cockatrice_event_player.cpp b/cockatrice/src/game_graphics/pq_cockatrice_event_player.cpp deleted file mode 100644 index c6376ef75..000000000 --- a/cockatrice/src/game_graphics/pq_cockatrice_event_player.cpp +++ /dev/null @@ -1,628 +0,0 @@ -#include "pq_cockatrice_event_player.h" - -#include "../../interface/widgets/tabs/tab_game.h" -#include "board/card_item.h" -#include "game_scene.h" -#include "game_view.h" -#include "zones/card_zone.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -Q_LOGGING_CATEGORY(PQ_COCKATRICE_EVENT_PLAYER, "cockatrice.guiTest.eventPlayer") - -namespace -{ -/** - * Strips Qt's mnemonic markers so a test can name a menu entry by the label the - * user sees ("Turn Over" rather than "T&urn Over"). - */ -QString normalizeActionText(const QString &text) -{ - QString normalized = text; - normalized.remove('&'); - return normalized.simplified(); -} - -/** - * Maps a printable character onto the key a US keyboard would report for it, so - * typing produces the same QKeyEvents a recording would. - */ -Qt::Key keyForCharacter(QChar character) -{ - if (character.isLetter()) { - return static_cast(Qt::Key_A + (character.toUpper().unicode() - 'A')); - } - if (character.isDigit()) { - return static_cast(Qt::Key_0 + (character.unicode() - '0')); - } - switch (character.unicode()) { - case ' ': - return Qt::Key_Space; - case '\n': - return Qt::Key_Return; - case '\t': - return Qt::Key_Tab; - case '-': - return Qt::Key_Minus; - case '_': - return Qt::Key_Underscore; - case '/': - return Qt::Key_Slash; - case ',': - return Qt::Key_Comma; - case '.': - return Qt::Key_Period; - case '\'': - return Qt::Key_Apostrophe; - case ';': - return Qt::Key_Semicolon; - case ':': - return Qt::Key_Colon; - case '"': - return Qt::Key_QuoteDbl; - case '&': - return Qt::Key_Ampersand; - case '(': - return Qt::Key_ParenLeft; - case ')': - return Qt::Key_ParenRight; - case '[': - return Qt::Key_BracketLeft; - case ']': - return Qt::Key_BracketRight; - default: - return Qt::Key_unknown; - } -} - -/** Depth-first search for the first model index whose label matches @p text. */ -QModelIndex findModelIndexByText(const QAbstractItemModel *model, const QString &text, const QModelIndex &parent = {}) -{ - for (int row = 0; row < model->rowCount(parent); ++row) { - const QModelIndex index = model->index(row, 0, parent); - if (index.data(Qt::DisplayRole).toString() == text) { - return index; - } - const QModelIndex child = findModelIndexByText(model, text, index); - if (child.isValid()) { - return child; - } - } - return {}; -} - -bool needsShift(QChar character) -{ - if (character.isUpper()) { - return true; - } - return QStringLiteral("_:\"&()").contains(character); -} -} // namespace - -pqCockatriceEventPlayer::pqCockatriceEventPlayer(QObject *parent) : pqWidgetEventPlayer(parent) -{ -} - -bool pqCockatriceEventPlayer::playEvent(QObject *object, const QString &command, const QString &arguments, bool &error) -{ - // A known command that fails is a handled-but-failed event: QtTesting logs - // "Unhandled event" when playEvent returns false, so report through `error` - // (the contract playExpectProperty already follows) and still return true. - const auto run = [&error](bool ok) { - if (!ok) { - error = true; - } - return true; - }; - - if (command == "clickCard") { - return run(playClickCard(object, arguments)); - } else if (command == "flipCard") { - return run(playFlipCard(object, arguments)); - } else if (command == "cardAction") { - return run(playCardAction(object, arguments)); - } else if (command == "moveCard") { - return run(playMoveCard(object, arguments)); - } else if (command == "drawArrow") { - return run(playDrawArrow(object, arguments)); - } else if (command == "createToken") { - return run(playCreateToken(object, arguments)); - } else if (command == "selectItem") { - return run(playSelectItem(object, arguments)); - } else if (command == "typeText") { - return run(playTypeText(object, arguments)); - } else if (command == "expectProperty") { - return playExpectProperty(object, arguments, error); - } - return false; -} - -QGraphicsItem * -pqCockatriceEventPlayer::findCardInScene(QGraphicsScene *scene, const QString &cardName, const QString &zoneName) -{ - for (QGraphicsItem *gi : scene->items()) { - if (gi->type() != typeCard) { - continue; - } - auto *card = static_cast(gi); - if (!card->getZone() || card->getZone()->getName() != zoneName) { - continue; - } - QString actualName = card->getName(); - if (actualName == cardName || actualName.endsWith(" " + cardName)) { - return card; - } - } - return nullptr; -} - -QGraphicsItem *pqCockatriceEventPlayer::findZoneInScene(QGraphicsScene *scene, const QString &zoneName) -{ - for (QGraphicsItem *gi : scene->items()) { - if (gi->type() != typeZone) { - continue; - } - auto *zone = static_cast(gi); - if (zone->getLogic() && zone->getLogic()->getName() == zoneName) { - return zone; - } - } - return nullptr; -} - -QAction *pqCockatriceEventPlayer::findActionInMenu(QMenu *menu, const QString &actionText) -{ - if (!menu || actionText.isEmpty()) { - return nullptr; - } - for (QAction *action : menu->actions()) { - if (action->isSeparator()) { - continue; - } - if (normalizeActionText(action->text()).contains(actionText, Qt::CaseInsensitive)) { - return action; - } - if (QMenu *subMenu = action->menu()) { - if (QAction *found = findActionInMenu(subMenu, actionText)) { - return found; - } - } - } - return nullptr; -} - -QMenu *pqCockatriceEventPlayer::visiblePopupMenu() -{ - for (QWidget *w : QApplication::topLevelWidgets()) { - auto *menu = qobject_cast(w); - if (menu && menu->isVisible()) { - return menu; - } - } - return nullptr; -} - -QPoint pqCockatriceEventPlayer::viewportPoint(QGraphicsView *view, const QGraphicsItem *item) -{ - return view->mapFromScene(item->sceneBoundingRect().center()); -} -void pqCockatriceEventPlayer::sendMouseEvent(QGraphicsView *view, - QEvent::Type type, - const QPoint &pos, - Qt::MouseButton button, - Qt::MouseButtons buttons) -{ - QMouseEvent event(type, pos, view->viewport()->mapToGlobal(pos), button, buttons, Qt::NoModifier); - qApp->notify(view->viewport(), &event); -} - -bool pqCockatriceEventPlayer::playClickCard(QObject *object, const QString &arguments) -{ - auto *gv = qobject_cast(object); - if (!gv) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "clickCard: target widget is not a QGraphicsView"; - return false; - } - - QStringList parts = arguments.split(','); - if (parts.size() < 2) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "clickCard: arguments must be 'cardName,zoneName', got:" << arguments; - return false; - } - - QGraphicsScene *scene = gv->scene(); - if (!scene) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "clickCard: no scene on the QGraphicsView"; - return false; - } - - auto *target = findCardInScene(scene, parts[0].trimmed(), parts[1].trimmed()); - if (!target) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "clickCard: card" << parts[0].trimmed() << "not found in zone" << parts[1].trimmed(); - return false; - } - - const QPoint pos = viewportPoint(gv, target); - // A double click is the play gesture from hand: press/release, then the - // double-click event and its release, exactly as a recording produces it. - sendMouseEvent(gv, QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton); - sendMouseEvent(gv, QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton); - sendMouseEvent(gv, QEvent::MouseButtonDblClick, pos, Qt::LeftButton, Qt::LeftButton); - sendMouseEvent(gv, QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton); - - return true; -} - -bool pqCockatriceEventPlayer::playFlipCard(QObject *object, const QString &arguments) -{ - auto *gv = qobject_cast(object); - if (!gv) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "flipCard: target widget is not a QGraphicsView"; - return false; - } - - QStringList parts = arguments.split(','); - if (parts.size() < 2) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "flipCard: arguments must be 'cardName,zoneName', got:" << arguments; - return false; - } - - QGraphicsScene *scene = gv->scene(); - if (!scene) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "flipCard: no scene"; - return false; - } - - auto *card = qgraphicsitem_cast(findCardInScene(scene, parts[0].trimmed(), parts[1].trimmed())); - if (!card) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "flipCard: card" << parts[0].trimmed() << "not found in zone" << parts[1].trimmed(); - return false; - } - - return invokeCardMenuAction("flipCard", gv, card, QStringLiteral("Turn Over")); -} - -bool pqCockatriceEventPlayer::playCardAction(QObject *object, const QString &arguments) -{ - auto *gv = qobject_cast(object); - if (!gv) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "cardAction: target widget is not a QGraphicsView"; - return false; - } - - QStringList parts = arguments.split(','); - if (parts.size() < 3) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "cardAction: arguments must be 'cardName,zoneName,actionText', got:" << arguments; - return false; - } - - QGraphicsScene *scene = gv->scene(); - if (!scene) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "cardAction: no scene"; - return false; - } - - auto *card = qgraphicsitem_cast(findCardInScene(scene, parts[0].trimmed(), parts[1].trimmed())); - if (!card) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "cardAction: card" << parts[0].trimmed() << "not found in zone" << parts[1].trimmed(); - return false; - } - - return invokeCardMenuAction("cardAction", gv, card, parts.mid(2).join(',').trimmed()); -} - -bool pqCockatriceEventPlayer::invokeCardMenuAction(const char *command, - QGraphicsView *view, - CardItem *card, - const QString &actionText) -{ - const QPoint pos = viewportPoint(view, card); - sendMouseEvent(view, QEvent::MouseButtonPress, pos, Qt::RightButton, Qt::RightButton); - sendMouseEvent(view, QEvent::MouseButtonRelease, pos, Qt::RightButton, Qt::NoButton); - qApp->processEvents(); - - QMenu *menu = visiblePopupMenu(); - if (!menu) { - qCWarning(PQ_COCKATRICE_EVENT_PLAYER) << command << ": no popup menu appeared after right-click"; - return false; - } - - QAction *action = findActionInMenu(menu, actionText); - if (!action) { - qCWarning(PQ_COCKATRICE_EVENT_PLAYER) << command << ": no context menu action matching" << actionText; - menu->close(); - return false; - } - - action->trigger(); - menu->close(); - qApp->processEvents(); - return true; -} - -bool pqCockatriceEventPlayer::playMoveCard(QObject *object, const QString &arguments) -{ - auto *gv = qobject_cast(object); - if (!gv) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "moveCard: target widget is not a QGraphicsView"; - return false; - } - - QStringList parts = arguments.split(','); - if (parts.size() < 3) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "moveCard: arguments must be 'cardName,srcZoneName,dstZoneName', got:" << arguments; - return false; - } - - const QString cardName = parts[0].trimmed(); - const QString srcZoneName = parts[1].trimmed(); - const QString dstZoneName = parts[2].trimmed(); - - QGraphicsScene *scene = gv->scene(); - if (!scene) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "moveCard: no scene"; - return false; - } - - auto *card = findCardInScene(scene, cardName, srcZoneName); - if (!card) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "moveCard: card" << cardName << "not found in zone" << srcZoneName; - return false; - } - - auto *dstZone = findZoneInScene(scene, dstZoneName); - if (!dstZone) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "moveCard: destination zone" << dstZoneName << "not found"; - return false; - } - - const QPoint srcPos = viewportPoint(gv, card); - const QPoint dstPos = viewportPoint(gv, dstZone); - - // A drag has to travel past Qt's start distance before the scene turns it - // into a card drag item, otherwise the move is interpreted as a click. - const int dragDistance = 2 * QApplication::startDragDistance() + 10; - const QPoint pastDragStart = srcPos + QPoint(dragDistance, 0); - - sendMouseEvent(gv, QEvent::MouseButtonPress, srcPos, Qt::LeftButton, Qt::LeftButton); - sendMouseEvent(gv, QEvent::MouseMove, pastDragStart, Qt::LeftButton, Qt::LeftButton); - qApp->processEvents(); - sendMouseEvent(gv, QEvent::MouseMove, dstPos, Qt::LeftButton, Qt::LeftButton); - qApp->processEvents(); - sendMouseEvent(gv, QEvent::MouseButtonRelease, dstPos, Qt::LeftButton, Qt::NoButton); - qApp->processEvents(); - - return true; -} - -bool pqCockatriceEventPlayer::playDrawArrow(QObject *object, const QString &arguments) -{ - auto *gv = qobject_cast(object); - if (!gv) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "drawArrow: target widget is not a QGraphicsView"; - return false; - } - - QStringList parts = arguments.split(','); - if (parts.size() < 4) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "drawArrow: arguments must be 'srcCard,srcZone,dstCard,dstZone', got:" << arguments; - return false; - } - - const QString srcCardName = parts[0].trimmed(); - const QString srcZoneName = parts[1].trimmed(); - const QString dstCardName = parts[2].trimmed(); - const QString dstZoneName = parts[3].trimmed(); - - QGraphicsScene *scene = gv->scene(); - if (!scene) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "drawArrow: no scene"; - return false; - } - - auto *srcCard = findCardInScene(scene, srcCardName, srcZoneName); - if (!srcCard) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "drawArrow: source card" << srcCardName << "not found in zone" << srcZoneName; - return false; - } - - auto *dstCard = findCardInScene(scene, dstCardName, dstZoneName); - if (!dstCard) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "drawArrow: dest card" << dstCardName << "not found in zone" << dstZoneName; - return false; - } - - const QPoint srcPos = viewportPoint(gv, srcCard); - const QPoint dstPos = viewportPoint(gv, dstCard); - - const int dragDistance = 2 * QApplication::startDragDistance() + 10; - const QPoint pastDragStart = srcPos + QPoint(dragDistance, 0); - - sendMouseEvent(gv, QEvent::MouseButtonPress, srcPos, Qt::RightButton, Qt::RightButton); - sendMouseEvent(gv, QEvent::MouseMove, pastDragStart, Qt::RightButton, Qt::RightButton); - qApp->processEvents(); - sendMouseEvent(gv, QEvent::MouseMove, dstPos, Qt::RightButton, Qt::RightButton); - sendMouseEvent(gv, QEvent::MouseButtonRelease, dstPos, Qt::RightButton, Qt::NoButton); - qApp->processEvents(); - - return true; -} - -bool pqCockatriceEventPlayer::playCreateToken(QObject *object, const QString &arguments) -{ - auto *gv = qobject_cast(object); - if (!gv) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "createToken: target widget is not a QGraphicsView"; - return false; - } - - QStringList parts = arguments.split(','); - if (parts.size() < 3) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "createToken: arguments must be 'name,color,pt', got:" << arguments; - return false; - } - - QString tokenName = parts[0].trimmed(); - QString tokenColor = parts[1].trimmed(); - QString tokenPt = parts[2].trimmed(); - - TabGame *tabGame = nullptr; - QWidget *w = gv; - while (w) { - tabGame = qobject_cast(w); - if (tabGame) { - break; - } - w = w->parentWidget(); - } - if (!tabGame) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "createToken: TabGame not found"; - return false; - } - - AbstractGame *game = tabGame->getGame(); - if (!game) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "createToken: no game"; - return false; - } - - int activePlayerId = game->getGameState()->getActivePlayer(); - PlayerLogic *player = game->getPlayerManager()->getActiveLocalPlayer(activePlayerId); - if (!player) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "createToken: no active local player"; - return false; - } - - TokenInfo info; - info.name = tokenName; - info.color = tokenColor; - info.pt = tokenPt; - info.destroy = true; - info.faceDown = false; - - player->getPlayerActions()->actCreateToken(info); - - for (int i = 0; i < 10; ++i) { - qApp->processEvents(); - } - - return true; -} - -bool pqCockatriceEventPlayer::playSelectItem(QObject *object, const QString &arguments) -{ - const QString text = arguments.trimmed(); - if (text.isEmpty()) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "selectItem: needs the label of the item to select"; - return false; - } - - if (auto *tree = qobject_cast(object)) { - QTreeWidgetItemIterator item(tree); - while (*item) { - if ((*item)->text(0) == text) { - tree->setCurrentItem(*item); - qApp->processEvents(); - return true; - } - ++item; - } - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "selectItem: no tree item labelled" << text; - return false; - } - - if (auto *view = qobject_cast(object)) { - const QModelIndex index = findModelIndexByText(view->model(), text); - if (!index.isValid()) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "selectItem: no item labelled" << text; - return false; - } - view->setCurrentIndex(index); - qApp->processEvents(); - return true; - } - - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "selectItem: target is neither a tree widget nor an item view"; - return false; -} - -bool pqCockatriceEventPlayer::playTypeText(QObject *object, const QString &arguments) -{ - auto *widget = qobject_cast(object); - if (!widget) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "typeText: target is not a QWidget"; - return false; - } - - for (const QChar character : arguments) { - const Qt::Key key = keyForCharacter(character); - const Qt::KeyboardModifiers modifiers = needsShift(character) ? Qt::ShiftModifier : Qt::NoModifier; - QKeyEvent press(QEvent::KeyPress, key, modifiers, QString(character)); - QKeyEvent release(QEvent::KeyRelease, key, modifiers, QString(character)); - qApp->notify(widget, &press); - qApp->notify(widget, &release); - } - qApp->processEvents(); - - return true; -} - -bool pqCockatriceEventPlayer::playExpectProperty(QObject *object, const QString &arguments, bool &error) -{ - if (!object) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "expectProperty: no target object"; - error = true; - return true; - } - - QStringList parts = arguments.split(','); - if (parts.size() < 2) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "expectProperty: arguments must be 'propertyName,expectedValue', got:" << arguments; - error = true; - return true; - } - - const QString propertyName = parts[0].trimmed(); - const QString expected = parts.mid(1).join(','); - - const QVariant actual = object->property(propertyName.toUtf8().constData()); - if (!actual.isValid()) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) - << "expectProperty:" << object->objectName() << "has no property" << propertyName; - error = true; - return true; - } - - if (actual.toString() != expected) { - qCCritical(PQ_COCKATRICE_EVENT_PLAYER) << "expectProperty:" << object->objectName() << propertyName << "is" - << actual.toString() << "but the test expected" << expected; - error = true; - return true; - } - - return true; -} diff --git a/cockatrice/src/game_graphics/pq_cockatrice_event_player.h b/cockatrice/src/game_graphics/pq_cockatrice_event_player.h deleted file mode 100644 index 89d88af77..000000000 --- a/cockatrice/src/game_graphics/pq_cockatrice_event_player.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef PQ_COCKATRICE_EVENT_PLAYER_H -#define PQ_COCKATRICE_EVENT_PLAYER_H - -#include "pqWidgetEventPlayer.h" - -#include - -class CardItem; -class QAction; -class QGraphicsItem; -class QGraphicsScene; -class QGraphicsView; -class QMenu; - -/** - * Custom event player for Cockatrice game interactions. - * - * Card commands are resolution independent: the card is looked up in the - * QGraphicsScene by name and zone, then mouse events are sent at the card's - * current viewport position. That keeps recordings usable across screen sizes - * and window geometries. - * - * Commands: - * clickCard "cardName,zoneName" - * left press/release + double click — plays a card from hand, taps a - * permanent, opens the deck view entry, depending on the zone - * flipCard "cardName,zoneName" - * right-click → "Turn Over" (shorthand for cardAction) - * cardAction "cardName,zoneName,actionText" - * right-click the card and trigger the first context menu action whose - * label contains actionText; submenus are searched as well, so - * "Add counter (A)" works - * moveCard "cardName,srcZoneName,dstZoneName" - * left-drag the card onto the destination zone — plays a card, moves it - * to the graveyard/exile, puts it back into hand, ... - * drawArrow "srcCard,srcZone,dstCard,dstZone" - * right-drag an arrow from one card to another - * createToken "name,color,pt" - * create a token through the active local player, as the table context - * menu's "Create token" dialog would - * selectItem "item text" - * selects the first item of a tree/list widget whose label matches, for - * navigating the settings navigation tree or similar label-driven views - * typeText "some text" - * types the text into the widget one key event at a time — use it for - * deck lists and search boxes instead of one event per character - * expectProperty "propertyName,expectedValue" - * assertion: playback fails unless object->property(propertyName) - * equals expectedValue — for example "count,2" on the tab widget - */ -class pqCockatriceEventPlayer : public pqWidgetEventPlayer -{ - Q_OBJECT - typedef pqWidgetEventPlayer Superclass; - -public: - explicit pqCockatriceEventPlayer(QObject *parent = nullptr); - - using Superclass::playEvent; - - bool playEvent(QObject *object, const QString &command, const QString &arguments, bool &error) override; - -private: - bool playClickCard(QObject *object, const QString &arguments); - bool playFlipCard(QObject *object, const QString &arguments); - bool playCardAction(QObject *object, const QString &arguments); - bool playMoveCard(QObject *object, const QString &arguments); - bool playDrawArrow(QObject *object, const QString &arguments); - bool playCreateToken(QObject *object, const QString &arguments); - bool playSelectItem(QObject *object, const QString &arguments); - bool playTypeText(QObject *object, const QString &arguments); - bool playExpectProperty(QObject *object, const QString &arguments, bool &error); - - /** @brief Triggers the context menu entry @p actionText after right-clicking @p card. */ - bool invokeCardMenuAction(const char *command, QGraphicsView *view, CardItem *card, const QString &actionText); - - static QGraphicsItem *findCardInScene(QGraphicsScene *scene, const QString &cardName, const QString &zoneName); - static QGraphicsItem *findZoneInScene(QGraphicsScene *scene, const QString &zoneName); - static QAction *findActionInMenu(QMenu *menu, const QString &actionText); - static QMenu *visiblePopupMenu(); - - /** @brief Viewport position of the item's centre, as the mouse events have to be sent. */ - static QPoint viewportPoint(QGraphicsView *view, const QGraphicsItem *item); - static void sendMouseEvent(QGraphicsView *view, - QEvent::Type type, - const QPoint &pos, - Qt::MouseButton button, - Qt::MouseButtons buttons); -}; - -#endif // PQ_COCKATRICE_EVENT_PLAYER_H diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index daa5dc98a..d413a6c04 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -1,6 +1,5 @@ #include "deck_loader.h" -#include "../theme_manager.h" #include "libcockatrice/card/card_info.h" #include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/deck_list/tree/abstract_deck_list_card_node.h" @@ -580,8 +579,6 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode if (!cards.isEmpty()) { QTextBlockFormat blockFormat; QTextCharFormat charFormat; - // Family comes from the DeckList theme role; sizes/weights unchanged. - charFormat.setFontFamilies({themeManager->deckListFont().family()}); charFormat.setFontPointSize(11); charFormat.setFontWeight(QFont::Bold); cursor->insertBlock(blockFormat, charFormat); @@ -595,7 +592,6 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode const AbstractDecklistCardNode *card = cards[i]; QTextCharFormat cellCharFormat; - cellCharFormat.setFontFamilies({themeManager->deckListFont().family()}); cellCharFormat.setFontPointSize(9); QTextTableCell cell = table->cellAt(i, 0); @@ -617,7 +613,6 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode QTextBlockFormat blockFormat; QTextCharFormat charFormat; - charFormat.setFontFamilies({themeManager->deckListFont().family()}); charFormat.setFontPointSize(14); charFormat.setFontWeight(QFont::Bold); @@ -645,18 +640,14 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList) { QTextDocument doc; - // Family comes from the DeckList theme role; the print sizes/weights below stay unchanged. - // Print output is a one-shot snapshot and intentionally does not subscribe - // to fontsChanged (there is no live view to repaint); reprinting picks up - // the current role font. - QFont font = themeManager->deckListFont(); + QFont font("Serif"); + font.setStyleHint(QFont::Serif); doc.setDefaultFont(font); QTextCursor cursor(&doc); QTextBlockFormat headerBlockFormat; QTextCharFormat headerCharFormat; - headerCharFormat.setFontFamilies({themeManager->deckListFont().family()}); headerCharFormat.setFontPointSize(16); headerCharFormat.setFontWeight(QFont::Bold); diff --git a/cockatrice/src/interface/palette_editor/color_button.cpp b/cockatrice/src/interface/palette_editor/color_button.cpp index ae9bd6297..a7db23d31 100644 --- a/cockatrice/src/interface/palette_editor/color_button.cpp +++ b/cockatrice/src/interface/palette_editor/color_button.cpp @@ -1,7 +1,5 @@ #include "color_button.h" -#include "../theme_manager.h" - #include #include #include @@ -23,7 +21,6 @@ ColorButton::ColorButton(QWidget *parent) : QToolButton(parent) setCursor(Qt::PointingHandCursor); setToolTip(tr("Click to pick a color")); connect(this, &QToolButton::clicked, this, &ColorButton::pickColor); - connect(themeManager, &ThemeManager::fontsChanged, this, &ColorButton::updateSwatch); } void ColorButton::setColor(const QColor &c) @@ -76,8 +73,7 @@ void ColorButton::updateSwatch() // Hex label — black or white for contrast int luma = 0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue(); painter.setPen((luma > 128 && color.alpha() > 80) ? QColor(0, 0, 0, 180) : QColor(255, 255, 255, 200)); - // Family comes from the InterfaceDefault theme role; the 8px size stays caller-side. - QFont f = themeManager->appFont(AppFont::InterfaceDefault); + QFont f = font(); f.setPixelSize(8); painter.setFont(f); painter.drawText(rect(), Qt::AlignCenter, color.name().toUpper()); diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp deleted file mode 100644 index 44ad59551..000000000 --- a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "font_grid_widget.h" - -#include "../theme_manager.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -static constexpr int MIN_FONT_SIZE = 6; -static constexpr int MAX_FONT_SIZE = 72; -static constexpr int FALLBACK_FONT_SIZE = 10; - -static const QMap APP_FONT_DESCRIPTIONS = { - {AppFont::InterfaceDefault, QT_TR_NOOP("Default font for menus, dialogs and widgets")}, - {AppFont::ChatLog, QT_TR_NOOP("Font for chat messages and logs")}, - {AppFont::CardText, QT_TR_NOOP("Font for text rendered on cards")}, - {AppFont::DeckList, QT_TR_NOOP("Font for deck lists and the deck editor")}, -}; - -static QString roleDisplayName(AppFont::Role role) -{ - switch (role) { - case AppFont::InterfaceDefault: - return FontGridWidget::tr("Interface default"); - case AppFont::ChatLog: - return FontGridWidget::tr("Chat log"); - case AppFont::CardText: - return FontGridWidget::tr("Card text"); - case AppFont::DeckList: - return FontGridWidget::tr("Deck list"); - default: - return QString(); - } -} - -FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent) -{ - auto *grid = new QGridLayout(this); - grid->setSpacing(3); - grid->setContentsMargins(12, 8, 12, 8); - grid->setColumnStretch(0, 0); - grid->setColumnStretch(1, 1); - grid->setColumnStretch(2, 0); - - QMetaEnum fontEnum = QMetaEnum::fromType(); - for (int i = 0; i < fontEnum.keyCount(); ++i) { - auto role = static_cast(fontEnum.value(i)); - - auto *label = new QLabel(this); - grid->addWidget(label, i, 0); - - auto *familyCombo = new QFontComboBox(this); - grid->addWidget(familyCombo, i, 1); - connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); }); - - auto *sizeSpin = new QSpinBox(this); - sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE); - grid->addWidget(sizeSpin, i, 2); - connect(sizeSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); }); - - label->setBuddy(familyCombo); - - fontRows[role] = {label, familyCombo, sizeSpin}; - } - - retranslateUi(); -} - -void FontGridWidget::loadFonts(const FontConfig &cfg) -{ - // Suppress fontsChanged while materializing fallback rows so the initial - // load never reads as a user edit (mirrors paletteGrid blocking). - blockSignals(true); - QMetaEnum fontEnum = QMetaEnum::fromType(); - for (int i = 0; i < fontEnum.keyCount(); ++i) { - auto role = static_cast(fontEnum.value(i)); - const FontRow &row = fontRows[role]; - - // Roles absent from the file resolve the same way the runtime does: an unset - // role falls back to the config's InterfaceDefault, and only then to the - // startup font. appFont() would return the live preview instead, so Reset and - // a first load must not measure against it. - const QFont fallback = cfg.fonts.value(AppFont::InterfaceDefault, themeManager->defaultAppFont(role)); - const QFont font = cfg.fonts.value(role, fallback); - row.familyCombo->setCurrentFont(font); - - int size = font.pointSize(); - if (size <= 0) { - size = qApp->font().pointSize(); - } - if (size <= 0) { - size = FALLBACK_FONT_SIZE; - } - row.sizeSpin->setValue(qBound(MIN_FONT_SIZE, size, MAX_FONT_SIZE)); - } - blockSignals(false); -} - -FontConfig FontGridWidget::currentFontConfig() const -{ - FontConfig cfg; - QMetaEnum fontEnum = QMetaEnum::fromType(); - for (int i = 0; i < fontEnum.keyCount(); ++i) { - auto role = static_cast(fontEnum.value(i)); - const FontRow &row = fontRows.value(role); - - // Canonical family + point size only: a fresh QFont carries default - // (non-bold, non-italic) weight/style, so nothing combo-side leaks in. - QFont font(row.familyCombo->currentFont().family()); - font.setPointSize(row.sizeSpin->value()); - cfg.fonts[role] = font; - } - return cfg; -} - -void FontGridWidget::retranslateUi() -{ - QMetaEnum fontEnum = QMetaEnum::fromType(); - for (int i = 0; i < fontEnum.keyCount(); ++i) { - auto role = static_cast(fontEnum.value(i)); - const FontRow &row = fontRows.value(role); - - row.label->setText(roleDisplayName(role)); - row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); - row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); - row.familyCombo->setAccessibleName(roleDisplayName(role)); - row.sizeSpin->setToolTip(tr("Point size for %1").arg(roleDisplayName(role))); - row.sizeSpin->setAccessibleName(tr("Point size for %1").arg(roleDisplayName(role))); - row.sizeSpin->setSuffix(tr(" pt")); - } -} diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.h b/cockatrice/src/interface/palette_editor/font_grid_widget.h deleted file mode 100644 index 8c80306d3..000000000 --- a/cockatrice/src/interface/palette_editor/font_grid_widget.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef COCKATRICE_FONT_GRID_WIDGET_H -#define COCKATRICE_FONT_GRID_WIDGET_H - -#include "../theme_config.h" - -#include -#include -#include - -class QFontComboBox; -class QGridLayout; -class QLabel; -class QSpinBox; - -// Per-role font editors — one (family, point size) row per AppFont::Role. -// Owns the load/read round-trip for FontConfig but has no file I/O, mirroring -// PaletteGridWidget. Only family and point size are edited; weight and italic -// stay caller-side concerns and are stripped when reading back. -class FontGridWidget : public QWidget -{ - Q_OBJECT -public: - explicit FontGridWidget(QWidget *parent = nullptr); - - void loadFonts(const FontConfig &cfg); - FontConfig currentFontConfig() const; - void retranslateUi(); - -signals: - void fontsChanged(); - -private: - struct FontRow - { - QLabel *label = nullptr; - QFontComboBox *familyCombo = nullptr; - QSpinBox *sizeSpin = nullptr; - }; - - QMap fontRows; -}; - -#endif // COCKATRICE_FONT_GRID_WIDGET_H diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index 5bcfbb251..b6271291d 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -2,7 +2,6 @@ #include "../theme_config.h" #include "../theme_manager.h" -#include "font_grid_widget.h" #include "palette_generator.h" #include "palette_grid_widget.h" #include "quick_setup_panel.h" @@ -42,7 +41,6 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt // Load both scheme configs upfront so switching is instant loadSchemes(); - loadFonts(); loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light"; @@ -127,33 +125,6 @@ void PaletteEditorDialog::setupUi() paletteGrid->setVisible(false); root->addWidget(paletteGrid, 1); - // Font section — sibling to the palette grid, sharing its collapsible - // advanced area pattern. Fonts are shared per theme, not split by scheme. - fontToggleButton = new QPushButton(this); - fontToggleButton->setCheckable(true); - fontToggleButton->setChecked(false); - fontToggleButton->setFlat(true); - fontToggleButton->setStyleSheet("QPushButton { text-align: left; padding: 5px 12px; font-weight: bold; }" - "QPushButton:checked { }"); - root->addWidget(fontToggleButton); - - fontSeparator = makeSeparator(); - fontSeparator->setVisible(false); - root->addWidget(fontSeparator); - - fontSection = new QWidget; - auto *fontSectionLayout = new QVBoxLayout(fontSection); - fontSectionLayout->setContentsMargins(0, 0, 0, 0); - fontSectionLayout->setSpacing(0); - fontNoteLabel = new QLabel(fontSection); - fontNoteLabel->setContentsMargins(12, 4, 12, 0); - fontNoteLabel->setWordWrap(true); - fontSectionLayout->addWidget(fontNoteLabel); - fontGrid = new FontGridWidget(fontSection); - fontSectionLayout->addWidget(fontGrid); - fontSection->setVisible(false); - root->addWidget(fontSection); - // Footer root->addWidget(makeSeparator()); footer = new QWidget; @@ -186,10 +157,6 @@ void PaletteEditorDialog::setupUi() connect(autoApplyTimer, &QTimer::timeout, this, &PaletteEditorDialog::onApply); connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &PaletteEditorDialog::onGenerateFromAccent); connect(paletteGrid, &PaletteGridWidget::paletteChanged, this, [this] { autoApplyTimer->start(); }); - connect(fontGrid, &FontGridWidget::fontsChanged, this, [this] { - fontsDirty = true; - autoApplyTimer->start(); - }); connect(revertButton, &QPushButton::clicked, this, &PaletteEditorDialog::onRevertToDefault); connect(resetBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onReset); connect(saveBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onSave); @@ -199,32 +166,15 @@ void PaletteEditorDialog::setupUi() paletteGridToggleButton->setText(open ? tr("▼ Edit Palette") : tr("▶ Edit Palette")); paletteGridSeparator->setVisible(open); paletteGrid->setVisible(open); - updateAdvancedHeight(); - }); - connect(fontToggleButton, &QPushButton::toggled, this, [this](bool open) { - fontToggleButton->setText(open ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts")); - fontSeparator->setVisible(open); - fontSection->setVisible(open); - updateAdvancedHeight(); - }); -} -void PaletteEditorDialog::updateAdvancedHeight() -{ - int height = 220; - if (paletteGridToggleButton->isChecked()) { - height += 460; - } - if (fontToggleButton->isChecked()) { - height += 200; - } - setMinimumHeight(height); - - if (paletteGrid->isVisible() || fontSection->isVisible()) { - resize(width(), height); - } else { - adjustSize(); // shrinks to fit just the visible content - } + if (open) { + setMinimumHeight(680); + resize(width(), 680); + } else { + setMinimumHeight(220); + adjustSize(); // shrinks to fit just the visible content + } + }); } void PaletteEditorDialog::retranslateUi() @@ -239,16 +189,12 @@ void PaletteEditorDialog::retranslateUi() editingLabel->setText(tr("Editing:")); paletteGridToggleButton->setText(tr("▶ Edit Palette")); paletteGridToggleButton->setToolTip(tr("Show or hide the per-role colour grid for manual tweaks")); - fontToggleButton->setText(fontSection->isVisible() ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts")); - fontToggleButton->setToolTip(tr("Show or hide the per-purpose font settings for this theme")); - fontNoteLabel->setText(tr("Fonts belong to theme %1 (shared by Light and Dark schemes)").arg(themeName)); - fontGrid->retranslateUi(); revertButton->setText(tr("↺ Revert to theme default")); resetBtn->setText(tr("Reset")); saveBtn->setText(tr("Save")); - resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette and fonts")); - saveBtn->setToolTip(tr("Write palette-%1.toml and fonts.toml, then reload the theme").arg(loadedScheme.toLower())); + resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette")); + saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower())); if (saveDir.isEmpty() || !ThemeManager::isDirReallyWritable(saveDir)) { saveBtn->setEnabled(false); @@ -282,13 +228,6 @@ void PaletteEditorDialog::loadSchemes() } } -void PaletteEditorDialog::loadFonts() -{ - savedFontConfig = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName); - fontGrid->loadFonts(savedFontConfig); - fontsDirty = false; -} - void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme) { QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight); @@ -319,9 +258,6 @@ void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensi void PaletteEditorDialog::onApply() { themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme); - if (fontsDirty) { - themeManager->previewFonts(fontGrid->currentFontConfig()); - } } void PaletteEditorDialog::onSave() @@ -372,20 +308,6 @@ void PaletteEditorDialog::onSave() savedConfig[it.key()] = it.value(); } - // Persist the shared per-theme fonts when they changed. - if (fontsDirty) { - const FontConfig fontCfg = fontGrid->currentFontConfig(); - if (fontCfg != savedFontConfig) { - if (!ThemeManager::saveFontConfig(saveDir, fontCfg)) { - QMessageBox::warning(this, tr("Save failed"), - tr("Could not write %1 to:\n%2").arg(FontConfig::fileName(), saveDir)); - return; - } - savedFontConfig = fontCfg; - } - fontsDirty = false; - } - themeManager->reloadCurrentTheme(); accept(); } @@ -394,12 +316,6 @@ void PaletteEditorDialog::onReset() { workingConfig[loadedScheme] = savedConfig[loadedScheme]; paletteGrid->loadPalette(savedConfig[loadedScheme]); - // Restore the live theme before re-reading the grid: loadFonts resolves - // unset roles, and the preview has currentFonts pointing at the edited - // config until the theme is reloaded. - themeManager->reloadCurrentTheme(); - fontGrid->loadFonts(savedFontConfig); - fontsDirty = false; seedAccentFromScheme(loadedScheme); } @@ -434,27 +350,6 @@ void PaletteEditorDialog::onRevertToDefault() workingConfig[loadedScheme] = def; paletteGrid->loadPalette(def); seedAccentFromScheme(loadedScheme); - - // Revert the shared per-theme fonts the same way: delete the user-written - // fonts.toml so the theme falls back to its shipped default, then reflect - // the resolved fonts in the editor. - QFile::remove(QDir(saveDir).absoluteFilePath(FontConfig::fileName())); - - // Reload the live theme so the font revert takes effect immediately. - themeManager->reloadCurrentTheme(); - - const FontConfig fontDef = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName); - savedFontConfig = fontDef; - fontGrid->loadFonts(fontDef); - fontsDirty = false; -} - -void PaletteEditorDialog::reject() -{ - // Discard any unsaved live preview (palette and fonts) so closing without - // Save restores the active theme instead of leaking the preview globally. - themeManager->reloadCurrentTheme(); - QDialog::reject(); } void PaletteEditorDialog::changeEvent(QEvent *e) diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h index 5e47536b5..e29a70cb0 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h @@ -15,7 +15,6 @@ class QComboBox; class QDialogButtonBox; class QPushButton; class PaletteGridWidget; -class FontGridWidget; class QuickSetupPanel; class QFrame; class QWidget; @@ -26,7 +25,6 @@ class PaletteEditorDialog : public QDialog public: explicit PaletteEditorDialog(const QString &themeDirPath, const QString &themeName, QWidget *parent = nullptr); void loadSchemes(); - void reject() override; private slots: void onSave(); @@ -40,10 +38,8 @@ private: void setupUi(); void retranslateUi(); void refreshChromePalettes(); - void updateAdvancedHeight(); void loadScheme(const QString &scheme); // snapshot current, switch, load void seedAccentFromScheme(const QString &scheme); - void loadFonts(); // shared per theme, not split by light/dark // Sub-widgets QWidget *header; @@ -53,11 +49,6 @@ private: PaletteGridWidget *paletteGrid = nullptr; QPushButton *paletteGridToggleButton = nullptr; QFrame *paletteGridSeparator = nullptr; - FontGridWidget *fontGrid = nullptr; - QPushButton *fontToggleButton = nullptr; - QFrame *fontSeparator = nullptr; - QWidget *fontSection = nullptr; - QLabel *fontNoteLabel = nullptr; QWidget *footer; QComboBox *schemeComboBox = nullptr; QDialogButtonBox *buttonBox = nullptr; @@ -77,18 +68,10 @@ private: QMap workingConfig; QMap savedConfig; - // Fonts are shared per theme (fonts.toml, not split by light/dark), so a - // single saved config suffices; the live edits are read from fontGrid. - FontConfig savedFontConfig; - // Set only by user-driven fontsChanged after the initial load completes. - // Gates both previewFonts and the fonts.toml write so a palette-only Save - // never materializes fallback fonts to disk. - bool fontsDirty = false; - QTimer *autoApplyTimer = nullptr; protected: void changeEvent(QEvent *e) override; }; -#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H +#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp index 392f01d0d..463c4038d 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp @@ -76,18 +76,6 @@ PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent) layout = QtUtils::makeLayout(this); layout->addWidget(scroll); - - connect(themeManager, &ThemeManager::fontsChanged, this, &PaletteGridWidget::applyInterfaceDefaultFont); -} - -void PaletteGridWidget::applyInterfaceDefaultFont() -{ - // The editor's own chrome follows the InterfaceDefault theme role; bold stays caller-side. - for (auto *label : headerLabels) { - QFont headerFont = themeManager->appFont(AppFont::InterfaceDefault); - headerFont.setBold(true); - label->setFont(headerFont); - } } void PaletteGridWidget::buildGrid(QWidget *host) @@ -113,8 +101,7 @@ void PaletteGridWidget::buildGrid(QWidget *host) auto *label = new QLabel(groupHeaders[col], host); label->setAlignment(Qt::AlignCenter); label->setToolTip(groupTips[col]); - // Family comes from the InterfaceDefault theme role; bold stays caller-side. - label->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); + label->setFont(QtUtils::boldFont(label->font())); label->setAutoFillBackground(true); label->setContentsMargins(4, 4, 4, 4); grid->addWidget(label, 0, col + 1); @@ -159,7 +146,7 @@ void PaletteGridWidget::buildGrid(QWidget *host) auto *appHeader = new QLabel(tr("App colors"), host); appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette")); - appHeader->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); + appHeader->setFont(QtUtils::boldFont(appHeader->font())); appHeader->setAutoFillBackground(true); appHeader->setContentsMargins(4, 4, 4, 4); grid->addWidget(appHeader, appHeaderRow, 0, 1, 4); diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.h b/cockatrice/src/interface/palette_editor/palette_grid_widget.h index 7c527a064..f1ff41ec2 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.h +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.h @@ -34,10 +34,6 @@ private: void changeEvent(QEvent *e); void refreshChromePalettes(); -private slots: - void applyInterfaceDefaultFont(); - -private: QMap> colorButtons; QMap appColorButtons; QScrollArea *scroll; diff --git a/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp index 00eac3253..04f98dcc3 100644 --- a/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp +++ b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp @@ -1,6 +1,5 @@ #include "quick_setup_panel.h" -#include "../theme_manager.h" #include "color_button.h" #include @@ -29,15 +28,10 @@ QuickSetupPanel::QuickSetupPanel(QWidget *parent) : QWidget(parent) labelLow = new QLabel(this); labelHigh = new QLabel(this); - // Family comes from the InterfaceDefault theme role; the *0.82 size stays caller-side. - auto applySmallFont = [this]() { - QFont small = themeManager->appFont(AppFont::InterfaceDefault); - small.setPointSizeF(small.pointSizeF() * 0.82); - labelLow->setFont(small); - labelHigh->setFont(small); - }; - applySmallFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, applySmallFont); + QFont small = labelLow->font(); + small.setPointSizeF(small.pointSizeF() * 0.82); + labelLow->setFont(small); + labelHigh->setFont(small); QPalette dimmed = labelLow->palette(); dimmed.setColor(QPalette::WindowText, qApp->palette().color(QPalette::Mid)); labelLow->setPalette(dimmed); diff --git a/cockatrice/src/interface/recording.cpp b/cockatrice/src/interface/recording.cpp deleted file mode 100644 index 2b11255f9..000000000 --- a/cockatrice/src/interface/recording.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "recording.h" - -#include "pqEventTypes.h" - -#include -#include -#include -#include - -XMLEventObserver::XMLEventObserver(QObject *parent) : pqEventObserver(parent) -{ -} - -XMLEventObserver::~XMLEventObserver() = default; - -void XMLEventObserver::setStream(QTextStream *stream) -{ - if (xmlStream) { - xmlStream->writeEndElement(); - xmlStream->writeEndDocument(); - xmlStream.reset(); - } - if (Stream) { - *Stream << xmlString; - } - xmlString.clear(); - pqEventObserver::setStream(stream); - if (Stream) { - xmlStream.reset(new QXmlStreamWriter(&xmlString)); - xmlStream->setAutoFormatting(true); - xmlStream->writeStartDocument(); - xmlStream->writeStartElement(QStringLiteral("events")); - } -} - -void XMLEventObserver::onRecordEvent(const QString &widget, - const QString &command, - const QString &arguments, - const int &eventType) -{ - if (!xmlStream) { - return; - } - - xmlStream->writeStartElement(QStringLiteral("event")); - xmlStream->writeAttribute(QStringLiteral("widget"), widget); - if (eventType == pqEventTypes::ACTION_EVENT) { - xmlStream->writeAttribute(QStringLiteral("command"), command); - } else { // pqEventTypes::CHECK_EVENT - xmlStream->writeAttribute(QStringLiteral("property"), command); - } - xmlStream->writeAttribute(QStringLiteral("arguments"), arguments); - xmlStream->writeEndElement(); -} - -XMLEventSource::XMLEventSource(QObject *parent) : pqEventSource(parent) -{ -} - -XMLEventSource::~XMLEventSource() = default; - -void XMLEventSource::setContent(const QString &xmlFileName) -{ - xmlStream.reset(); - - QFile xml(xmlFileName); - if (!xml.open(QIODevice::ReadOnly)) { - qCWarning(RecordingLog) << "Could not read test file" << xmlFileName; - return; - } - - xmlStream.reset(new QXmlStreamReader(xml.readAll())); -} - -int XMLEventSource::getNextEvent(QString &widget, QString &command, QString &arguments, int &eventType) -{ - if (!xmlStream) { - return DONE; - } - - while (!xmlStream->atEnd()) { - if (xmlStream->readNext() != QXmlStreamReader::StartElement || xmlStream->name() != QStringLiteral("event")) { - continue; - } - - const QXmlStreamAttributes attributes = xmlStream->attributes(); - eventType = pqEventTypes::ACTION_EVENT; - widget = attributes.value(QStringLiteral("widget")).toString(); - command = attributes.value(QStringLiteral("command")).toString(); - if (command.isEmpty()) { - // A recorded check event carries a property instead of a command. - command = attributes.value(QStringLiteral("property")).toString(); - } - arguments = attributes.value(QStringLiteral("arguments")).toString(); - return SUCCESS; - } - - if (xmlStream->hasError()) { - qCWarning(RecordingLog) << "Malformed test file:" << xmlStream->errorString(); - } - return DONE; -} diff --git a/cockatrice/src/interface/recording.h b/cockatrice/src/interface/recording.h deleted file mode 100644 index a5d813ea9..000000000 --- a/cockatrice/src/interface/recording.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef COCKATRICE_RECORDING_H -#define COCKATRICE_RECORDING_H - -#include "pqEventObserver.h" -#include "pqEventSource.h" - -#include -#include - -inline Q_LOGGING_CATEGORY(RecordingLog, "recording"); - -class QXmlStreamReader; -class QXmlStreamWriter; - -/** - * @brief Writes a session recording as an XML test file. - * - * The events are buffered in memory and flushed to the stream when recording - * ends, because the stream is handed over once the file name is known. - */ -class XMLEventObserver : public pqEventObserver -{ - QScopedPointer xmlStream; - QString xmlString; - -public: - explicit XMLEventObserver(QObject *parent); - ~XMLEventObserver() override; - -protected: - void setStream(QTextStream *stream) override; - void onRecordEvent(const QString &widget, - const QString &command, - const QString &arguments, - const int &eventType) override; -}; - -/** - * @brief Plays the events of an XML test file back, one at a time. - */ -class XMLEventSource : public pqEventSource -{ - QScopedPointer xmlStream; - -public: - explicit XMLEventSource(QObject *parent); - ~XMLEventSource() override; - -protected: - void setContent(const QString &xmlFileName) override; - int getNextEvent(QString &widget, QString &command, QString &arguments, int &eventType) override; -}; - -#endif // COCKATRICE_RECORDING_H diff --git a/cockatrice/src/interface/theme_config.cpp b/cockatrice/src/interface/theme_config.cpp index 54143d7d3..652e322c8 100644 --- a/cockatrice/src/interface/theme_config.cpp +++ b/cockatrice/src/interface/theme_config.cpp @@ -304,150 +304,4 @@ QPalette PaletteConfig::apply(QPalette base) const } return base; -} - -bool FontConfig::hasFonts() const -{ - return !fonts.isEmpty(); -} - -QString FontConfig::toToml() const -{ - QMetaEnum roleEnum = QMetaEnum::fromType(); - - QString entries; - - for (auto it = fonts.cbegin(); it != fonts.cend(); ++it) { - const char *roleName = roleEnum.valueToKey(it.key()); - - if (!roleName) { - continue; - } - - QString family = it.value().family().trimmed(); - int pointSize = it.value().pointSize(); - - if (family.isEmpty() || pointSize <= 0) { - continue; - } - - entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize); - } - - if (entries.isEmpty()) { - return {}; - } - - return "[Fonts]\n" + entries + "\n"; -} - -QString FontConfig::fileName() -{ - return "fonts.toml"; -} - -FontConfig FontConfig::fromFile(const QString &filePath) -{ - FontConfig cfg; - - QFile f(filePath); - - if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { - return cfg; - } - - QMetaEnum roleEnum = QMetaEnum::fromType(); - - QString currentSection; - - QTextStream in(&f); - - while (!in.atEnd()) { - QString line = in.readLine().trimmed(); - - if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) { - continue; - } - - if (line.startsWith('[') && line.endsWith(']')) { - currentSection = line.mid(1, line.length() - 2).trimmed(); - continue; - } - - if (currentSection.compare("Fonts", Qt::CaseInsensitive) != 0) { - continue; - } - - int eq = line.indexOf('='); - - if (eq < 0) { - continue; - } - - QString key = line.left(eq).trimmed(); - QString value = line.mid(eq + 1).trimmed(); - - // Strip inline comments if preceded by whitespace - for (int i = 1; i < value.size(); ++i) { - if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) { - value = value.left(i).trimmed(); - break; - } - } - - // Strip surrounding quotes, if any - if (value.size() >= 2 && - ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) { - value = value.mid(1, value.size() - 2).trimmed(); - } - - int comma = value.lastIndexOf(','); - - if (comma < 0) { - continue; - } - - QString family = value.left(comma).trimmed(); - - // Tolerate a partially-quoted family, e.g. "Inter", 10 - if (family.size() >= 2 && - ((family.startsWith('"') && family.endsWith('"')) || (family.startsWith('\'') && family.endsWith('\'')))) { - family = family.mid(1, family.size() - 2).trimmed(); - } - - bool ok = false; - int pointSize = value.mid(comma + 1).trimmed().toInt(&ok); - - if (!ok || family.isEmpty() || pointSize <= 0) { - continue; - } - - if (key.startsWith("AppFont::")) { - key = key.mid(QStringLiteral("AppFont::").size()); - } - - int roleInt = roleEnum.keyToValue(key.toUtf8().constData()); - - if (roleInt < 0) { - continue; - } - - // Canonical family + point size only: a fresh QFont carries default - // (non-bold, non-italic) weight/style, so nothing caller-side leaks in. - // No validation against installed fonts; Qt substitution applies. - QFont font(family); - font.setPointSize(pointSize); - cfg.fonts[static_cast(roleInt)] = font; - } - - return cfg; -} - -FontConfig FontConfig::fromThemeDir(const QString &themeDirPath) -{ - if (themeDirPath.isEmpty()) { - return {}; - } - - return fromFile(QDir(themeDirPath).absoluteFilePath(fileName())); -} +} \ No newline at end of file diff --git a/cockatrice/src/interface/theme_config.h b/cockatrice/src/interface/theme_config.h index 84601a2bf..8b91613f5 100644 --- a/cockatrice/src/interface/theme_config.h +++ b/cockatrice/src/interface/theme_config.h @@ -2,7 +2,6 @@ #define COCKATRICE_THEME_CONFIG_H #include -#include #include #include #include @@ -24,23 +23,6 @@ enum Role Q_ENUM_NS(Role) } // namespace AppColor -// Application-specific font roles, one configurable family + point size per -// purpose. Stored in a shared fonts.toml under a [Fonts] section (one file per -// theme, not split by light/dark). Only family and point size are stored; -// weight/italic are stripped on load and stay caller-side concerns. -namespace AppFont -{ -Q_NAMESPACE -enum Role -{ - InterfaceDefault, - ChatLog, - CardText, - DeckList, -}; -Q_ENUM_NS(Role) -} // namespace AppFont - struct ThemeConfig { QString colorScheme; @@ -78,33 +60,4 @@ struct PaletteConfig QPalette apply(QPalette base) const; }; -/** @brief Per-purpose font configuration, stored as family + point size only. - * - * Serialized to a shared fonts.toml file with a [Fonts] section, e.g. - * @code - * [Fonts] - * InterfaceDefault = "Inter, 10" - * @endcode - */ -struct FontConfig -{ - QMap fonts; - - bool operator==(const FontConfig &rhs) const - { - return fonts == rhs.fonts; - } - bool operator!=(const FontConfig &rhs) const - { - return !(*this == rhs); - } - bool hasFonts() const; - QString toToml() const; - - static QString fileName(); - - static FontConfig fromFile(const QString &filePath); - static FontConfig fromThemeDir(const QString &themeDirPath); -}; - -#endif // COCKATRICE_THEME_CONFIG_H +#endif // COCKATRICE_THEME_CONFIG_H \ No newline at end of file diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index f120af8e4..893cdc795 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -128,10 +127,8 @@ static QString usableDefaultStyle(const QString &style) ThemeManager::ThemeManager(QObject *parent) : QObject(parent) { defaultStyleName = usableDefaultStyle(qApp->style()->objectName()); - // Capture the untouched application palette and font before any theme is - // applied. + // Capture the untouched application palette before any theme is applied. defaultPalette = qApp->palette(); - defaultFont = qApp->font(); ensureThemeDirectoryExists(); #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, [this] { @@ -443,46 +440,6 @@ PaletteConfig ThemeManager::loadDefaultPaletteConfig(const QString &themeDirPath return cfg; } -FontConfig ThemeManager::loadFontConfig(const QString &themeDirPath) -{ - if (themeDirPath.isEmpty()) { - return {}; - } - return FontConfig::fromThemeDir(themeDirPath); -} - -FontConfig ThemeManager::loadDefaultFontConfig(const QString &themeDirPath, const QString &themeName) -{ - FontConfig cfg = FontConfig::fromThemeDir(themeDirPath); - if (!cfg.hasFonts()) { - // The shipped fonts.toml may live in the system theme directory rather - // than the resolved (user) theme directory, so built-in themes still - // get their curated fonts. - cfg = FontConfig::fromThemeDir(QDir(systemThemesBasePath()).absoluteFilePath(themeName)); - } - return cfg; -} - -bool ThemeManager::saveFontConfig(const QString &themeDirPath, const FontConfig &cfg) -{ - if (themeDirPath.isEmpty()) { - return false; - } - - QDir dir(themeDirPath); - if (!dir.exists()) { - dir.mkpath("."); - } - - QFile f(dir.absoluteFilePath(FontConfig::fileName())); - if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { - return false; - } - - QTextStream(&f) << cfg.toToml(); - return true; -} - bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg) { if (!savePaletteConfig(themeDirPath, colorScheme, cfg)) { @@ -531,24 +488,6 @@ void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &schem applyStyleAndPalette(themeName, themeCfg, cfg, scheme); } -void ThemeManager::previewFonts(const FontConfig &cfg) -{ - currentFonts = cfg; - - // QApplication::setFont already sends a font change to every widget, so a - // repolish pass is not needed for fonts (unlike the palette path). - const auto it = cfg.fonts.constFind(AppFont::InterfaceDefault); - if (it != cfg.fonts.constEnd()) { - qApp->setFont(it.value()); - } else { - // A config without InterfaceDefault means "no override": restore the - // startup font instead of leaving the last preview applied. - qApp->setFont(defaultFont); - } - - emit fontsChanged(); -} - void ThemeManager::applyStyleAndPalette(const QString &themeName, const ThemeConfig &themeCfg, const PaletteConfig &palCfg, @@ -652,56 +591,6 @@ QColor ThemeManager::appColor(AppColor::Role role) const return accent; } -QFont ThemeManager::appFont(AppFont::Role role) const -{ - const auto it = currentFonts.fonts.constFind(role); - if (it != currentFonts.fonts.constEnd()) { - return it.value(); - } - - // Role fallbacks preserve the pre-theme defaults when no fonts.toml is - // present: an unset role resolves to the application font. Surfaces that - // hardcoded a different family ask for it explicitly: counter painters - // through cardTextFont(), the deck view headers and printed deck list - // through deckListFont(), the report dialogs through - // report_utils::reportLogFont(). Only the family basis is fixed there; - // sizes stay caller-side. - return qApp->font(); -} - -bool ThemeManager::hasAppFont(AppFont::Role role) const -{ - return currentFonts.fonts.contains(role); -} - -QFont ThemeManager::cardTextFont() const -{ - if (hasAppFont(AppFont::CardText)) { - return appFont(AppFont::CardText); - } - QFont serifFont(QStringLiteral("Serif")); - serifFont.setStyleHint(QFont::Serif); - return serifFont; -} - -QFont ThemeManager::deckListFont() const -{ - if (hasAppFont(AppFont::DeckList)) { - return appFont(AppFont::DeckList); - } - QFont serifFont(QStringLiteral("Serif")); - serifFont.setStyleHint(QFont::Serif); - return serifFont; -} - -QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const -{ - // No per-role scaling and no preview: an unset role resolves to the font - // captured at startup, which is what the theme falls back to once the - // editor closes. - return defaultFont; -} - void ThemeManager::themeChangedSlot() { QString themeName = SettingsCache::instance().getThemeName(); @@ -753,19 +642,6 @@ void ThemeManager::themeChangedSlot() applyStyleAndPalette(themeName, themeCfg, palette, activeScheme); - // ── Load fonts: shared per theme, no light/dark split ─────────────────── - FontConfig fonts = loadDefaultFontConfig(dirPath, themeName); - currentFonts = fonts; - const auto fontIt = currentFonts.fonts.constFind(AppFont::InterfaceDefault); - if (fontIt != currentFonts.fonts.constEnd()) { - qApp->setFont(fontIt.value()); - } else { - // No theme font: restore the pristine application font captured at - // startup, so a reverted preview cannot leak into the live theme. - qApp->setFont(defaultFont); - } - emit fontsChanged(); - QStringList resources; if (!dirPath.isEmpty()) { resources << dir.absolutePath(); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index ffe1043b5..c1ab65685 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -48,15 +47,10 @@ private: // palette is applied. Used as the base when a theme supplies no palette, so // switching away from a custom palette restores the original colours. QPalette defaultPalette; - // Pristine application font captured at startup, before any theme font is - // applied. Restored when a theme (or a preview) supplies no InterfaceDefault - // font, so switching away from a themed font cannot leave it behind. - QFont defaultFont; QString currentThemePath; std::array brushes; QStringMap availableThemes; QMap currentAppColors; - FontConfig currentFonts; /* Internal cache for multiple backgrounds */ @@ -111,9 +105,6 @@ public: // Load/save per-scheme palette colors static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme); static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg); - // Load/save shared per-theme fonts (fonts.toml, not split by scheme) - static FontConfig loadFontConfig(const QString &themeDirPath); - static bool saveFontConfig(const QString &themeDirPath, const FontConfig &cfg); // Resolve prefix to a scheme-qualified "theme:" path. Existence is probed // internally across the formats themes may ship (.png/.jpg/.svg), so // callers load the returned path directly. Prefers "-" @@ -132,9 +123,6 @@ public: // theme directory when it is absent from the resolved (user) directory. static PaletteConfig loadDefaultPaletteConfig(const QString &themeDirPath, const QString &themeName, const QString &colorScheme); - // Load the theme's shipped default fonts, falling back to the system theme - // directory when fonts.toml is absent from the resolved (user) directory. - static FontConfig loadDefaultFontConfig(const QString &themeDirPath, const QString &themeName); /** @brief Writes cfg to disk as the theme's palette-.toml and updates the * theme's stored colour scheme to match. Shared by PaletteEditorDialog::onSave * and FirstRunWizard's theme step so the two "generate + keep" paths can't drift. */ @@ -144,31 +132,10 @@ public: void reloadCurrentTheme(); void previewPalette(const PaletteConfig &cfg, const QString &scheme); - void previewFonts(const FontConfig &cfg); // Resolves an application color role: the theme's stored [AppColors] value // when present, otherwise a palette-accent-derived fallback. QColor appColor(AppColor::Role role) const; - /** @brief Resolves an application font role: the theme's stored [Fonts] - * value when present, otherwise the application font. Roles that - * had a historical hardcoded family ask for it through - * cardTextFont() and deckListFont() instead. */ - QFont appFont(AppFont::Role role) const; - /** @brief Whether the active theme defines this role in fonts.toml. */ - bool hasAppFont(AppFont::Role role) const; - /** @brief The CardText role with its historical Serif fallback. Used by the - * counter painters, which were Serif before the role existed. */ - QFont cardTextFont() const; - /** @brief The DeckList role with its historical Serif fallback. Used by the - * two surfaces that hardcoded Serif before the role existed (the - * deck view zone headers and the printed deck list); every other - * DeckList reader falls back to the application font. */ - QFont deckListFont() const; - /** @brief Resolves an application font role against the startup font only, - * ignoring any previewed config in currentFonts. Editors that show - * "what this role resolves to" must use this, because appFont() - * reflects a live preview while the editor is open. */ - QFont defaultAppFont(AppFont::Role role) const; QBrush &getBgBrush(Role zone); QBrush getExtraBgBrush(Role zone, int zoneId = 0); @@ -177,7 +144,6 @@ protected slots: signals: void themeChanged(); void paletteChanged(); - void fontsChanged(); }; extern ThemeManager *themeManager; diff --git a/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp b/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp index 8475a0ed9..c4b347047 100644 --- a/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp +++ b/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp @@ -1,6 +1,5 @@ #include "art_crop_attribution.h" -#include "../../theme_manager.h" #include "libcockatrice/card/printing/printing_info.h" #include @@ -36,10 +35,7 @@ QRectF paintArtAttribution(QPainter &painter, painter.save(); - // Family comes from the CardText theme role; the incoming painter size - // stays the basis for the scale factor exactly as before. QFont font = painter.font(); - font.setFamily(themeManager->appFont(AppFont::CardText).family()); font.setPointSizeF(qMax(6.0, font.pointSizeF() * scale)); painter.setFont(font); diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index 56fe0bda4..04fe9ab69 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -1,7 +1,5 @@ #include "card_info_picture_with_text_overlay_widget.h" -#include "../../theme_manager.h" - #include #include #include @@ -45,7 +43,6 @@ CardInfoPictureWithTextOverlayWidget::CardInfoPictureWithTextOverlayWidget(QWidg fontSize(fontSize), textAlignment(alignment), highlighted(false) { this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - connect(themeManager, &ThemeManager::fontsChanged, this, &CardInfoPictureWithTextOverlayWidget::applyCardTextFont); } /** @@ -100,13 +97,6 @@ void CardInfoPictureWithTextOverlayWidget::setTextAlignment(const Qt::Alignment update(); } -void CardInfoPictureWithTextOverlayWidget::applyCardTextFont() -{ - // The overlay paint reads the CardText role font at paint time, so a - // theme font change only needs a repaint here. - update(); -} - void CardInfoPictureWithTextOverlayWidget::setHighlighted(bool _highlighted) { if (highlighted == _highlighted) { @@ -178,10 +168,8 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) return; } - // Calculate the optimal font size. The family comes from the CardText - // theme role; the fontSize member stays the starting size and the - // shrink-to-fit logic below is unchanged. - QFont font = themeManager->appFont(AppFont::CardText); + // Calculate the optimal font size + QFont font = painter.font(); int optimalFontSize = fontSize; // Start with the user-defined font size const QFontMetrics baseMetrics(font); int textWidth = pixmapRect.width(); @@ -296,7 +284,7 @@ QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const const QSize pixmapSize = pixmap.isNull() ? QSize(0, 0) : pixmap.size(); // Get the font metrics for the overlay text - QFont font = themeManager->appFont(AppFont::CardText); + QFont font; font.setPointSize(fontSize); const QFontMetrics fontMetrics(font); diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h index 41019f543..6c547dc13 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h @@ -47,9 +47,6 @@ protected: void mousePressEvent(QMouseEvent *event) override; [[nodiscard]] QSize minimumSizeHint() const override; -private slots: - void applyCardTextFont(); - private: void drawOutlinedText(QPainter &painter, const QRect &textRect, diff --git a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp index df2a645f1..85dba6468 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp @@ -1,6 +1,5 @@ #include "resizable_panel.h" -#include "../interface/theme_manager.h" #include "abstract_analytics_panel_widget.h" #include "libcockatrice/utility/qt_utils.h" @@ -13,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -95,17 +93,9 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid dragButton->setStyleSheet(QString("QPushButton { " "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:1 %2); " - "border: none; color: %3; }" + "border: none; color: %3; font-size: 10px; }" "QPushButton:hover { background: %4; }") .arg(mid.name(), dark.name(), placeholderText.name(), midLight.name())); - // Family comes from the InterfaceDefault theme role; the 10px size stays caller-side. - auto applyDragFont = [this]() { - QFont dragFont = themeManager->appFont(AppFont::InterfaceDefault); - dragFont.setPixelSize(10); - dragButton->setFont(dragFont); - }; - applyDragFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, applyDragFont); resizeHandle->setStyleSheet(QString("QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " "stop:0 %1, stop:1 %2); }") diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp index a9c9cad7a..1e1aefe58 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp @@ -1,7 +1,5 @@ #include "deck_list_style_proxy.h" -#include "../../theme_manager.h" - #include #include #include @@ -10,16 +8,6 @@ #include #include -DeckListStyleProxy::DeckListStyleProxy(QObject *parent) : QIdentityProxyModel(parent) -{ - // FontRole data reads the DeckList role live; rebroadcast so attached - // views re-query instead of waiting for an incidental repaint. - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { - emit layoutAboutToBeChanged(); - emit layoutChanged(); - }); -} - QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const { QModelIndex src = mapToSource(index); @@ -32,8 +20,7 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const bool isCard = src.data(DeckRoles::IsCardRole).toBool(); if (role == Qt::FontRole && !isCard) { - // Family comes from the DeckList theme role; bold stays caller-side. - QFont f = themeManager->appFont(AppFont::DeckList); + QFont f; f.setBold(true); return f; } diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h index 3149da6de..e4d1de4f4 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h @@ -8,7 +8,7 @@ class DeckListStyleProxy : public QIdentityProxyModel { Q_OBJECT public: - explicit DeckListStyleProxy(QObject *parent = nullptr); + using QIdentityProxyModel::QIdentityProxyModel; [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; }; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp index 853a00828..1f567989e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp @@ -33,7 +33,6 @@ class QWidget; DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) { - setObjectName("connectDialog"); previousHostButton = new QRadioButton(tr("Known Hosts"), this); previousHosts = new QComboBox(this); @@ -86,11 +85,6 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) passwordLabel->setBuddy(passwordEdit); passwordEdit->setEchoMode(QLineEdit::Password); - hostEdit->setObjectName("connectHostEdit"); - portEdit->setObjectName("connectPortEdit"); - playernameEdit->setObjectName("connectPlayernameEdit"); - passwordEdit->setObjectName("connectPasswordEdit"); - savePasswordCheckBox = new QCheckBox(tr("&Save password")); autoConnectCheckBox = new QCheckBox(tr("A&uto connect")); @@ -296,13 +290,11 @@ void DlgConnect::updateDisplayInfo(const QString &saveName) playernameEdit->setFocus(); savePasswordCheckBox->setChecked(savePasswordStatus); storedVerifier.clear(); - savedVerifier.clear(); if (savePasswordStatus) { const QString stored = _data.at(4); if (stored.startsWith("$")) { storedVerifier = stored; - savedVerifier = stored; passwordEdit->clear(); passwordEdit->setPlaceholderText(tr("Password saved")); } else { @@ -339,7 +331,6 @@ void DlgConnect::newHostSelected(bool state) passwordEdit->clear(); passwordEdit->setPlaceholderText(QString()); storedVerifier.clear(); - savedVerifier.clear(); saveEdit->clear(); saveEdit->setPlaceholderText(tr("Unique Server Name")); saveEdit->setDisabled(false); @@ -355,19 +346,9 @@ void DlgConnect::passwordSaved(QT_STATE_CHANGED_T state) Q_UNUSED(state); if (savePasswordCheckBox->isChecked()) { autoConnectCheckBox->setEnabled(true); - // Re-ticking keeps a password the user has started typing; otherwise restore the saved - // login this dialog was opened with, so unticking and changing your mind is harmless. - if (passwordEdit->text().isEmpty()) { - storedVerifier = savedVerifier; - passwordEdit->setPlaceholderText(savedVerifier.isEmpty() ? QString() : tr("Password saved")); - } } else { autoConnectCheckBox->setChecked(false); autoConnectCheckBox->setEnabled(false); - // Unticking means the password typed below is sent for this connection: stop sending the - // saved verifier instead, and stop claiming a password is saved. - storedVerifier.clear(); - passwordEdit->setPlaceholderText(QString()); } } @@ -375,11 +356,10 @@ void DlgConnect::actOk() { ServersSettings &servers = SettingsCache::instance().servers(); - // Never write a newly typed plaintext password to disk when a verifier is available: the - // typed value is used for this connection and a fresh verifier is persisted after a - // successful login. Without any verifier we keep the previous (plaintext legacy) behavior. - const QString verifierToSave = storedVerifier.isEmpty() ? savedVerifier : storedVerifier; - const QString passwordToSave = verifierToSave.isEmpty() ? passwordEdit->text() : verifierToSave; + // Never write a newly typed plaintext password to disk when a verifier is already stored: + // the typed value is used for this connection and a fresh verifier is persisted after a + // successful login. Without a stored verifier we keep the previous (plaintext legacy) behavior. + const QString passwordToSave = storedVerifier.isEmpty() ? passwordEdit->text() : storedVerifier; if (newHostButton->isChecked()) { if (saveEdit->text().isEmpty()) { diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_connect.h b/cockatrice/src/interface/widgets/dialogs/dlg_connect.h index dad3623f5..bef5f136e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_connect.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_connect.h @@ -98,10 +98,6 @@ private: QPushButton *btnConnect, *btnForgotPassword, *btnRefreshServers, *btnDeleteServer; QMap> savedHostList; HandlePublicServers *hps; - //! \brief Saved "$scrypt$..." verifier as loaded for the selected profile, kept so that - //! re-ticking "Save password" after unticking restores it. - QString savedVerifier; - //! \brief Verifier in effect for this connection: empty while "Save password" is unticked. QString storedVerifier; const QString placeHolderText = tr("Downloading..."); }; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp index 3845029f7..cbf61a394 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp @@ -40,7 +40,6 @@ class QWidget; DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nullptr) { - setObjectName("editTokensDialog"); nameLabel = new QLabel(tr("&Name:")); nameEdit = new QLineEdit; nameEdit->setMaxLength(MAX_NAME_LENGTH); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp index c1d0676ca..2c0fd3f6f 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp @@ -28,16 +28,12 @@ class QWidget; */ AbstractDlgDeckTextEdit::AbstractDlgDeckTextEdit(QWidget *parent) : QDialog(parent) { - setObjectName("dlgDeckTextEdit"); - contentsEdit = new QPlainTextEdit; - contentsEdit->setObjectName("contentsEdit"); refreshButton = new QPushButton(tr("&Refresh")); connect(refreshButton, &QPushButton::clicked, this, &AbstractDlgDeckTextEdit::actRefresh); auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - buttonBox->setObjectName("buttonBox"); buttonBox->addButton(refreshButton, QDialogButtonBox::ActionRole); connect(buttonBox, &QDialogButtonBox::accepted, this, &AbstractDlgDeckTextEdit::actOK); connect(buttonBox, &QDialogButtonBox::rejected, this, &AbstractDlgDeckTextEdit::reject); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp index 3ebb5c093..858deb751 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp @@ -16,11 +16,8 @@ class QWidget; DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) { - setObjectName("localGameDialog"); - numberPlayersLabel = new QLabel(tr("Players:"), this); numberPlayersEdit = new QSpinBox(this); - numberPlayersEdit->setObjectName("numberPlayersEdit"); numberPlayersEdit->setMinimum(1); numberPlayersEdit->setMaximum(8); numberPlayersEdit->setValue(1); @@ -31,12 +28,10 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) generalGrid->addWidget(numberPlayersLabel, 0, 0); generalGrid->addWidget(numberPlayersEdit, 0, 1); generalGroupBox = new QGroupBox(tr("General"), this); - generalGroupBox->setObjectName("generalGroupBox"); generalGroupBox->setLayout(generalGrid); startingLifeTotalLabel = new QLabel(tr("Starting life total:"), this); startingLifeTotalEdit = new QSpinBox(this); - startingLifeTotalEdit->setObjectName("startingLifeTotalEdit"); startingLifeTotalEdit->setMinimum(-999999999); startingLifeTotalEdit->setMaximum(999999999); startingLifeTotalEdit->setValue(20); @@ -47,14 +42,11 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0); gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1); gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this); - gameSetupOptionsGroupBox->setObjectName("gameSetupOptionsGroupBox"); gameSetupOptionsGroupBox->setLayout(gameSetupGrid); rememberSettingsCheckBox = new QCheckBox(tr("Remember settings"), this); - rememberSettingsCheckBox->setObjectName("rememberSettingsCheckBox"); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - buttonBox->setObjectName("buttonBox"); connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgLocalGameOptions::actOK); connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgLocalGameOptions::reject); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp index caca5da92..dbfe38e26 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp @@ -48,7 +48,6 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) { - setObjectName("manageSetsWindow"); setOrderIsSorted = false; // left toolbar @@ -222,7 +221,6 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) WndSets::~WndSets() { - disconnect(view->header(), &QHeaderView::geometriesChanged, this, &WndSets::saveHeaderState); } void WndSets::closeEvent(QCloseEvent * /*ev*/) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp index a7387a068..2a2758ba5 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp @@ -1,13 +1,11 @@ #include "dlg_my_reports.h" -#include "../interface/theme_manager.h" #include "../utility/report_utils.h" #include "abstract_client.h" #include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/serverinfo_report.pb.h" #include -#include #include #include #include @@ -26,6 +24,7 @@ #include #include #include +#include #include #include @@ -70,8 +69,7 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent) auto *chatGroup = new QGroupBox(tr("Chat Log Context")); chatLogEdit = new QTextEdit; chatLogEdit->setReadOnly(true); - applyChatLogFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, &DlgMyReports::applyChatLogFont); + chatLogEdit->setFont(QtUtils::monospaceFont()); auto *chatLayout = new QVBoxLayout(chatGroup); chatLayout->setContentsMargins(4, 4, 4, 4); chatLayout->addWidget(chatLogEdit); @@ -124,11 +122,6 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent) refreshList(); } -void DlgMyReports::applyChatLogFont() -{ - chatLogEdit->setFont(report_utils::reportLogFont()); -} - void DlgMyReports::refreshList() { selectedReportIdBeforeRefresh = selectedReportId; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h index 90772f051..dc9f90d2d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h @@ -29,7 +29,6 @@ private slots: void reportDetailsResponse(const Response &response); void addComment(); void addCommentResponse(const Response &response); - void applyChatLogFont(); private: void loadReportDetails(const ServerInfo_Report &report); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp index c7c32469b..3ef45020b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp @@ -1,13 +1,10 @@ #include "dlg_report_user.h" -#include "../interface/theme_manager.h" -#include "../utility/report_utils.h" #include "abstract_client.h" #include "libcockatrice/protocol/pb/response.pb.h" #include #include -#include #include #include #include @@ -94,8 +91,7 @@ DlgReportUser::DlgReportUser(AbstractClient *_client, auto *chatLayout = new QVBoxLayout(chatGroup); chatLogEdit = new QTextEdit; chatLogEdit->setReadOnly(true); - applyChatLogFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, &DlgReportUser::applyChatLogFont); + chatLogEdit->setFont(QtUtils::monospaceFont()); if (!_autoChatLog.isEmpty()) { chatLogEdit->setPlainText(_autoChatLog); } else { @@ -124,11 +120,6 @@ DlgReportUser::DlgReportUser(AbstractClient *_client, layout->addWidget(buttonBox); } -void DlgReportUser::applyChatLogFont() -{ - chatLogEdit->setFont(report_utils::reportLogFont()); -} - void DlgReportUser::actSubmit() { const QString description = descriptionEdit->toPlainText().trimmed(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h index 0f2fa5080..c7fce055d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h @@ -27,7 +27,6 @@ public: private slots: void actSubmit(); void reportResponse(const Response &response); - void applyChatLogFont(); private: static QString categoryLabel(const QString &key); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index 3602e67c6..ab870a2c8 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -73,8 +73,6 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) resize(calculateDialogSize(hostSize, available.size(), minimum)); setSizeGripEnabled(true); - setObjectName("settingsDialog"); - connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this, &DlgSettings::updateLanguage); settingsView = new SettingsView(this); @@ -84,7 +82,6 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) buttonBox->addStretch(); okButton = new QPushButton; okButton->setDefault(true); - okButton->setObjectName("settingsOkButton"); connect(okButton, &QPushButton::clicked, this, &DlgSettings::close); buttonBox->addWidget(okButton); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp index 49bacd081..d29aee27f 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp @@ -2,7 +2,6 @@ #include "../deck_share/shared_deck_preview_widget.h" #include "../general/layout_containers/flow_widget.h" -#include "../interface/theme_manager.h" #include #include @@ -36,14 +35,9 @@ DlgSharedDecksPreview::DlgSharedDecksPreview(QWidget *parent, // QLabels render AutoText and markup would otherwise be shown as rich text. auto *titleLabel = new QLabel(tr("Share: %1").arg((shareName.isEmpty() ? tr("Untitled") : shareName).toHtmlEscaped()), this); - // Family comes from the InterfaceDefault theme role; the +2 size and bold stay caller-side. - auto applyTitleFont = [titleLabel]() { - QFont titleFont = QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)); - titleFont.setPointSize(titleFont.pointSize() + 2); - titleLabel->setFont(titleFont); - }; - applyTitleFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, applyTitleFont); + QFont titleFont = QtUtils::boldFont(titleLabel->font()); + titleFont.setPointSize(titleFont.pointSize() + 2); + titleLabel->setFont(titleFont); mainLayout->addWidget(titleLabel); if (!serverText.isEmpty()) { diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp index c1df0c963..511ff1c7d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp @@ -28,7 +28,6 @@ DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent) { - setObjectName("tipOfTheDayDialog"); successfulInit = false; QString xmlPath = "theme:tips/tips_of_the_day.xml"; tipDatabase = new TipsOfTheDay(xmlPath, this); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp index 11dcd7e27..ca55c7b41 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp @@ -42,7 +42,6 @@ static const QString UPDATE_INSTALLER_OVERRIDE = "Cockatrice-Update-Override.exe DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) { - setObjectName("updateDialog"); // Handle layout statusLabel = new QLabel(this); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_view_log.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_view_log.cpp index e75cf352a..d55e800a1 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_view_log.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_view_log.cpp @@ -18,7 +18,6 @@ class QWidget; DlgViewLog::DlgViewLog(QWidget *parent) : QDialog(parent) { - setObjectName("viewLogDialog"); logArea = new QPlainTextEdit; logArea->setReadOnly(true); diff --git a/cockatrice/src/interface/widgets/dialogs/settings_view.cpp b/cockatrice/src/interface/widgets/dialogs/settings_view.cpp index 005824995..d22a8d0b3 100644 --- a/cockatrice/src/interface/widgets/dialogs/settings_view.cpp +++ b/cockatrice/src/interface/widgets/dialogs/settings_view.cpp @@ -7,7 +7,6 @@ #include "../../../client/settings/cache_settings.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" #include "../settings_page/deck_editor_settings_page.h" @@ -120,8 +119,6 @@ QWidget *fromWidgetId(WidgetId id) SettingsView::SettingsView(QWidget *parent) : QWidget(parent), currentTabIndex(0), searchActive(false) { - setObjectName("settingsView"); - setupUi(); connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this, &SettingsView::retranslateUi); @@ -138,14 +135,12 @@ void SettingsView::setupUi() searchEdit->addAction(themePixmap(QStringLiteral("icons/search")), QLineEdit::LeadingPosition); searchEdit->installEventFilter(this); connect(searchEdit, &QLineEdit::textChanged, this, &SettingsView::onSearchTextChanged); - searchEdit->setObjectName("settingsSearchEdit"); auto *searchLayout = new QHBoxLayout; searchLayout->addWidget(searchEdit); // Pages stacked widget pagesWidget = new QStackedWidget; - pagesWidget->setObjectName("pagesWidget"); auto *generalPage = new GeneralSettingsPage; auto *appearancePage = new AppearanceSettingsPage; @@ -204,17 +199,12 @@ void SettingsView::setupUi() searchDelegate->setPageIcons(icons); searchResultsView->viewport()->update(); }); - // The search delegate reads the InterfaceDefault role at paint time, so a - // font change only needs a repaint, mirroring the theme change above. - connect(themeManager, &ThemeManager::fontsChanged, searchResultsView, - [this] { searchResultsView->viewport()->update(); }); // Build search index after pages are created buildSearchIndex(); // Pages container (stacked widget + search results overlay) pagesContainer = new QWidget; - pagesContainer->setObjectName("pagesContainer"); auto *containerLayout = new QStackedLayout; containerLayout->setStackingMode(QStackedLayout::StackAll); containerLayout->addWidget(pagesWidget); @@ -254,7 +244,6 @@ void SettingsView::setupUi() void SettingsView::setupNavTree() { navTree = new QTreeWidget; - navTree->setObjectName("navTree"); navTree->setHeaderHidden(true); navTree->setRootIsDecorated(true); navTree->setIconSize(QSize(24, 24)); diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp index 5cb62db0f..baf46bbba 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp @@ -1,11 +1,9 @@ #include "banner_widget.h" #include "../../../pixel_map_generator.h" -#include "../../../theme_manager.h" #include #include -#include #include #include #include @@ -30,9 +28,7 @@ BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation if (transparency > 50) { textColor = " color: white;"; } - bannerLabel->setStyleSheet(textColor); - applyInterfaceDefaultFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, &BannerWidget::applyInterfaceDefaultFont); + bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold;" + textColor); layout->addWidget(iconLabel); layout->addWidget(bannerLabel); @@ -60,16 +56,6 @@ void BannerWidget::setText(const QString &text) const bannerLabel->setText(text); } -void BannerWidget::applyInterfaceDefaultFont() -{ - // Family comes from the InterfaceDefault theme role; the 24px size and - // bold weight previously carried by the stylesheet stay caller-side. - QFont bannerFont = themeManager->appFont(AppFont::InterfaceDefault); - bannerFont.setPixelSize(24); - bannerFont.setWeight(QFont::Bold); - bannerLabel->setFont(bannerFont); -} - void BannerWidget::setClickable(bool _clickable) { clickable = _clickable; diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.h b/cockatrice/src/interface/widgets/general/display/banner_widget.h index d46250bdb..06556089b 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.h +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.h @@ -48,7 +48,6 @@ signals: private slots: void toggleBuddyVisibility() const; void updateDropdownIconState() const; - void applyInterfaceDefaultFont(); }; #endif // BANNER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp index 6bb1dcd62..484ac861d 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp @@ -1,7 +1,5 @@ #include "bar_chart_widget.h" -#include "../../interface/theme_manager.h" - #include #include #include @@ -21,10 +19,6 @@ BarChartWidget::BarChartWidget(QWidget *parent) : QWidget(parent) { setMouseTracking(true); - - // Labels read the InterfaceDefault role at paint time, so a font change - // only needs a repaint. - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); }); } void BarChartWidget::setBars(const QVector &newBars) @@ -152,8 +146,7 @@ void BarChartWidget::paintEvent(QPaintEvent *) // draw label below bar QRect labelRect(left + i * (barWidth + spacing), bottom, barWidth, LABEL_H); - // Family comes from the InterfaceDefault theme role; bold stays caller-side. - QFont f = themeManager->appFont(AppFont::InterfaceDefault); + QFont f = p.font(); f.setBold(true); p.setFont(f); p.setPen(Qt::black); diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp index 359d56772..5a3130964 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp @@ -1,7 +1,5 @@ #include "segmented_bar_widget.h" -#include "../../interface/theme_manager.h" - #include #include #include @@ -24,10 +22,6 @@ SegmentedBarWidget::SegmentedBarWidget(QString label, QVector segments, setMinimumWidth(36); setMaximumWidth(50); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - - // The label reads the InterfaceDefault role at paint time, so a font - // change only needs a repaint. - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); }); } QSize SegmentedBarWidget::sizeHint() const @@ -101,8 +95,7 @@ void SegmentedBarWidget::paintEvent(QPaintEvent *) // draw label QRect labelRect(0, h - LABEL_H, w, LABEL_H); - // Family comes from the InterfaceDefault theme role; bold stays caller-side. - QFont f = themeManager->appFont(AppFont::InterfaceDefault); + QFont f = p.font(); f.setBold(true); p.setFont(f); p.setPen(Qt::black); diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp index 598c70fe2..0609eb16a 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp @@ -1,7 +1,5 @@ #include "dynamic_font_size_label.h" -#include "../../interface/theme_manager.h" - #include #include #include @@ -30,13 +28,9 @@ void DynamicFontSizeLabel::paintEvent(QPaintEvent *event) // QElapsedTimer timer; // timer.start(); - // Family follows the InterfaceDefault theme role; the fitted point size - // stays computed from the widget geometry as before. - QFont newFont = themeManager->appFont(AppFont::InterfaceDefault); + QFont newFont = font(); float fontSize = getWidgetMaximumFontSize(this, this->text()); newFont.setPointSizeF(fontSize); - // Preserve the caller's bold emphasis, which the role font does not carry. - newFont.setBold(font().bold()); setFont(newFont); // qDebug() << "Font size set to" << fontSize; @@ -46,10 +40,7 @@ void DynamicFontSizeLabel::paintEvent(QPaintEvent *event) float DynamicFontSizeLabel::getWidgetMaximumFontSize(QWidget *widget, const QString &text) { - // Measure with the InterfaceDefault family so the fit matches the painted font. - QFont font = themeManager->appFont(AppFont::InterfaceDefault); - font.setBold(widget->font().bold()); - font.setPointSizeF(widget->font().pointSizeF()); + QFont font = widget->font(); const QRect widgetRect = widget->contentsRect(); const float widgetWidth = widgetRect.width(); const float widgetHeight = widgetRect.height(); diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp index 9b5f5bdda..25e8ca48f 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp @@ -1,6 +1,5 @@ #include "dynamic_font_size_push_button.h" -#include "../../interface/theme_manager.h" #include "dynamic_font_size_label.h" #include @@ -21,12 +20,10 @@ void DynamicFontSizePushButton::paintEvent(QPaintEvent *event) // Call the base class paintEvent to preserve any other painting behavior QPushButton::paintEvent(event); - // Adjust the font size dynamically based on the text. Family follows the - // InterfaceDefault theme role; the fitted size stays computed as before. - QFont newFont = themeManager->appFont(AppFont::InterfaceDefault); + // Adjust the font size dynamically based on the text + QFont newFont = font(); float fontSize = DynamicFontSizeLabel::getWidgetMaximumFontSize(this, this->text()); newFont.setPointSizeF(fontSize); - newFont.setBold(font().bold()); setFont(newFont); // Get painter for custom painting diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.cpp b/cockatrice/src/interface/widgets/general/home_styled_button.cpp index 9c31d3815..7f23827bc 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.cpp +++ b/cockatrice/src/interface/widgets/general/home_styled_button.cpp @@ -1,9 +1,6 @@ #include "home_styled_button.h" -#include "../../theme_manager.h" - #include -#include #include #include #include @@ -24,18 +21,6 @@ HomeStyledButton::HomeStyledButton(const QString &text, QPair _g setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setMinimumHeight(25); setStyleSheet(generateButtonStylesheet(gradientColors)); - applyInterfaceDefaultFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, &HomeStyledButton::applyInterfaceDefaultFont); -} - -void HomeStyledButton::applyInterfaceDefaultFont() -{ - // Family comes from the InterfaceDefault theme role; the 34px size and - // bold weight previously carried by the stylesheet stay caller-side. - QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); - buttonFont.setPixelSize(34); - buttonFont.setWeight(QFont::Bold); - setFont(buttonFont); } void HomeStyledButton::updateStylesheet(const QPair &colors) @@ -65,6 +50,9 @@ QString HomeStyledButton::generateButtonStylesheet(const QPair & return QString(R"( QPushButton { + font-size: 34px; + font-weight: bold; + font-family: sans-serif, "Segoe UI", "Helvetica Neue"; padding: 30px; color: white; border: 2px solid %1; diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.h b/cockatrice/src/interface/widgets/general/home_styled_button.h index 5756db5a1..55ac5550f 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.h +++ b/cockatrice/src/interface/widgets/general/home_styled_button.h @@ -24,9 +24,6 @@ public: public slots: void paintEvent(QPaintEvent *event) override; -private slots: - void applyInterfaceDefaultFont(); - private: QPair gradientColors; }; diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index e6b5bf255..5905b2c40 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -68,7 +68,6 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) : QWidget(parent), tabSupervisor(_tabSupervisor), background(themePixmap(QStringLiteral("backgrounds/home"))) { - setObjectName("homeWidget"); layout = new QGridLayout(this); backgroundSourceCard = new CardInfoPictureArtCropWidget(this); @@ -288,9 +287,9 @@ void HomeWidget::updateButtonsToBackgroundColor() QGroupBox *HomeWidget::createButtons() { QGroupBox *box = new QGroupBox(this); - box->setObjectName("homeButtonsBox"); box->setStyleSheet(R"( QGroupBox { + font-size: 20px; color: white; /* Title text color */ background: transparent; } @@ -301,14 +300,6 @@ QGroupBox *HomeWidget::createButtons() subcontrol-position: top center; /* or top left / right */ } )"); - // Family comes from the InterfaceDefault theme role; the 20px size stays caller-side. - auto applyBoxFont = [box]() { - QFont boxFont = themeManager->appFont(AppFont::InterfaceDefault); - boxFont.setPixelSize(20); - box->setFont(boxFont); - }; - applyBoxFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, applyBoxFont); box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QVBoxLayout *boxLayout = new QVBoxLayout; boxLayout->setAlignment(Qt::AlignHCenter); @@ -320,34 +311,27 @@ QGroupBox *HomeWidget::createButtons() boxLayout->addSpacing(25); connectButton = new HomeStyledButton(QString(), gradientColors); - connectButton->setObjectName("homeConnectButton"); boxLayout->addWidget(connectButton); visualDeckEditorButton = new HomeStyledButton(QString(), gradientColors); - visualDeckEditorButton->setObjectName("homeCreateNewDeckButton"); connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->openDeckInNewTab(LoadedDeck()); }); boxLayout->addWidget(visualDeckEditorButton); visualDeckStorageButton = new HomeStyledButton(QString(), gradientColors); - visualDeckStorageButton->setObjectName("homeBrowseDecksButton"); connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabVisualDeckStorage(true); }); boxLayout->addWidget(visualDeckStorageButton); visualDatabaseDisplayButton = new HomeStyledButton(QString(), gradientColors); - visualDatabaseDisplayButton->setObjectName("homeBrowseCardDatabaseButton"); connect(visualDatabaseDisplayButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addVisualDatabaseDisplayTab); boxLayout->addWidget(visualDatabaseDisplayButton); edhrecButton = new HomeStyledButton(QString(), gradientColors); - edhrecButton->setObjectName("homeBrowseEdhrecButton"); connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab); boxLayout->addWidget(edhrecButton); archidektButton = new HomeStyledButton(QString(), gradientColors); - archidektButton->setObjectName("homeBrowseArchidektButton"); connect(archidektButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addArchidektTab); boxLayout->addWidget(archidektButton); replayButton = new HomeStyledButton(QString(), gradientColors); - replayButton->setObjectName("homeViewReplaysButton"); connect(replayButton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); }); boxLayout->addWidget(replayButton); if (qobject_cast(tabSupervisor->parentWidget())) { @@ -479,9 +463,7 @@ void HomeWidget::paintEvent(QPaintEvent *event) constexpr int margin = 15; constexpr qreal attributionMargin = 4.0; - // Family comes from the InterfaceDefault theme role; the 14px sizes and - // bold weights stay caller-side as before. - QFont attributionFont = themeManager->appFont(AppFont::InterfaceDefault); + QFont attributionFont = painter.font(); attributionFont.setPointSize(14); attributionFont.setBold(true); painter.setFont(attributionFont); @@ -494,7 +476,7 @@ void HomeWidget::paintEvent(QPaintEvent *event) // Card name bubble above the attribution (when enabled). if (!cardName.isEmpty() && SettingsCache::instance().appearance().getHomeTabDisplayCardName()) { - QFont font = themeManager->appFont(AppFont::InterfaceDefault); + QFont font = painter.font(); font.setPointSize(14); font.setBold(true); painter.setFont(font); diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp index e69687c54..c4298d6b6 100644 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp +++ b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp @@ -1,6 +1,5 @@ #include "first_run_wizard.h" -#include "../interface/theme_manager.h" #include "first_run_wizard_page.h" #include "pages/account_setup_page.h" #include "pages/card_database_setup_page.h" @@ -33,8 +32,9 @@ FirstRunWizard::FirstRunWizard(QWidget *parent) : QDialog(parent) bannerHost = new BannerHost(this); titleLabel = new QLabel(this); - applyInterfaceDefaultFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, &FirstRunWizard::applyInterfaceDefaultFont); + QFont titleFont = QtUtils::boldFont(titleLabel->font()); + titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4); + titleLabel->setFont(titleFont); subtitleLabel = new QLabel(this); subtitleLabel->setWordWrap(true); @@ -154,16 +154,6 @@ void FirstRunWizard::updateChrome() } } -void FirstRunWizard::applyInterfaceDefaultFont() -{ - // Family comes from the InterfaceDefault theme role; the *1.4 size - // multiplier and bold weight stay caller-side as before. - QFont titleFont = themeManager->appFont(AppFont::InterfaceDefault); - titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4); - titleFont.setBold(true); - titleLabel->setFont(titleFont); -} - void FirstRunWizard::goNext() { FirstRunWizardPage *page = pages[currentIndex]; diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h index a5fa3b4bd..812985a9f 100644 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h +++ b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h @@ -55,7 +55,6 @@ private slots: void goBack(); void skip(); void updateChrome(); - void applyInterfaceDefaultFont(); private: void addPage(FirstRunWizardPage *page); diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp index 13a43bd47..bad88ddbb 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -1,6 +1,5 @@ #include "all_zones_card_amount_widget.h" -#include "../../theme_manager.h" #include "../general/display/shadow_background_label.h" #include "card_amount_widget.h" #include "libcockatrice/card/card_info.h" @@ -68,7 +67,6 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, buttonBoxSideboard->setVisible(!isToken); connect(cardSizeSlider, &QSlider::valueChanged, this, &AllZonesCardAmountWidget::adjustFontSize); - connect(themeManager, &ThemeManager::fontsChanged, this, &AllZonesCardAmountWidget::applyCardTextFont); QTimer::singleShot(10, this, [this]() { adjustFontSize(this->cardSizeSlider->value()); }); @@ -92,9 +90,8 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); - // Update the font labels. The family comes from the CardText theme - // role; the slider-driven point size stays as before. - QFont zoneLabelFont = themeManager->appFont(AppFont::CardText); + // Update the font labels + QFont zoneLabelFont = zoneLabelMainboard->font(); zoneLabelFont.setPointSize(newFontSize); zoneLabelMainboard->setFont(zoneLabelFont); zoneLabelSideboard->setFont(zoneLabelFont); @@ -104,11 +101,6 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) repaint(); } -void AllZonesCardAmountWidget::applyCardTextFont() -{ - adjustFontSize(cardSizeSlider->value()); -} - void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount) { buttonBoxMainboard->setAmount(mainboardAmount); diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index 3213cf16b..cefabe293 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -36,9 +36,6 @@ public slots: void adjustFontSize(int scalePercentage); void setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount); -private slots: - void applyCardTextFont(); - private: QVBoxLayout *layout; QSlider *cardSizeSlider; diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 7f25ea1f4..eae49d7ef 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -1,6 +1,5 @@ #include "card_amount_widget.h" -#include "../../theme_manager.h" #include "../deck_editor/deck_state_manager.h" #include "../general/display/dynamic_font_size_push_button.h" #include "deck_list_model.h" @@ -78,7 +77,6 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, // Connect slider for dynamic font size adjustment connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); - connect(themeManager, &ThemeManager::fontsChanged, this, &CardAmountWidget::applyCardTextFont); } int CardAmountWidget::getAmount() @@ -140,9 +138,8 @@ void CardAmountWidget::adjustFontSize(int scalePercentage) int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); - // Update the font for card count label. The family comes from the - // CardText theme role; the slider-driven point size stays as before. - QFont cardCountFont = themeManager->appFont(AppFont::CardText); + // Update the font for card count label + QFont cardCountFont = cardCountInZone->font(); cardCountFont.setPointSize(newFontSize); cardCountInZone->setFont(cardCountFont); @@ -153,11 +150,6 @@ void CardAmountWidget::adjustFontSize(int scalePercentage) repaint(); } -void CardAmountWidget::applyCardTextFont() -{ - adjustFontSize(cardSizeSlider->value()); -} - /** * @brief Updates the card count display in the widget. */ diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index cef872157..6f5889fc7 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -67,7 +67,6 @@ private slots: void removePrintingSideboard(); void removePrintingTokensboard(); void adjustFontSize(int scalePercentage); - void applyCardTextFont(); }; #endif // CARD_AMOUNT_WIDGET_H diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp index 225c3e21d..7f9ad66c8 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp @@ -1,8 +1,5 @@ #include "printing_selector_placeholder_widget.h" -#include "../interface/theme_manager.h" - -#include #include #include #include @@ -33,12 +30,10 @@ PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *pa textLabel->setStyleSheet(R"( QLabel { color: palette(mid); + font-size: 14px; padding: 10px; } )"); - applyInterfaceDefaultFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, - &PrintingSelectorPlaceholderWidget::applyInterfaceDefaultFont); mainLayout->addWidget(imageLabel); mainLayout->addWidget(textLabel); @@ -49,12 +44,4 @@ PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *pa void PrintingSelectorPlaceholderWidget::retranslateUi() { textLabel->setText(tr("Select a card to view its available printings")); -} - -void PrintingSelectorPlaceholderWidget::applyInterfaceDefaultFont() -{ - // Family comes from the InterfaceDefault theme role; the 14px size stays caller-side. - QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault); - placeholderFont.setPixelSize(14); - textLabel->setFont(placeholderFont); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h index 9e6e47bcb..6702d325a 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h @@ -14,9 +14,6 @@ class PrintingSelectorPlaceholderWidget : public QWidget public: explicit PrintingSelectorPlaceholderWidget(QWidget *parent = nullptr); -private slots: - void applyInterfaceDefaultFont(); - private: QVBoxLayout *mainLayout; QLabel *imageLabel; diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.cpp b/cockatrice/src/interface/widgets/replay/replay_manager.cpp index b01d7f9d6..8ba0cb653 100644 --- a/cockatrice/src/interface/widgets/replay/replay_manager.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_manager.cpp @@ -57,7 +57,7 @@ ReplayManager::ReplayManager(QObject *parent, GameReplay *replay) rewindBufferingTimer = new QTimer(this); rewindBufferingTimer->setSingleShot(true); - connect(rewindBufferingTimer, &QTimer::timeout, this, [this] { processRewind(); }); + connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayManager::processRewind); } ReplayManager::~ReplayManager() @@ -65,7 +65,7 @@ ReplayManager::~ReplayManager() delete replay; } -void ReplayManager::skipToTime(int newTime, bool doRewindBuffering, bool includeEventsAtTime) +void ReplayManager::skipToTime(int newTime, bool doRewindBuffering) { // check boundary conditions if (newTime < 0) { @@ -75,17 +75,15 @@ void ReplayManager::skipToTime(int newTime, bool doRewindBuffering, bool include newTime = maxTime; } - if (!includeEventsAtTime) { - newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval - } + newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval const bool isBackwardsSkip = newTime < currentProcessedTime; currentVisualTime = newTime; if (isBackwardsSkip) { - handleBackwardsSkip(doRewindBuffering, includeEventsAtTime); + handleBackwardsSkip(doRewindBuffering); } else { - processNewEvents(FORWARD_SKIP, includeEventsAtTime); + processNewEvents(FORWARD_SKIP); } timeChanged(currentVisualTime); @@ -97,7 +95,7 @@ void ReplayManager::skipToTime(int newTime, bool doRewindBuffering, bool include * @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind * is processed at the end. When false, the backwards skip will always cause an immediate rewind. */ -void ReplayManager::handleBackwardsSkip(bool doRewindBuffering, bool includeEventsAtTime) +void ReplayManager::handleBackwardsSkip(bool doRewindBuffering) { if (doRewindBuffering) { // We use a one-shot timer to implement the rewind buffering. @@ -107,11 +105,11 @@ void ReplayManager::handleBackwardsSkip(bool doRewindBuffering, bool includeEven rewindBufferingTimer->start(SettingsCache::instance().userInterface().getRewindBufferingMs()); } else { // otherwise, process the rewind immediately - processRewind(includeEventsAtTime); + processRewind(); } } -void ReplayManager::processRewind(bool includeEventsAtTime) +void ReplayManager::processRewind() { // stop any queued-up rewinds rewindBufferingTimer->stop(); @@ -119,7 +117,7 @@ void ReplayManager::processRewind(bool includeEventsAtTime) // process the rewind currentEvent = 0; emit rewound(); - processNewEvents(BACKWARD_SKIP, includeEventsAtTime); + processNewEvents(BACKWARD_SKIP); } void ReplayManager::replayTimerTimeout() @@ -136,13 +134,11 @@ void ReplayManager::replayTimerTimeout() } /** @brief Processes all unprocessed events up to the current time. */ -void ReplayManager::processNewEvents(PlaybackMode playbackMode, bool includeEventsAtTime) +void ReplayManager::processNewEvents(PlaybackMode playbackMode) { currentProcessedTime = currentVisualTime; - while (currentEvent < replayTimeline.size() && - (replayTimeline[currentEvent] < currentProcessedTime || - (includeEventsAtTime && replayTimeline[currentEvent] == currentProcessedTime))) { + while (currentEvent < replayTimeline.size() && replayTimeline[currentEvent] < currentProcessedTime) { EventProcessingOptions options; // backwards skip => always skip reveal windows @@ -240,24 +236,6 @@ void ReplayManager::setTime(int time) skipToTime(time, false); } -void ReplayManager::setTimeToEvent(int eventIndex) -{ - if (eventIndex < 0 || eventIndex >= replayTimeline.size()) { - return; - } - - // Flush a pending buffered rewind before seeking, otherwise its timer fires after this seek and rebuilds the - // replay state from an outdated currentVisualTime, dropping the event we seek to. Stopping the timer instead - // of processing it is not enough: playback ticks advance currentProcessedTime without rewinding currentEvent, - // so the board could still be ahead of the target. - if (rewindBufferingTimer->isActive()) { - processRewind(); - } - - // Unlike a timeline seek, jumping to an event should apply that event immediately. - skipToTime(replayTimeline.at(eventIndex), false, true); -} - void ReplayManager::skipByAmount(int amount) { skipToTime(currentVisualTime + amount, amount < 0); diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.h b/cockatrice/src/interface/widgets/replay/replay_manager.h index 34bae5f9a..3849b86ae 100644 --- a/cockatrice/src/interface/widgets/replay/replay_manager.h +++ b/cockatrice/src/interface/widgets/replay/replay_manager.h @@ -40,10 +40,10 @@ class ReplayManager : public QObject ///< to rewind buffering int currentEvent = 0; ///< current event's index - void skipToTime(int newTime, bool doRewindBuffering, bool includeEventsAtTime = false); - void handleBackwardsSkip(bool doRewindBuffering, bool includeEventsAtTime = false); - void processRewind(bool includeEventsAtTime = false); - void processNewEvents(PlaybackMode playbackMode, bool includeEventsAtTime = false); + void skipToTime(int newTime, bool doRewindBuffering); + void handleBackwardsSkip(bool doRewindBuffering); + void processRewind(); + void processNewEvents(PlaybackMode playbackMode); void handleSkipEmptySection(); private slots: @@ -73,7 +73,6 @@ public slots: void startReplay(); void stopReplay(); void setTime(int time); - void setTimeToEvent(int eventIndex); void skipByAmount(int amount); // use a negative amount to skip backwards signals: diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_widget.cpp index 3f0bf35d9..07b4e72ce 100644 --- a/cockatrice/src/interface/widgets/replay/replay_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_widget.cpp @@ -28,7 +28,6 @@ ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay) connect(replayManager, &ReplayManager::eventReplayed, this, &ReplayWidget::eventReplayed); connect(replayManager, &ReplayManager::replayFinished, this, &ReplayWidget::replayFinished); connect(replayManager, &ReplayManager::rewound, this, &ReplayWidget::rewound); - connect(replayManager, &ReplayManager::timeChanged, this, &ReplayWidget::timeChanged); // timeline widget timelineWidget = new ReplayTimelineWidget; @@ -97,16 +96,6 @@ ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay) refreshShortcuts(); } -const QList &ReplayWidget::getReplayTimeline() const -{ - return replayManager->getReplayTimeline(); -} - -void ReplayWidget::seekToEvent(int eventIndex) -{ - replayManager->setTimeToEvent(eventIndex); -} - void ReplayWidget::replayFinished() { replayPlayButton->setChecked(false); diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.h b/cockatrice/src/interface/widgets/replay/replay_widget.h index 5d23b358d..0e78442ce 100644 --- a/cockatrice/src/interface/widgets/replay/replay_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_widget.h @@ -8,7 +8,6 @@ #ifndef REPLAY_WIDGET_H #define REPLAY_WIDGET_H -#include #include #include #include @@ -36,14 +35,8 @@ public: */ ReplayWidget(QWidget *parent, GameReplay *replay); - [[nodiscard]] const QList &getReplayTimeline() const; - -public slots: - void seekToEvent(int eventIndex); - signals: void rewound(); - void timeChanged(int time); void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options); private: diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index c57f65352..037a4a1e5 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -4,7 +4,6 @@ #include "../../client/sound_engine.h" #include "../../interface/card_localization.h" #include "../../interface/pixel_map_generator.h" -#include "../../interface/theme_manager.h" #include "../../tabs/tab_supervisor.h" #include "../user/user_context_menu.h" #include "../user/user_list_manager.h" @@ -64,9 +63,6 @@ ChatView::ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _sho connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &ChatView::adjustColorsToPalette); - applyChatFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, &ChatView::applyChatFont); - userContextMenu = new UserContextMenu(tabSupervisor, this, game); connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); @@ -112,16 +108,6 @@ void ChatView::adjustColorsToPalette() QTimer::singleShot(0, this, &ChatView::refreshBlockColors); } -void ChatView::applyChatFont() -{ - // Family comes from the ChatLog theme role; the message char formats carry - // no family of their own, so they (and new content) follow the widget and - // document default fonts. Weights/italics stay caller-side as before. - const QFont chatFont = themeManager->appFont(AppFont::ChatLog); - setFont(chatFont); - document()->setDefaultFont(chatFont); -} - void ChatView::refreshBlockColors() { QTextDocument *doc = document(); diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h index a2e2f33dd..3fa7224e5 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h @@ -111,7 +111,6 @@ private slots: void openLink(const QUrl &link); void actMessageClicked(); void adjustColorsToPalette(); - void applyChatFont(); void refreshBlockColors(); void onScrollBarRangeChanged(); void onScrollBarValueChanged(int value); diff --git a/cockatrice/src/interface/widgets/server/user/user_games_model.cpp b/cockatrice/src/interface/widgets/server/user/user_games_model.cpp deleted file mode 100644 index b7c728c45..000000000 --- a/cockatrice/src/interface/widgets/server/user/user_games_model.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "user_games_model.h" - -#include -#include -#include -#include -#include -#include -#include - -UserGamesModel::UserGamesModel(AbstractClient *_client, int requestTimeoutMs, QObject *parent) - : QAbstractListModel(parent), client(_client) -{ - timeout.setSingleShot(true); - timeout.setInterval(requestTimeoutMs); - connect(&timeout, &QTimer::timeout, this, [this] { - invalidateRequest(); - setState(State::Error); - }); - connected = client->getStatus() == StatusLoggedIn; - connect(client, &AbstractClient::statusChanged, this, [this](ClientStatus status) { - // AbstractClient::setStatus emits this signal while holding the (non-recursive) - // status mutex, which getStatus() would try to lock again. Track the emitted - // value here, and read this cache from canRefresh() for the same reason. - connected = status == StatusLoggedIn; - if (!connected) { - invalidateRequest(); - replaceGames({}); - setState(online ? State::Disconnected : State::Offline); - } else if (online) { - refresh(); - } - }); - connect(client, &QObject::destroyed, this, [this] { - client = nullptr; - connected = false; - invalidateRequest(); - replaceGames({}); - setState(online ? State::Disconnected : State::Offline); - }); -} - -int UserGamesModel::rowCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : games.size(); -} - -QVariant UserGamesModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.model() != this || index.column() != 0 || index.row() < 0 || - index.row() >= games.size() || role != GameRole) { - return {}; - } - return QVariant::fromValue(games.at(index.row())); -} - -void UserGamesModel::setUser(const QString &name, bool isOnline) -{ - userName = name; - online = isOnline; - refresh(); -} - -UserGamesModel::State UserGamesModel::getState() const -{ - return state; -} - -bool UserGamesModel::canRefresh() const -{ - return online && !userName.isEmpty() && client && connected && state != State::Loading; -} - -void UserGamesModel::invalidateRequest() -{ - ++requestGeneration; - // The client owns pending commands. Detach our interest without deleting - // an object that may already have moved to the client's thread. - disconnect(responseConnection); - responseConnection = {}; - timeout.stop(); -} - -void UserGamesModel::replaceGames(QList replacement) -{ - beginResetModel(); - games = std::move(replacement); - endResetModel(); -} - -void UserGamesModel::setState(State newState) -{ - state = newState; - emit stateChanged(); -} - -void UserGamesModel::refresh() -{ - invalidateRequest(); - const quint64 request = requestGeneration; - replaceGames({}); - if (!online || userName.isEmpty()) { - setState(State::Offline); - return; - } - if (!client || !connected) { - setState(State::Disconnected); - return; - } - - setState(State::Loading); - // State listeners may select another user or disconnect the client. - if (request != requestGeneration) { - return; - } - - Command_GetGamesOfUser command; - command.set_user_name(userName.toStdString()); - PendingCommand *pending = client->prepareSessionCommand(command); - responseConnection = connect(pending, &PendingCommand::finished, this, - [this, request](const Response &response) { finishRequest(response, request); }); - timeout.start(); - client->sendCommand(pending); -} - -void UserGamesModel::finishRequest(const Response &response, quint64 request) -{ - if (request != requestGeneration) { - return; - } - invalidateRequest(); - const quint64 completion = requestGeneration; - if (response.response_code() != Response::RespOk || !response.HasExtension(Response_GetGamesOfUser::ext)) { - setState(State::Error); - return; - } - - const auto &result = response.GetExtension(Response_GetGamesOfUser::ext); - QList replacement; - replacement.reserve(result.game_list_size()); - for (const auto &game : result.game_list()) { - replacement.append(game); - } - replaceGames(std::move(replacement)); - if (completion != requestGeneration) { - return; - } - setState(games.isEmpty() ? State::Empty : State::Ready); -} diff --git a/cockatrice/src/interface/widgets/server/user/user_games_model.h b/cockatrice/src/interface/widgets/server/user/user_games_model.h deleted file mode 100644 index b8ad71a67..000000000 --- a/cockatrice/src/interface/widgets/server/user/user_games_model.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef COCKATRICE_USER_GAMES_MODEL_H -#define COCKATRICE_USER_GAMES_MODEL_H - -#include -#include -#include -#include -#include -#include - -class AbstractClient; -class Response; - -/** Current user's games and the lifetime of the request that supplies them. */ -class UserGamesModel : public QAbstractListModel -{ - Q_OBJECT - -public: - enum class State - { - Offline, - Disconnected, - Loading, - Ready, - Empty, - Error - }; - static constexpr int DefaultRequestTimeoutMs = 15000; - static constexpr int GameRole = Qt::UserRole + 10; - - explicit UserGamesModel(AbstractClient *client, - int requestTimeoutMs = DefaultRequestTimeoutMs, - QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = {}) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - void setUser(const QString &name, bool isOnline); - void refresh(); - State getState() const; - bool canRefresh() const; - -signals: - void stateChanged(); - -private: - void invalidateRequest(); - void replaceGames(QList replacement); - void finishRequest(const Response &response, quint64 request); - void setState(State newState); - - QPointer client; - QString userName; - bool online = false; - bool connected = false; - State state = State::Offline; - QList games; - quint64 requestGeneration = 0; - QMetaObject::Connection responseConnection; - QTimer timeout; -}; - -#endif // COCKATRICE_USER_GAMES_MODEL_H diff --git a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp index 997001c50..fa7698037 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp @@ -1,7 +1,6 @@ #include "user_info_box.h" #include "../../interface/pixel_map_generator.h" -#include "../../interface/theme_manager.h" #include "../../interface/widgets/dialogs/dlg_edit_avatar.h" #include "../../interface/widgets/dialogs/dlg_edit_password.h" #include "../../interface/widgets/dialogs/dlg_edit_user.h" @@ -40,8 +39,10 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), client(_client), editable(_editable) { - applyInterfaceDefaultFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, &UserInfoBox::applyInterfaceDefaultFont); + QFont nameFont = nameLabel.font(); + nameFont.setBold(true); + nameFont.setPointSizeF(nameFont.pointSizeF() * 1.5); + nameLabel.setFont(nameFont); auto *userIconAndNameLayout = new QHBoxLayout; userIconAndNameLayout->addWidget(&userLevelIcon, 0, Qt::AlignCenter); @@ -90,16 +91,6 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *paren retranslateUi(); } -void UserInfoBox::applyInterfaceDefaultFont() -{ - // Family comes from the InterfaceDefault theme role; the *1.5 size - // multiplier and bold weight stay caller-side as before. - QFont nameFont = themeManager->appFont(AppFont::InterfaceDefault); - nameFont.setBold(true); - nameFont.setPointSizeF(nameFont.pointSizeF() * 1.5); - nameLabel.setFont(nameFont); -} - void UserInfoBox::retranslateUi() { realNameLabel1.setText(tr("Real Name:")); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_box.h b/cockatrice/src/interface/widgets/server/user/user_info_box.h index 98919b027..6bc571a37 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_box.h +++ b/cockatrice/src/interface/widgets/server/user/user_info_box.h @@ -48,7 +48,6 @@ private slots: void processEditResponse(const Response &r); void processPasswordResponse(const Response &r); void processAvatarResponse(const Response &r); - void applyInterfaceDefaultFont(); void actEdit(); void actEditInternal(const Response &r); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp index 4b4887bc4..efb3b2e31 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp @@ -6,9 +6,10 @@ #include "../../interface/widgets/tabs/tab_supervisor.h" #include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/card/printing/exact_card.h" +#include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/serverinfo_game.pb.h" #include "libcockatrice/protocol/pb/serverinfo_user.pb.h" -#include "user_games_model.h" +#include "libcockatrice/protocol/pb/session_commands.pb.h" #include "user_level.h" #include "user_list_painter.h" @@ -33,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include #include #include @@ -50,6 +52,8 @@ #include #include #include +#include +#include #include #include #include @@ -138,8 +142,7 @@ public: p->drawEllipse(QRectF(rect.left() + 9, rect.top() + (rect.height() - 8) / 2.0, 8, 8)); // Game title (bold, elided) - // Family comes from the InterfaceDefault theme role; bold stays caller-side. - QFont tf = themeManager->appFont(AppFont::InterfaceDefault); + QFont tf = option.font; tf.setBold(true); p->setFont(tf); p->setPen(pal.color(QPalette::Text)); @@ -151,7 +154,7 @@ public: // Player count const bool full = game.player_count() >= game.max_players(); - p->setFont(themeManager->appFont(AppFont::InterfaceDefault)); + p->setFont(option.font); p->setPen(full ? QColor(249, 115, 22) : pal.color(QPalette::Disabled, QPalette::Text)); p->drawText(QRect(rect.right() - countW - 4, rect.top(), countW, rect.height()), Qt::AlignVCenter | Qt::AlignRight, @@ -320,8 +323,7 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) const int tx = ax + AvatarSize + AvatarToTextGap; const int tw = rect.width() - tx - 8; - // Family comes from the InterfaceDefault theme role; the *1.12 size and bold stay caller-side. - QFont nf = themeManager->appFont(AppFont::InterfaceDefault); + QFont nf = font(); nf.setBold(true); nf.setPointSizeF(nf.pointSizeF() * 1.12); p.setFont(nf); @@ -350,7 +352,7 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) } if (!badge.text.isEmpty()) { - QFont bf = themeManager->appFont(AppFont::InterfaceDefault); + QFont bf = font(); bf.setPointSizeF(bf.pointSizeF() * 0.70); bf.setBold(true); p.setFont(bf); @@ -367,8 +369,7 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) // The painter font at this point depends on whether a badge was drawn // (badge font vs username font), so pin an explicit font for the pill. - // Family follows the InterfaceDefault theme role like the rest of the popup chrome. - p.setFont(themeManager->appFont(AppFont::InterfaceDefault)); + p.setFont(font()); // Only show the attribution when there is actually art on screen: on a // cache miss cardArt is null and the pill would float over the plain @@ -397,7 +398,6 @@ UserInfoPopup::UserInfoPopup(TabSupervisor *_ts, // Restyle the popup chrome when the theme or its color scheme changes. if (themeManager) { connect(themeManager, &ThemeManager::themeChanged, this, &UserInfoPopup::applyTheme); - connect(themeManager, &ThemeManager::fontsChanged, this, &UserInfoPopup::applyTheme); } } @@ -427,7 +427,6 @@ void UserInfoPopup::buildUi() refreshBtn = new QPushButton(QStringLiteral("↻"), this); refreshBtn->setFixedSize(20, 20); refreshBtn->setFlat(true); - refreshBtn->setToolTip(tr("Refresh games")); connect(refreshBtn, &QPushButton::clicked, this, &UserInfoPopup::refreshGames); gh->addWidget(refreshBtn); root->addLayout(gh); @@ -438,8 +437,7 @@ void UserInfoPopup::buildUi() root->addWidget(gamesStatus); // Games list - gamesModel = new UserGamesModel(client, UserGamesModel::DefaultRequestTimeoutMs, this); - connect(gamesModel, &UserGamesModel::stateChanged, this, &UserInfoPopup::updateGamesDisplay); + gamesModel = new QStandardItemModel(this); gamesView = new QListView(this); gamesView->setModel(gamesModel); gamesView->setItemDelegate(new PopupGameDelegate(gamesView)); @@ -477,24 +475,15 @@ void UserInfoPopup::applyTheme() separator->setStyleSheet(QStringLiteral("color:%1; margin: 0 8px;").arg(colorStr(theme.border))); - gamesLabel->setStyleSheet(QStringLiteral("color:%1; background:transparent;").arg(colorStr(theme.subText))); - QFont gamesLabelFont = themeManager->appFont(AppFont::InterfaceDefault); - gamesLabelFont.setPixelSize(11); - gamesLabelFont.setWeight(QFont::Bold); - gamesLabel->setFont(gamesLabelFont); + gamesLabel->setStyleSheet(QStringLiteral("color:%1; font-size:11px; font-weight:bold; background:transparent;") + .arg(colorStr(theme.subText))); - refreshBtn->setStyleSheet(QStringLiteral("QPushButton{color:%1;border:none;background:transparent;}" + refreshBtn->setStyleSheet(QStringLiteral("QPushButton{color:%1;border:none;font-size:14px;background:transparent;}" "QPushButton:hover{color:%2;}") .arg(colorStr(theme.subText), colorStr(theme.text))); - QFont refreshFont = themeManager->appFont(AppFont::InterfaceDefault); - refreshFont.setPixelSize(14); - refreshBtn->setFont(refreshFont); - gamesStatus->setStyleSheet( - QStringLiteral("color:%1; padding:10px; background:transparent;").arg(colorStr(theme.statusText))); - QFont statusFont = themeManager->appFont(AppFont::InterfaceDefault); - statusFont.setPixelSize(11); - gamesStatus->setFont(statusFont); + gamesStatus->setStyleSheet(QStringLiteral("color:%1; font-size:11px; padding:10px; background:transparent;") + .arg(colorStr(theme.statusText))); gamesView->setStyleSheet(QStringLiteral("QListView{background:%1;border:none;}" "QListView::item:selected{background:%2;}") @@ -502,12 +491,9 @@ void UserInfoPopup::applyTheme() closeBtn->setStyleSheet( QStringLiteral("QPushButton{background:%1;color:%2;" - "border:none;border-radius:11px;}" + "border:none;border-radius:11px;font-size:10px;}" "QPushButton:hover{color:%3;background:%4;}") .arg(colorStr(theme.closeBg), colorStr(theme.subText), colorStr(theme.text), colorStr(theme.closeHover))); - QFont closeFont = themeManager->appFont(AppFont::InterfaceDefault); - closeFont.setPixelSize(10); - closeBtn->setFont(closeFont); header->update(); } @@ -522,17 +508,13 @@ static QPushButton *makeBtn(const QString &label, const QString &tip, QWidget *p b->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); b->setStyleSheet(QStringLiteral("QPushButton{" " background:%1;color:%2;border:1px solid %3;" - " border-radius:4px;padding:0 4px;" + " border-radius:4px;font-size:11px;padding:0 4px;" "}" "QPushButton:hover{background:%4;color:%5;}" "QPushButton:pressed{background:%6;}" "QPushButton:disabled{color:%7;border-color:%3;}") .arg(colorStr(t.buttonBg), colorStr(t.text), colorStr(t.buttonBorder), colorStr(t.buttonHover), colorStr(t.text), colorStr(t.buttonPressed), colorStr(t.buttonDisabled))); - // Family comes from the InterfaceDefault theme role; the 11px size stays caller-side. - QFont btnFont = themeManager->appFont(AppFont::InterfaceDefault); - btnFont.setPixelSize(11); - b->setFont(btnFont); return b; } @@ -663,21 +645,13 @@ void UserInfoPopup::rebuildActionButtons(const ServerInfo_User &userInfo, bool o void UserInfoPopup::updateActionButtons(const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored) { - currentUserInfo = userInfo; - currentBuddy = isBuddy; - currentIgnored = isIgnored; - if (currentOnline != online) { - setOnline(online); - } else { - refreshHeader(); - rebuildActionButtons(userInfo, online, isBuddy, isIgnored); - adjustSize(); - } + rebuildActionButtons(userInfo, online, isBuddy, isIgnored); + adjustSize(); } void UserInfoPopup::onGamesContextMenu(const QPoint &pos) { - const QPersistentModelIndex idx = gamesView->indexAt(pos); + const QModelIndex idx = gamesView->indexAt(pos); if (!idx.isValid()) { return; } @@ -705,7 +679,7 @@ void UserInfoPopup::onGamesContextMenu(const QPoint &pos) } const QAction *chosen = menu.exec(gamesView->viewport()->mapToGlobal(pos)); - if (!chosen || !idx.isValid()) { + if (!chosen) { return; } @@ -742,8 +716,6 @@ void UserInfoPopup::showForUser(const QString &userName, currentUser = userName; currentUserInfo = userInfo; currentOnline = online; - currentBuddy = isBuddy; - currentIgnored = isIgnored; // Header refreshHeader(); @@ -751,71 +723,88 @@ void UserInfoPopup::showForUser(const QString &userName, // Actions rebuildActionButtons(userInfo, online, isBuddy, isIgnored); + // Games list reset + gamesModel->clear(); + gamesView->hide(); + gamesStatus->setText(tr("Loading games…")); + gamesStatus->show(); + // Close button, top right corner, above everything closeBtn->move(PopupWidth - closeBtn->width() - 6, 6); closeBtn->raise(); - gamesModel->setUser(currentUser, currentOnline); adjustSize(); + fetchGames(); } // ── Games fetch ─────────────────────────────────────────────────────────────── void UserInfoPopup::fetchGames() { - gamesModel->refresh(); -} - -void UserInfoPopup::setOnline(bool online) -{ - if (currentOnline == online) { + if (!client || currentUser.isEmpty()) { return; } - currentOnline = online; - refreshHeader(); - rebuildActionButtons(currentUserInfo, online, currentBuddy, currentIgnored); - gamesModel->setUser(currentUser, online); + + Command_GetGamesOfUser cmd; + cmd.set_user_name(currentUser.toStdString()); + + const QString snapshot = currentUser; + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, &PendingCommand::finished, this, + [this, snapshot](const Response &r) { onGamesReceived(r, snapshot); }); + client->sendCommand(pend); } -void UserInfoPopup::updateGamesDisplay() +void UserInfoPopup::onGamesReceived(const Response &r, const QString &forUser) { - refreshBtn->setEnabled(gamesModel->canRefresh()); - const auto state = gamesModel->getState(); - const bool ready = state == UserGamesModel::State::Ready; - gamesView->setVisible(ready); - gamesStatus->setVisible(!ready); - - switch (state) { - case UserGamesModel::State::Offline: - gamesStatus->setText(tr("User is offline.")); - break; - case UserGamesModel::State::Disconnected: - gamesStatus->setText(tr("Not connected to the server.")); - break; - case UserGamesModel::State::Loading: - gamesStatus->setText(tr("Loading games…")); - break; - case UserGamesModel::State::Empty: - gamesStatus->setText(tr("No active games.")); - break; - case UserGamesModel::State::Error: - gamesStatus->setText(tr("Could not load games.")); - break; - case UserGamesModel::State::Ready: { - // Fit exactly to the number of visible rows, scroll when more than 5. - constexpr int rowH = 38; // must match PopupGameDelegate::sizeHint - constexpr int maxRows = 5; - const int count = gamesModel->rowCount(); - gamesView->setFixedHeight(qMin(count, maxRows) * rowH + 2); - gamesView->setVerticalScrollBarPolicy(count > maxRows ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); - break; - } + if (forUser != currentUser) { + return; // stale response, different user showing now } + + gamesModel->clear(); + + if (r.response_code() != Response::RespOk) { + gamesStatus->setText(tr("Could not load games.")); + gamesStatus->show(); + gamesView->hide(); + return; + } + + const auto &resp = r.GetExtension(Response_GetGamesOfUser::ext); + if (resp.game_list_size() == 0) { + gamesStatus->setText(tr("No active games.")); + gamesStatus->show(); + gamesView->hide(); + return; + } + + for (int i = 0; i < resp.game_list_size(); ++i) { + auto *item = new QStandardItem; + item->setData(QVariant::fromValue(resp.game_list(i)), PopupRoles::GameData); + item->setEditable(false); + gamesModel->appendRow(item); + } + + gamesStatus->hide(); + gamesView->show(); + + // Fit exactly to the number of visible rows, scroll when more than 5 + constexpr int rowH = 38; // must match PopupGameDelegate::sizeHint + constexpr int maxRows = 5; + const int count = gamesModel->rowCount(); + const int visible = qMin(count, maxRows); + gamesView->setFixedHeight(visible * rowH + 2); + gamesView->setVerticalScrollBarPolicy(count > maxRows ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); + adjustSize(); } void UserInfoPopup::refreshGames() { + gamesModel->clear(); + gamesView->hide(); + gamesStatus->setText(tr("Loading games…")); + gamesStatus->show(); fetchGames(); } diff --git a/cockatrice/src/interface/widgets/server/user/user_info_popup.h b/cockatrice/src/interface/widgets/server/user/user_info_popup.h index 1af4ad0e1..f2a91d025 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_popup.h +++ b/cockatrice/src/interface/widgets/server/user/user_info_popup.h @@ -1,7 +1,6 @@ #ifndef COCKATRICE_USER_INFO_POPUP_H #define COCKATRICE_USER_INFO_POPUP_H -#include "user_games_model.h" #include "user_list_painter.h" #include @@ -22,13 +21,15 @@ class QPushButton; class TabSupervisor; class QListView; class QPoint; +class QStandardItemModel; +class Response; template class QMap; // ── Roles ───────────────────────────────────────────────────────────────────── namespace PopupRoles { -constexpr int GameData = UserGamesModel::GameRole; +constexpr int GameData = Qt::UserRole + 10; } // Popup theme @@ -142,7 +143,6 @@ public: void showForUser(const QString &userName, const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored); void fetchGames(); - void setOnline(bool online); [[nodiscard]] QString getCurrentUser() const { @@ -199,7 +199,7 @@ protected: private slots: void refreshGames(); - void updateGamesDisplay(); + void onGamesReceived(const Response &r, const QString &forUser); void onGamesContextMenu(const QPoint &pos); private: @@ -218,8 +218,6 @@ private: QString currentUser; ServerInfo_User currentUserInfo; bool currentOnline = false; - bool currentBuddy = false; - bool currentIgnored = false; std::function gameInviteAvailable; UserInfoHeaderWidget *header; @@ -227,7 +225,7 @@ private: QLabel *gamesLabel; QFrame *separator; QListView *gamesView; - UserGamesModel *gamesModel; + QStandardItemModel *gamesModel; QLabel *gamesStatus; QPushButton *closeBtn; QPushButton *refreshBtn; diff --git a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp index acb91351b..08eaaaff7 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp @@ -1,7 +1,6 @@ #include "user_list_painter.h" #include "../../interface/pixel_map_generator.h" -#include "../../interface/theme_manager.h" #include "libcockatrice/protocol/pb/serverinfo_user.pb.h" #include @@ -293,9 +292,7 @@ void UserListPainter::drawUserName(QPainter *painter, bool online, const Style &style) { - Q_UNUSED(option); - // Family comes from the InterfaceDefault theme role; bold stays caller-side. - QFont nameFont = themeManager->appFont(AppFont::InterfaceDefault); + QFont nameFont = option.font; nameFont.setBold(true); painter->setFont(nameFont); @@ -354,9 +351,7 @@ void UserListPainter::drawBadges(QPainter *painter, return; } - Q_UNUSED(option); - // Family comes from the InterfaceDefault theme role; the *0.68 size and bold stay caller-side. - QFont badgeFont = themeManager->appFont(AppFont::InterfaceDefault); + QFont badgeFont = option.font; badgeFont.setPointSizeF(badgeFont.pointSizeF() * 0.68); badgeFont.setBold(true); painter->setFont(badgeFont); diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index 943b4d5de..a20b39b82 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -519,19 +519,6 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, userTree->viewport()->update(); userTree->update(); }); - // Divider headers store their font on the item, so re-apply the role - // family here; the painter reads the role live and needs only the - // repaint below. - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { - QFont dividerFont = themeManager->appFont(AppFont::InterfaceDefault); - dividerFont.setBold(true); - for (auto it = sectionItems.constBegin(); it != sectionItems.constEnd(); ++it) { - it.value()->setFont(0, dividerFont); - it.value()->setSizeHint(0, QSize(0, QFontMetrics(dividerFont).height() + 16)); - } - userTree->viewport()->update(); - userTree->update(); - }); } QVBoxLayout *vbox = new QVBoxLayout; @@ -630,14 +617,7 @@ void UserListWidget::bind(UserListManager *mgr) connect(manager, &UserListManager::addedToIgnoreList, this, refreshCurrentPopup); connect(manager, &UserListManager::removedFromIgnoreList, this, refreshIfPopupOpen); connect(manager, &UserListManager::userJoinedOnline, this, refreshCurrentPopup); - connect(manager, &UserListManager::userLeftOnline, this, [this, refreshIfPopupOpen](const QString &name) { - // The online row may already have been removed; the pinned popup must - // still invalidate its games request and display the offline state. - if (userInfoPopup && userInfoPopup->getCurrentUser() == name) { - userInfoPopup->setOnline(false); - } - refreshIfPopupOpen(name); - }); + connect(manager, &UserListManager::userLeftOnline, this, refreshIfPopupOpen); rebuild(); } @@ -1512,7 +1492,7 @@ QTreeWidgetItem *UserListWidget::createSectionItem(Section section) // They act as collapsible section headers once they have focus. divider->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - QFont font = themeManager->appFont(AppFont::InterfaceDefault); + QFont font = userTree->font(); font.setBold(true); divider->setFont(0, font); // A little taller than a plain text row so the header reads as a section diff --git a/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp b/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp index 67354c9d7..bf35b7557 100644 --- a/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp +++ b/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp @@ -5,7 +5,6 @@ */ #include "settings_search_delegate.h" -#include "../interface/theme_manager.h" #include "settings_search_model.h" #include @@ -89,8 +88,7 @@ void SettingsSearchDelegate::paint(QPainter *painter, QRect textRect = contentRect.adjusted(iconOffset, 0, 0, 0); // Breadcrumb: "Page > Group" - // Family comes from the InterfaceDefault theme role; the -1 size and bold stay caller-side. - QFont breadcrumbFont = themeManager->appFont(AppFont::InterfaceDefault); + QFont breadcrumbFont = option.font; breadcrumbFont.setPointSize(breadcrumbFont.pointSize() - 1); breadcrumbFont.setBold(true); @@ -115,7 +113,7 @@ void SettingsSearchDelegate::paint(QPainter *painter, yPos += 20; // Setting label - QFont labelFont = themeManager->appFont(AppFont::InterfaceDefault); + QFont labelFont = option.font; labelFont.setPointSize(labelFont.pointSize() + 1); labelFont.setBold(isSelected); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp index 3720aeb55..97383534f 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp @@ -1,6 +1,5 @@ #include "archidekt_deck_preview_image_display_widget.h" -#include "../../../../../theme_manager.h" #include "../../../../general/display/shadow_background_label.h" #include @@ -18,27 +17,19 @@ ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(Q bottomLeftLabel = new ShadowBackgroundLabel(this, ""); bottomRightLabel = new ShadowBackgroundLabel(this, ""); - applyCardTextFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, - &ArchidektDeckPreviewImageDisplayWidget::applyCardTextFont); - - // Raise so labels appear above image - topLeftLabel->raise(); - topRightLabel->raise(); - bottomLeftLabel->raise(); - bottomRightLabel->raise(); -} - -void ArchidektDeckPreviewImageDisplayWidget::applyCardTextFont() -{ - // Family comes from the CardText theme role; size and weight stay caller-side as before. - QFont f = themeManager->appFont(AppFont::CardText); + QFont f; f.setBold(true); f.setPointSize(16); topLeftLabel->setFont(f); topRightLabel->setFont(f); bottomLeftLabel->setFont(f); bottomRightLabel->setFont(f); + + // Raise so labels appear above image + topLeftLabel->raise(); + topRightLabel->raise(); + bottomLeftLabel->raise(); + bottomRightLabel->raise(); } void ArchidektDeckPreviewImageDisplayWidget::setAspectRatio(float ratio) diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h index 41585e851..991f27b6a 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h @@ -60,9 +60,6 @@ protected: */ void resizeEvent(QResizeEvent *event) override; -private slots: - void applyCardTextFont(); - private: float aspectRatio = 1.0f; ///< Aspect ratio to maintain for the preview image }; 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 9f7369435..7f7a2d3fd 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -61,6 +60,8 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) { // Initialize network networkManager = NetworkManagerFactory::create(this); + networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); + connect(networkManager, &QNetworkAccessManager::finished, this, &TabArchidekt::processApiJson); // Initialize debounce timer searchDebounceTimer = new QTimer(this); @@ -76,48 +77,6 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) getTopDecks(); } -TabArchidekt::~TabArchidekt() -{ - cancelRequest(); -} - -void TabArchidekt::cancelRequest() -{ - const QPointer previousReply = activeReply; - activeReply = nullptr; - isLoadingMore = false; - if (previousReply) { - disconnect(previousReply, nullptr, this, nullptr); - previousReply->abort(); - previousReply->deleteLater(); - } -} - -void TabArchidekt::startRequest(QUrl url, RequestKind kind, int page) -{ - cancelRequest(); - requestUrl = url; - requestKind = kind; - requestedPage = page; - isLoadingMore = true; - retryButton->hide(); - requestStatusLabel->setText(kind == RequestKind::Deck ? tr("Loading deck…") : tr("Loading decks…")); - requestStatusLabel->show(); - - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); - auto *reply = networkManager->get(request); - activeReply = reply; - connect(reply, &QNetworkReply::finished, this, [this, reply]() { processApiJson(reply); }); -} - -void TabArchidekt::showRequestError(const QString &error) -{ - requestStatusLabel->setText(tr("Unable to load decks: %1").arg(error)); - requestStatusLabel->show(); - retryButton->show(); -} - void TabArchidekt::initializeUi() { // Main container @@ -235,18 +194,6 @@ void TabArchidekt::initializeUi() mainLayout->addWidget(primaryToolbar); mainLayout->addWidget(secondaryToolbar); - - auto *requestStatusRow = new QHBoxLayout; - requestStatusLabel = new QLabel(container); - requestStatusLabel->setTextFormat(Qt::PlainText); - requestStatusLabel->setWordWrap(true); - retryButton = new QPushButton(tr("Retry"), container); - retryButton->hide(); - connect(retryButton, &QPushButton::clicked, this, - [this]() { startRequest(requestUrl, requestKind, requestedPage); }); - requestStatusRow->addWidget(requestStatusLabel, 1); - requestStatusRow->addWidget(retryButton); - mainLayout->addLayout(requestStatusRow); mainLayout->addWidget(scrollArea); setCentralWidget(container); @@ -457,7 +404,6 @@ void TabArchidekt::retranslateUi() minDeckSizeLogicCombo->setItemText(0, tr("Exact")); searchButton->setText(tr("Search")); - retryButton->setText(tr("Retry")); settingsButton->setToolTip(tr("Display Settings")); } @@ -571,95 +517,82 @@ QString TabArchidekt::buildSearchUrl() void TabArchidekt::doSearch() { + // Reset to first page on new search + currentPage = 1; + // We're searching, so we'll be in list mode + isListMode = true; // Don't debounce - only called by explicit user actions now doSearchImmediate(); } void TabArchidekt::doSearchImmediate() { - searchDebounceTimer->stop(); - currentPage = 1; - startRequest(QUrl(buildSearchUrl()), RequestKind::Search); + QString url = buildSearchUrl(); + QNetworkRequest req{QUrl(url)}; + req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + networkManager->get(req); } void TabArchidekt::loadNextPage() { - if (activeReply || !isListMode || !listingsWidget || nextPageUrl.isEmpty()) { + if (isLoadingMore) { return; } - // Continue the accepted search, even if the user has edited filters without submitting them. - // Advance currentPage only after this response has been accepted, so failures can retry it. - startRequest(nextPageUrl, RequestKind::NextPage, currentPage + 1); + isLoadingMore = true; + currentPage++; + + QString url = buildSearchUrl(); + QNetworkRequest req{QUrl(url)}; + req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + networkManager->get(req); } void TabArchidekt::actNavigatePage(QString url) { - searchDebounceTimer->stop(); - startRequest(QUrl(url), RequestKind::Deck); + QNetworkRequest request{QUrl(url)}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + networkManager->get(request); } void TabArchidekt::getTopDecks() { - doSearchImmediate(); + currentPage = 1; + QNetworkRequest request{QUrl(buildSearchUrl())}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + networkManager->get(request); } void TabArchidekt::processApiJson(QNetworkReply *reply) { - if (reply != activeReply) { - reply->deleteLater(); - return; - } - activeReply = nullptr; - isLoadingMore = false; - reply->deleteLater(); - if (reply->error() != QNetworkReply::NoError) { - showRequestError(reply->errorString()); + isLoadingMore = false; + reply->deleteLater(); return; } QByteArray responseData = reply->readAll(); - QJsonParseError parseError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &parseError); + QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData); - if (parseError.error != QJsonParseError::NoError || !jsonDoc.isObject()) { - showRequestError(tr("Invalid response from Archidekt.")); + if (!jsonDoc.isObject()) { + isLoadingMore = false; + reply->deleteLater(); return; } QJsonObject jsonObj = jsonDoc.object(); - if (requestKind != RequestKind::Deck) { - if (!jsonObj.value("results").isArray() || - (!jsonObj.value("next").isNull() && !jsonObj.value("next").isString())) { - showRequestError(tr("Invalid deck listing from Archidekt.")); - return; - } - for (const auto &value : jsonObj.value("results").toArray()) { - if (!value.isObject() || value.toObject().value("id").toInt() <= 0) { - showRequestError(tr("Invalid deck listing from Archidekt.")); - return; - } - } - currentPage = requestedPage; - const QString next = jsonObj.value("next").toString(); - nextPageUrl = next.isEmpty() ? QUrl() : reply->url().resolved(QUrl(next)); - // An empty or self-repeating page cannot make forward progress. - if (jsonObj.value("results").toArray().isEmpty() || nextPageUrl == reply->url()) { - nextPageUrl.clear(); - } - requestStatusLabel->setText( - currentPage == 1 && jsonObj.value("results").toArray().isEmpty() ? tr("No decks found.") : QString()); - requestStatusLabel->setVisible(!requestStatusLabel->text().isEmpty()); + QString responseUrl = reply->url().toString(); + + if (responseUrl.startsWith("https://archidekt.com/api/decks/v3/")) { processTopDecksResponse(jsonObj); - } else { - if (jsonObj.value("id").toInt() <= 0 || !jsonObj.value("cards").isArray()) { - showRequestError(tr("Invalid deck from Archidekt.")); - return; - } - requestStatusLabel->hide(); + } else if (responseUrl.startsWith("https://archidekt.com/api/decks/")) { processDeckResponse(jsonObj); + } else { + prettyPrintJson(jsonObj, 4); } + + isLoadingMore = false; + reply->deleteLater(); } void TabArchidekt::processTopDecksResponse(QJsonObject reply) @@ -667,12 +600,8 @@ void TabArchidekt::processTopDecksResponse(QJsonObject reply) ArchidektDeckListingApiResponse deckData; deckData.fromJson(reply); - // An accepted listing restores list mode; a failed deck navigation leaves the listing usable. - isListMode = true; - // New search → clear everything - if (requestKind == RequestKind::Search) { - listingsWidget = nullptr; + if (currentPage == 1) { QtUtils::clearLayoutRec(resultsLayout, QtUtils::DeletePolicy::Immediate); listingsWidget = new ArchidektApiResponseDeckListingsDisplayWidget(resultsContainer, deckData, cardSizeSlider); @@ -697,10 +626,8 @@ void TabArchidekt::processDeckResponse(QJsonObject reply) // We're in single deck mode - disable infinite scroll isListMode = false; - nextPageUrl.clear(); // Clear existing results for single deck view - listingsWidget = nullptr; QtUtils::clearLayoutRec(resultsLayout, QtUtils::DeletePolicy::Immediate); auto display = new ArchidektApiResponseDeckDisplayWidget(resultsContainer, deckData, cardSizeSlider); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h index 81c9502df..c74380b63 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h @@ -8,10 +8,8 @@ #include #include #include -#include #include #include -#include #include #include @@ -57,7 +55,6 @@ public: * connects signals and slots, and triggers an initial fetch of top decks. */ explicit TabArchidekt(TabSupervisor *_tabSupervisor); - ~TabArchidekt() override; /** * @brief Update all UI text to reflect the current language or translation @@ -193,17 +190,6 @@ protected: bool eventFilter(QObject *obj, QEvent *event) override; private: - enum class RequestKind - { - Search, - NextPage, - Deck - }; - - void startRequest(QUrl url, RequestKind kind, int page = 1); - void cancelRequest(); - void showRequestError(const QString &error); - /** * @brief Initialize the main UI layout and toolbars * @@ -250,14 +236,7 @@ private: int currentPage; ///< Current page number for infinite scroll bool isLoadingMore; ///< Flag to prevent multiple simultaneous page loads bool isListMode; - QPointer activeReply; - RequestKind requestKind = RequestKind::Search; - QUrl requestUrl; - QUrl nextPageUrl; - int requestedPage = 1; - QPointer listingsWidget; - QLabel *requestStatusLabel; - QPushButton *retryButton; + ArchidektApiResponseDeckListingsDisplayWidget *listingsWidget = nullptr; // --------------------------------------------------------------------- // Layout Containers diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp index 7b60ed23c..8e0448faa 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp @@ -26,11 +26,6 @@ quint64 CommanderBracketService::estimateBracket(const DeckList &deck, QObject * return CommanderSpellbookApiAccessor::instance().estimateBracket(deck, requester); } -void CommanderBracketService::cancelEstimate(quint64 requestId) -{ - CommanderSpellbookApiAccessor::instance().cancelEstimate(requestId); -} - void CommanderBracketService::onEstimateBracketFinished(CommanderSpellbookApiAccessor::RequestId id, QObject *requester, const EstimateBracketResult &result) diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.h index 00debce86..22962cc95 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.h +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.h @@ -30,7 +30,6 @@ public: static CommanderBracketService &instance(); quint64 estimateBracket(const DeckList &deck, QObject *requester); - void cancelEstimate(quint64 requestId); signals: void estimateFinished(quint64 requestId, QObject *requester, const CommanderBracketEstimate &estimate); 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 9adda008e..5279165af 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 @@ -28,18 +28,9 @@ template class QSharedPointer; CommanderBracketWidget::CommanderBracketWidget(QWidget *parent) : QWidget(parent) { - autoEstimateTimer = new QTimer(this); - autoEstimateTimer->setSingleShot(true); - connect(autoEstimateTimer, &QTimer::timeout, this, [this]() { - if (SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled() == - commanderSpellbookIntegrationEnabledIndexAutomatic) { - requestBracketEstimate(); - } - }); bracketLabel = new QLabel(tr("Bracket:"), this); bracketValueLabel = new QLabel(this); - bracketValueLabel->setTextFormat(Qt::PlainText); bracketValueLabel->setText("-"); bracketValueLabel->setObjectName("bracketValueLabel"); @@ -74,38 +65,18 @@ CommanderBracketWidget::CommanderBracketWidget(QWidget *parent) : QWidget(parent this, &CommanderBracketWidget::maybeAutoEstimateBracket); connect(&SettingsCache::instance().deckEditor(), &DeckEditorSettings::commanderSpellbookIntegrationUseOfficialBracketNamesChanged, this, - &CommanderBracketWidget::displayEstimate); + &CommanderBracketWidget::maybeAutoEstimateBracket); setVisible(false); } -CommanderBracketWidget::~CommanderBracketWidget() -{ - cancelEstimate(); -} - -void CommanderBracketWidget::cancelEstimate() -{ - autoEstimateTimer->stop(); - const quint64 canceledRequest = requestId; - requestId = 0; - CommanderBracketService::instance().cancelEstimate(canceledRequest); - bracketRefreshButton->setEnabled(true); - if (canceledRequest) { - bracketValueLabel->setText("-"); - bracketValueLabel->setToolTip({}); - } -} - void CommanderBracketWidget::setDeck(const QSharedPointer &_deck) { - cancelEstimate(); deck = _deck; - hasEstimate = false; + requestId = 0; // invalidate any in-flight estimate for the previous deck // Reset the displayed bracket bracketValueLabel->setText("-"); - bracketValueLabel->setToolTip({}); bracketInfoButton->setToolTip({}); bracketInfoButton->setEnabled(false); bracketRefreshButton->setEnabled(true); @@ -173,7 +144,7 @@ bool CommanderBracketWidget::promptCommanderSpellbookIntegration() dialog.exec(); // Persist naming choice (if not disabled) - if (clickedButton == enableBtn || clickedButton == automaticBtn) { + if (clickedButton != disableBtn) { bool useOfficial = namingCombo->currentIndex() == 1; SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationUseOfficialBracketNames(useOfficial); } @@ -205,20 +176,9 @@ void CommanderBracketWidget::updateBracketVisibility(bool visible) void CommanderBracketWidget::requestBracketEstimate() { - const int mode = SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled(); - if (!deck || deck->getGameFormat().compare("commander", Qt::CaseInsensitive) != 0 || requestId || - (mode != commanderSpellbookIntegrationEnabledIndexEnabled && - mode != commanderSpellbookIntegrationEnabledIndexAutomatic)) { - return; - } - - autoEstimateTimer->stop(); - hasEstimate = false; bracketRefreshButton->setEnabled(false); bracketInfoButton->setEnabled(false); - bracketInfoButton->setToolTip({}); bracketValueLabel->setText(tr("Calculating…")); - bracketValueLabel->setToolTip({}); requestId = CommanderBracketService::instance().estimateBracket(*deck, this); } @@ -231,35 +191,22 @@ void CommanderBracketWidget::onEstimateBracketFinished(quint64 id, return; } - requestId = 0; - hasEstimate = true; - lastEstimate = result; - displayEstimate(); - bracketRefreshButton->setEnabled(true); -} - -void CommanderBracketWidget::displayEstimate() -{ - if (!hasEstimate) { - bracketInfoButton->setToolTip(tr("Why this bracket?")); - return; - } - BracketExplainer explainer; - lastBracketExplanation = explainer.explain(lastEstimate.rawResult); + lastBracketExplanation = explainer.explain(result.rawResult); // Display bracket bracketValueLabel->setText( SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationUseOfficialBracketNames() - ? lastEstimate.officialName - : lastEstimate.displayName); + ? result.officialName + : result.displayName); + bracketRefreshButton->setEnabled(true); // Build tooltip QString tooltip; for (const auto §ion : lastBracketExplanation.sections) { - tooltip += "" + section.title.toHtmlEscaped() + "
    "; + tooltip += "" + section.title + "
    "; for (const auto &line : section.bulletPoints) { - tooltip += "• " + line.toHtmlEscaped() + "
    "; + tooltip += "• " + line + "
    "; } tooltip += "
    "; } @@ -268,17 +215,13 @@ void CommanderBracketWidget::displayEstimate() bracketInfoButton->setEnabled(!tooltip.isEmpty()); } -void CommanderBracketWidget::onEstimateBracketError(quint64 id, QObject *requester, const QString &error) +void CommanderBracketWidget::onEstimateBracketError(quint64 id, QObject *requester, const QString & /*error*/) { if (requester != this || id != requestId) { return; } - requestId = 0; - hasEstimate = false; bracketValueLabel->setText("-"); - bracketValueLabel->setToolTip( - QStringLiteral("%1").arg(tr("Unable to estimate bracket: %1").arg(error).toHtmlEscaped())); bracketRefreshButton->setEnabled(true); bracketInfoButton->setToolTip({}); bracketInfoButton->setEnabled(false); @@ -286,15 +229,6 @@ void CommanderBracketWidget::onEstimateBracketError(quint64 id, QObject *request void CommanderBracketWidget::maybeAutoEstimateBracket() { - if (prompting) { - return; - } - autoEstimateTimer->stop(); - if (!deck) { - cancelEstimate(); - updateBracketVisibility(false); - return; - } const QString formatKey = deck->getGameFormat(); const bool isCommander = (formatKey.compare("commander", Qt::CaseInsensitive) == 0); @@ -302,20 +236,17 @@ void CommanderBracketWidget::maybeAutoEstimateBracket() int mode = SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled(); if (!isCommander || mode == commanderSpellbookIntegrationEnabledIndexDisabled) { - cancelEstimate(); updateBracketVisibility(false); return; } if (mode == commanderSpellbookIntegrationEnabledIndexUnprompted) { - const auto promptedDeck = deck; + if (prompting) { + return; + } prompting = true; const bool accepted = promptCommanderSpellbookIntegration(); prompting = false; - if (deck != promptedDeck || !deck || deck->getGameFormat().compare("commander", Qt::CaseInsensitive) != 0) { - maybeAutoEstimateBracket(); - return; - } if (!accepted) { updateBracketVisibility(false); return; @@ -330,17 +261,17 @@ void CommanderBracketWidget::maybeAutoEstimateBracket() } // Avoid firing if we already have a result or a request in flight - if (requestId || hasEstimate) { + if (!bracketRefreshButton->isEnabled()) { return; } // Defer to avoid races during init / model rebuild - autoEstimateTimer->start(0); + QTimer::singleShot(0, this, &CommanderBracketWidget::requestBracketEstimate); } void CommanderBracketWidget::retranslateUi() { bracketLabel->setText(tr("Bracket:")); - displayEstimate(); + bracketInfoButton->setToolTip(tr("Why this bracket?")); bracketRefreshButton->setToolTip(tr("Recalculate bracket")); } diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.h index 0ef1eedee..f5b2b7958 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.h @@ -1,7 +1,6 @@ #ifndef COCKATRICE_COMMANDER_BRACKET_WIDGET_H #define COCKATRICE_COMMANDER_BRACKET_WIDGET_H -#include "commander_bracket_service.h" #include "commander_spellbook_bracket_explainer.h" #include @@ -12,7 +11,6 @@ class QLabel; class QToolButton; -class QTimer; struct CommanderBracketEstimate; class DeckList; class QObject; @@ -24,7 +22,6 @@ class CommanderBracketWidget : public QWidget public: explicit CommanderBracketWidget(QWidget *parent = nullptr); - ~CommanderBracketWidget() override; void setDeck(const QSharedPointer &_deck); void retranslateUi(); @@ -38,15 +35,10 @@ private slots: private: bool promptCommanderSpellbookIntegration(); void updateBracketVisibility(bool visible); - void cancelEstimate(); - void displayEstimate(); QSharedPointer deck; bool prompting = false; quint64 requestId = 0; - QTimer *autoEstimateTimer; - bool hasEstimate = false; - CommanderBracketEstimate lastEstimate; QLabel *bracketLabel; QLabel *bracketValueLabel; diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.cpp index 8456e2e21..bd8d9379d 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.cpp @@ -9,10 +9,9 @@ #include #include #include -#include #include #include -#include +#include #include class DeckList; @@ -25,18 +24,13 @@ CommanderSpellbookApiAccessor &CommanderSpellbookApiAccessor::instance() return instance; } -CommanderSpellbookApiAccessor::CommanderSpellbookApiAccessor(QObject *parent) - : QObject(parent), network(NetworkManagerFactory::create(this)) +CommanderSpellbookApiAccessor::CommanderSpellbookApiAccessor(QObject *parent) : QObject(parent) { } CommanderSpellbookApiAccessor::RequestId CommanderSpellbookApiAccessor::estimateBracket(const DeckList &deck, QObject *requester) { - if (!requester) { - return 0; - } - CommanderSpellbookDeckRequest deckRequest = CommanderSpellbookDeckRequest::fromDeckList(deck); QJsonDocument doc(deckRequest.toJson()); @@ -46,39 +40,30 @@ CommanderSpellbookApiAccessor::RequestId CommanderSpellbookApiAccessor::estimate req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); - QNetworkReply *reply = network->post(req, body); + QNetworkReply *reply = network.post(req, body); const RequestId id = nextRequestId++; - pendingRequests.insert(id, reply); - const QPointer guardedRequester(requester); - connect(requester, &QObject::destroyed, reply, [this, id]() { cancelEstimate(id); }); - connect(reply, &QNetworkReply::finished, this, [this, reply, id, guardedRequester]() { - if (pendingRequests.take(id) != reply) { - return; - } - reply->deleteLater(); - if (guardedRequester) { - onEstimateReplyFinished(reply, id, guardedRequester); - } - }); + reply->setProperty("requestId", QVariant::fromValue(id)); + reply->setProperty("requester", QVariant::fromValue(requester)); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { onEstimateReplyFinished(reply); }); return id; } -void CommanderSpellbookApiAccessor::cancelEstimate(RequestId id) +void CommanderSpellbookApiAccessor::onEstimateReplyFinished(QNetworkReply *reply) { - const QPointer reply = pendingRequests.take(id); - if (!reply) { + reply->deleteLater(); + + const RequestId id = reply->property("requestId").toULongLong(); + QObject *requester = reply->property("requester").value(); + + if (!requester) { + // Requester died — silently drop return; } - disconnect(reply, nullptr, this, nullptr); - reply->abort(); - reply->deleteLater(); -} -void CommanderSpellbookApiAccessor::onEstimateReplyFinished(QNetworkReply *reply, RequestId id, QObject *requester) -{ if (reply->error() != QNetworkReply::NoError) { emit estimateBracketError(id, requester, reply->errorString()); return; @@ -93,10 +78,6 @@ void CommanderSpellbookApiAccessor::onEstimateReplyFinished(QNetworkReply *reply } EstimateBracketResult result = EstimateBracketResult::fromJson(doc.object()); - if (result.bracketTag.trimmed().isEmpty()) { - emit estimateBracketError(id, requester, QStringLiteral("Missing bracket in response")); - return; - } emit estimateBracketFinished(id, requester, result); } diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.h index a1b2936bb..98e7e3710 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.h +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_spellbook_api_accessor.h @@ -1,16 +1,14 @@ #ifndef COCKATRICE_COMMANDER_SPELLBOOK_API_ACCESSOR_H #define COCKATRICE_COMMANDER_SPELLBOOK_API_ACCESSOR_H -#include +#include #include -#include #include #include #include class DeckList; class QNetworkReply; -class QNetworkAccessManager; struct EstimateBracketResult; class CommanderSpellbookApiAccessor final : public QObject @@ -23,7 +21,6 @@ public: using RequestId = quint64; RequestId estimateBracket(const DeckList &deck, QObject *requester); - void cancelEstimate(RequestId id); signals: void estimateBracketFinished(RequestId id, QObject *requester, const EstimateBracketResult &result); @@ -34,10 +31,9 @@ private: explicit CommanderSpellbookApiAccessor(QObject *parent = nullptr); Q_DISABLE_COPY_MOVE(CommanderSpellbookApiAccessor) - void onEstimateReplyFinished(QNetworkReply *reply, RequestId id, QObject *requester); + void onEstimateReplyFinished(QNetworkReply *reply); - QNetworkAccessManager *network; - QHash> pendingRequests; + QNetworkAccessManager network; RequestId nextRequestId = 1; }; diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp index be490ce37..407f53e99 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp @@ -3,14 +3,11 @@ #include "../../../../../client/settings/cache_settings.h" #include -#include #include -#include #include #include #include #include -#include #include #include #include @@ -25,14 +22,6 @@ HandleCommanderBrackets::HandleCommanderBrackets(QObject *parent) { } -HandleCommanderBrackets::~HandleCommanderBrackets() -{ - if (reply) { - disconnect(reply, nullptr, this, nullptr); - reply->abort(); - } -} - void HandleCommanderBrackets::downloadBracketDefinitions() { if (reply) { @@ -46,49 +35,31 @@ void HandleCommanderBrackets::downloadBracketDefinitions() void HandleCommanderBrackets::actFinishParsingDownloadedData() { - auto *finishedReply = qobject_cast(sender()); - if (!finishedReply || finishedReply != reply) { - return; - } + reply = qobject_cast(sender()); - // Clear the active request before notifying listeners, which may immediately retry. - reply = nullptr; - finishedReply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + emit sigBracketDefinitionsDownloadFailed(reply->error()); - if (finishedReply->error() != QNetworkReply::NoError) { - emit sigBracketDefinitionsDownloadFailed(finishedReply->error()); + reply->deleteLater(); return; } QJsonParseError parseError; - auto document = QJsonDocument::fromJson(finishedReply->readAll(), &parseError); + auto document = QJsonDocument::fromJson(reply->readAll(), &parseError); - if (parseError.error != QJsonParseError::NoError || !document.isObject() || - !document.object().value("brackets").isArray() || document.object().value("brackets").toArray().isEmpty()) { + if (parseError.error != QJsonParseError::NoError) { emit sigBracketDefinitionsDownloadFailed(QNetworkReply::UnknownContentError); - return; - } - // Validate the complete replacement before touching the last usable definitions. - // Tags are QSettings group names, so path separators must not create nested groups. - QSet tags; - for (const auto &value : document.object().value("brackets").toArray()) { - const auto definition = value.toObject(); - const auto tag = definition.value("tag").toString(); - if (!value.isObject() || tag.trimmed().isEmpty() || tag.contains('/') || tag.contains('\\') || - tags.contains(tag) || definition.value("officialName").toString().trimmed().isEmpty() || - definition.value("displayName").toString().trimmed().isEmpty() || - !definition.value("explanation").isString()) { - emit sigBracketDefinitionsDownloadFailed(QNetworkReply::UnknownContentError); - return; - } - tags.insert(tag); + reply->deleteLater(); + return; } updateBracketDefinitions(document.toVariant().toMap()); emit sigBracketDefinitionsDownloaded(); + + reply->deleteLater(); } void HandleCommanderBrackets::updateBracketDefinitions(const QVariantMap &jsonMap) diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.h b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.h index 99b15832c..39bddba05 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.h +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.h @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -15,7 +14,6 @@ class HandleCommanderBrackets : public QObject public: explicit HandleCommanderBrackets(QObject *parent = nullptr); - ~HandleCommanderBrackets() override; void downloadBracketDefinitions(); @@ -30,7 +28,7 @@ private: void updateBracketDefinitions(const QVariantMap &jsonMap); QNetworkAccessManager *nam; - QPointer reply; + QNetworkReply *reply; }; #endif // COCKATRICE_HANDLE_COMMANDER_BRACKETS_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index 2b76de973..b2ddace01 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -1,6 +1,5 @@ #include "edhrec_api_response_card_details_display_widget.h" -#include "../../../../../../theme_manager.h" #include "../../../../../cards/card_info_picture_widget.h" #include "../../../../../cards/card_size_widget.h" #include "../../../../../general/display/background_plate_widget.h" @@ -10,7 +9,6 @@ #include "edhrec_api_response_card_synergy_display_widget.h" #include "libcockatrice/card/database/card_database_querier.h" -#include #include #include #include @@ -34,9 +32,7 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi nameLabel = new QLabel(this); nameLabel->setText(toDisplay.name); nameLabel->setAlignment(Qt::AlignHCenter); - applyCardTextFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, - &EdhrecApiResponseCardDetailsDisplayWidget::applyCardTextFont); + nameLabel->setStyleSheet("font-size: 20px; font-weight: bold"); inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay); @@ -90,14 +86,3 @@ void EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation() { emit requestUrl(toDisplay.url); } - -void EdhrecApiResponseCardDetailsDisplayWidget::applyCardTextFont() -{ - // Family comes from the CardText theme role; the 20px size and bold - // weight previously carried by the stylesheet stay caller-side. Pixel - // size, not point size: the stylesheet said 20px, and 20pt is ~27px. - QFont cardFont = themeManager->appFont(AppFont::CardText); - cardFont.setPixelSize(20); - cardFont.setWeight(QFont::Bold); - nameLabel->setFont(cardFont); -} diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h index 8a3c8fba4..d738f4685 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h @@ -30,9 +30,6 @@ public slots: signals: void requestUrl(QString url); -private slots: - void applyCardTextFont(); - private: EdhrecApiResponseCardDetails toDisplay; QVBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index df0319f88..2ff22f2b3 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp @@ -1,6 +1,5 @@ #include "edhrec_api_response_commander_details_display_widget.h" -#include "../../../../../../theme_manager.h" #include "../../../../../cards/card_info_picture_widget.h" #include "../../../../../cards/card_size_widget.h" #include "../../api_response/cards/edhrec_commander_api_response_commander_details.h" @@ -9,7 +8,6 @@ #include "edhrec_commander_api_response_navigation_widget.h" #include "libcockatrice/card/database/card_database_querier.h" -#include #include #include #include @@ -50,16 +48,15 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom commanderName = new QLabel(this); commanderName->setText(commanderDetails.getName()); commanderName->setAlignment(Qt::AlignCenter); + commanderName->setStyleSheet("font-size: 28px; font-weight: bold"); label = new QLabel(this); label->setAlignment(Qt::AlignCenter); + label->setStyleSheet("font-size: 16px"); salt = new QLabel(this); salt->setAlignment(Qt::AlignCenter); - - applyCardTextFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, - &EdhrecCommanderResponseCommanderDetailsDisplayWidget::applyCardTextFont); + salt->setStyleSheet("font-size: 16px"); cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices()); @@ -86,19 +83,3 @@ void EdhrecCommanderResponseCommanderDetailsDisplayWidget::retranslateUi() label->setText(commanderDetails.getLabel()); salt->setText(tr("Salt: ") + QString::number(commanderDetails.getSalt())); } - -void EdhrecCommanderResponseCommanderDetailsDisplayWidget::applyCardTextFont() -{ - // Family comes from the CardText theme role; the sizes and the bold - // weight previously carried by the stylesheets stay caller-side. Pixel - // sizes, not point sizes: the stylesheets said 28px and 16px. - QFont nameFont = themeManager->appFont(AppFont::CardText); - nameFont.setPixelSize(28); - nameFont.setWeight(QFont::Bold); - commanderName->setFont(nameFont); - - QFont detailsFont = themeManager->appFont(AppFont::CardText); - detailsFont.setPixelSize(16); - label->setFont(detailsFont); - salt->setFont(detailsFont); -} diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h index 99e6b3d43..197263512 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h @@ -30,9 +30,6 @@ public: QString baseUrl); void retranslateUi(); -private slots: - void applyCardTextFont(); - private: EdhrecCommanderApiResponseCommanderDetails commanderDetails; QHBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp index 749476619..b26a7e574 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp @@ -1,9 +1,6 @@ #include "edhrec_commander_api_response_bracket_navigation_widget.h" -#include "../../interface/theme_manager.h" - #include -#include #include #include #include @@ -28,7 +25,7 @@ EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBra QString label = option.isEmpty() ? "All" : option.at(0).toUpper() + option.mid(1); QPushButton *optionButton = new QPushButton(label, this); optionButton->setMinimumHeight(84); - optionButton->setStyleSheet(QString()); + optionButton->setStyleSheet("font-size: 24px"); gameChangerButtons[option] = optionButton; layout->addWidget(optionButton, 2, i); connect(optionButton, &QPushButton::clicked, this, [=, this]() { @@ -38,10 +35,6 @@ EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBra }); } - applyInterfaceDefaultFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, - &EdhrecCommanderApiResponseBracketNavigationWidget::applyInterfaceDefaultFont); - updateOptionButtonSelection(gameChangerButtons, ""); retranslateUi(); @@ -101,18 +94,8 @@ void EdhrecCommanderApiResponseBracketNavigationWidget::updateOptionButtonSelect const QString &selectedKey) { for (auto it = buttons.begin(); it != buttons.end(); ++it) { - const bool selected = (it.key() == selectedKey); - it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString()); - QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); - buttonFont.setPixelSize(24); - buttonFont.setWeight(selected ? QFont::Bold : QFont::Normal); - it.value()->setFont(buttonFont); + it.value()->setStyleSheet(it.key() == selectedKey + ? "background-color: lightblue; font-weight: bold; font-size: 24px;" + : "font-size: 24px"); } -} - -void EdhrecCommanderApiResponseBracketNavigationWidget::applyInterfaceDefaultFont() -{ - // Family comes from the InterfaceDefault theme role; the 24px size stays - // caller-side and the selected button keeps its bold emphasis. - updateOptionButtonSelection(gameChangerButtons, selectedGameChanger); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h index aa4e61ff1..a741b6696 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h @@ -37,9 +37,6 @@ private: QMap gameChangerButtons; void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); - -private slots: - void applyInterfaceDefaultFont(); }; #endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp index 27cd1c51f..48704d010 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp @@ -1,9 +1,6 @@ #include "edhrec_commander_api_response_budget_navigation_widget.h" -#include "../../interface/theme_manager.h" - #include -#include #include #include #include @@ -28,7 +25,7 @@ EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudg QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1); QPushButton *btn = new QPushButton(label, this); btn->setMinimumHeight(84); - btn->setStyleSheet(QString()); + btn->setStyleSheet("font-size: 24px"); budgetButtons[option] = btn; layout->addWidget(btn, 4, i); connect(btn, &QPushButton::clicked, this, [=, this]() { @@ -38,10 +35,6 @@ EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudg }); } - applyInterfaceDefaultFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, - &EdhrecCommanderApiResponseBudgetNavigationWidget::applyInterfaceDefaultFont); - updateOptionButtonSelection(budgetButtons, ""); retranslateUi(); @@ -103,18 +96,8 @@ void EdhrecCommanderApiResponseBudgetNavigationWidget::updateOptionButtonSelecti const QString &selectedKey) { for (auto it = buttons.begin(); it != buttons.end(); ++it) { - const bool selected = (it.key() == selectedKey); - it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString()); - QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); - buttonFont.setPixelSize(24); - buttonFont.setWeight(selected ? QFont::Bold : QFont::Normal); - it.value()->setFont(buttonFont); + it.value()->setStyleSheet(it.key() == selectedKey + ? "background-color: lightblue; font-weight: bold; font-size: 24px;" + : "font-size: 24px"); } -} - -void EdhrecCommanderApiResponseBudgetNavigationWidget::applyInterfaceDefaultFont() -{ - // Family comes from the InterfaceDefault theme role; the 24px size stays - // caller-side and the selected button keeps its bold emphasis. - updateOptionButtonSelection(budgetButtons, selectedBudget); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h index d727225a5..8013d1d6b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h @@ -37,9 +37,6 @@ private: QMap budgetButtons; void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); - -private slots: - void applyInterfaceDefaultFont(); }; #endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp index 57070a5e1..220dcfbda 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp @@ -1,11 +1,9 @@ #include "edhrec_commander_api_response_navigation_widget.h" -#include "../../interface/theme_manager.h" #include "../../tab_edhrec_main.h" #include "edhrec_commander_api_response_bracket_navigation_widget.h" #include "edhrec_commander_api_response_budget_navigation_widget.h" -#include #include #include #include @@ -32,14 +30,10 @@ EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigation comboPushButton = new QPushButton(this); comboPushButton->setMinimumHeight(84); - comboPushButton->setStyleSheet(QString()); + comboPushButton->setStyleSheet("font-size: 24px"); averageDeckPushButton = new QPushButton(this); averageDeckPushButton->setMinimumHeight(84); - averageDeckPushButton->setStyleSheet(QString()); - - applyInterfaceDefaultFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, - &EdhrecCommanderApiResponseNavigationWidget::applyInterfaceDefaultFont); + averageDeckPushButton->setStyleSheet("font-size: 24px"); layout->addWidget(comboPushButton, 0, 0, 1, 1); layout->addWidget(averageDeckPushButton, 0, 1, 1, 1); @@ -67,15 +61,6 @@ void EdhrecCommanderApiResponseNavigationWidget::retranslateUi() averageDeckPushButton->setText(tr("Average Deck")); } -void EdhrecCommanderApiResponseNavigationWidget::applyInterfaceDefaultFont() -{ - // Family comes from the InterfaceDefault theme role; the 24px size stays caller-side. - QFont navFont = themeManager->appFont(AppFont::InterfaceDefault); - navFont.setPixelSize(24); - comboPushButton->setFont(navFont); - averageDeckPushButton->setFont(navFont); -} - QString EdhrecCommanderApiResponseNavigationWidget::addNavigationOptionsToUrl(QString baseUrl) { if (!bracketNavigationWidget->getSelectedGameChanger().isEmpty()) { diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h index 456228554..737558625 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h @@ -35,9 +35,6 @@ public slots: signals: void requestUrl(QString url); -private slots: - void applyInterfaceDefaultFont(); - private: QGridLayout *layout; diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index e88b2aad6..7ca179cea 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -131,16 +131,10 @@ void TabDeckEditor::loadLayout() LayoutsSettings &layouts = SettingsCache::instance().layouts(); auto layoutState = layouts.getDeckEditorLayoutState(); - // Start with every current dock placed. Saved layouts from older versions - // cannot describe docks introduced later; restoreState leaves those docks - // in their existing positions while restoring the saved docks, including - // their visibility and floating state. - restartLayout(); - if (!layoutState.isEmpty()) { - if (!restoreState(layoutState)) { - // A corrupt or incompatible state must not leave a partial layout. - restartLayout(); - } + if (layoutState.isNull()) { + restartLayout(); + } else { + restoreState(layoutState); restoreGeometry(layouts.getDeckEditorGeometry()); } } diff --git a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp b/cockatrice/src/interface/widgets/tabs/tab_developer.cpp index 024cd46f0..d641a1ea5 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_developer.cpp @@ -6,7 +6,6 @@ #include "tab_developer.h" -#include "../interface/theme_manager.h" #include "libcockatrice/protocol/pb/response.pb.h" #include @@ -101,20 +100,6 @@ TabDeveloper::TabDeveloper(TabSupervisor *_tabSupervisor, AbstractClient *_clien central->setLayout(mainLayout); setCentralWidget(central); - // Separator rows store their font on the item; re-apply the role family on - // font changes (value cells keep the default font and need only a repaint). - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { - QFont separatorFont = themeManager->appFont(AppFont::InterfaceDefault); - separatorFont.setBold(true); - for (int row = 0; row < statsTable->rowCount(); ++row) { - auto *labelItem = statsTable->item(row, 0); - if (labelItem && !(labelItem->flags() & Qt::ItemIsSelectable)) { - labelItem->setFont(separatorFont); - } - } - statsTable->viewport()->update(); - }); - retranslateUi(); } @@ -170,8 +155,7 @@ void TabDeveloper::appendSeparatorRow(const QString §ionTitle) const int row = statsTable->rowCount(); statsTable->insertRow(row); auto *labelItem = new QTableWidgetItem(sectionTitle); - // Family comes from the InterfaceDefault theme role; bold stays caller-side. - QFont font = themeManager->appFont(AppFont::InterfaceDefault); + auto font = labelItem->font(); font.setBold(true); labelItem->setFont(font); labelItem->setFlags(labelItem->flags() & ~Qt::ItemIsSelectable); diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index cbff850a4..9e724727f 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -99,7 +99,6 @@ class Message; TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) : Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr) { - setObjectName("TabGame"); // THIS CTOR IS USED ON REPLAY game = new Replay(this, _replay, tabSupervisor->getIsLocalGame(), &SettingsCache::instance().userInterface(), new SettingsCardPreferenceProvider(this)); @@ -117,7 +116,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) addDockWidget(Qt::BottomDockWidgetArea, replayDock); mainWidget = new QStackedWidget(this); - mainWidget->setObjectName("mainWidget"); mainWidget->addWidget(deckViewContainerWidget); mainWidget->addWidget(gamePlayAreaWidget); setCentralWidget(mainWidget); @@ -146,7 +144,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, const QMap &_roomGameTypes) : Tab(_tabSupervisor), userListProxy(_tabSupervisor->getUserListManager()) { - setObjectName("TabGame"); // THIS CTOR IS USED ON GAMES game = new Game(this, tabSupervisor->getIsLocalGame(), _clients, event, _roomGameTypes, &SettingsCache::instance().userInterface(), new SettingsCardPreferenceProvider(this)); @@ -163,7 +160,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock); mainWidget = new QStackedWidget(this); - mainWidget->setObjectName("mainWidget"); mainWidget->addWidget(deckViewContainerWidget); mainWidget->addWidget(gamePlayAreaWidget); mainWidget->setContentsMargins(0, 0, 0, 0); @@ -1122,7 +1118,6 @@ void TabGame::createMenuItems() connect(aFocusChat, &QAction::triggered, sayEdit, qOverload<>(&LineEditCompleter::setFocus)); phasesMenu = new TearOffMenu(this); - phasesMenu->setObjectName("phasesMenu"); for (int i = 0; i < phasesToolbar->phaseCount(); ++i) { auto *temp = new QAction(QString(), this); @@ -1133,16 +1128,12 @@ void TabGame::createMenuItems() phasesMenu->addSeparator(); phasesMenu->addAction(aNextPhase); - aNextPhase->setObjectName("aNextPhase"); phasesMenu->addAction(aNextPhaseAction); - aNextPhaseAction->setObjectName("aNextPhaseAction"); gameMenu = new QMenu(this); - gameMenu->setObjectName("gameMenu"); playersSeparator = gameMenu->addSeparator(); gameMenu->addMenu(phasesMenu); gameMenu->addAction(aNextTurn); - aNextTurn->setObjectName("aNextTurn"); gameMenu->addAction(aReverseTurn); gameMenu->addSeparator(); gameMenu->addAction(aRemoveLocalArrows); diff --git a/cockatrice/src/interface/widgets/tabs/tab_home.cpp b/cockatrice/src/interface/widgets/tabs/tab_home.cpp index 3ad16aa91..af9994fdd 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_home.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_home.cpp @@ -6,7 +6,6 @@ class TabSupervisor; TabHome::TabHome(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client) { - setObjectName("TabHome"); homeWidget = new HomeWidget(this, tabSupervisor); setCentralWidget(homeWidget); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp index dc14b6e4a..ce37ce440 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp @@ -4,7 +4,6 @@ #include "../../deck_loader/deck_loader.h" #include "../cards/card_size_widget.h" #include "../general/layout_containers/flow_widget.h" -#include "../interface/theme_manager.h" #include "../visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h" #include "../visual_deck_storage/deck_preview/public_deck_preview_widget.h" #include "../visual_deck_storage/remote_public_decks_model.h" @@ -46,17 +45,7 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c cardSize = SettingsCache::instance().cardsDisplay().getVisualDeckStorageCardSize(); titleLabel = new QLabel(tr("Public decks of %1").arg(userName.toHtmlEscaped()), this); - // Family comes from the InterfaceDefault theme role; bold stays caller-side. - auto applyTitleFont = [this]() { - titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); - }; - applyTitleFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, [this, applyTitleFont] { - applyTitleFont(); - // Preview cells paint with the live role font; repaint the grid so the - // new family shows without waiting for a scroll or hover. - flowWidget->update(); - }); + titleLabel->setFont(QtUtils::boldFont(titleLabel->font())); auto *headerLayout = new QHBoxLayout; headerLayout->addWidget(titleLabel); diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.cpp b/cockatrice/src/interface/widgets/tabs/tab_report.cpp index 7986286b3..988c3e7d9 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_report.cpp @@ -1,6 +1,5 @@ #include "tab_report.h" -#include "../interface/theme_manager.h" #include "../server/user/user_context_menu.h" #include "../utility/report_utils.h" #include "abstract_client.h" @@ -13,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -139,8 +137,7 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T chatGroup = new QGroupBox; chatLogEdit = new QTextEdit; chatLogEdit->setReadOnly(true); - applyChatLogFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, &TabReport::applyChatLogFont); + chatLogEdit->setFont(QtUtils::monospaceFont()); auto *chatLayout = new QVBoxLayout(chatGroup); chatLayout->setContentsMargins(4, 4, 4, 4); chatLayout->addWidget(chatLogEdit); @@ -272,11 +269,6 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T refreshList(); } -void TabReport::applyChatLogFont() -{ - chatLogEdit->setFont(report_utils::reportLogFont()); -} - void TabReport::retranslateUi() { userContextMenu->retranslateUi(); diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.h b/cockatrice/src/interface/widgets/tabs/tab_report.h index 43e778f4b..32ebe1a3f 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.h +++ b/cockatrice/src/interface/widgets/tabs/tab_report.h @@ -62,7 +62,6 @@ private slots: void statsResponse(const Response &response); void reopenReport(); void reopenResponse(const Response &response); - void applyChatLogFont(); private: int selectedReportId() const; diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index cf71dd7ed..3316232a4 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -158,15 +157,6 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget * tabReplays(nullptr), tabAdmin(nullptr), tabCardArtRules(nullptr), tabLog(nullptr), tabReport(nullptr), tabModeration(nullptr), tabDeveloper(nullptr), isLocalGame(false) { - setObjectName("tabSupervisor"); - - // QTabWidget keeps tab pages in an internal stacked widget. Give it a stable - // name so the GUI tests can address the pages by a readable path - // (tabSupervisor/stackedWidget/). - if (auto *stackedWidget = findChild()) { - stackedWidget->setObjectName("stackedWidget"); - } - setElideMode(Qt::ElideRight); setMovable(true); setIconSize(QSize(15, 15)); @@ -472,9 +462,6 @@ void TabSupervisor::addCloseButtonToTab(Tab *tab, int tabIndex, QAction *manager auto closeSide = static_cast( tabBar()->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, tabBar())); auto *closeButton = new CloseButton(tab); - // No object name: the tab bar index shifts when tabs move or close, so an - // index-derived name would go stale (or collide). Callers find the button - // through the tab widget instead. if (manager) { // If managed, all close requests should go through the menu action connect(closeButton, &CloseButton::clicked, this, [manager] { checkAndTrigger(manager, false); }); diff --git a/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp b/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp index 0090e9d65..a629df43c 100644 --- a/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp +++ b/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp @@ -1,6 +1,5 @@ #include "card_completer_delegate.h" -#include "../../interface/theme_manager.h" #include "../../pixel_map_generator.h" #include "../cards/additional_info/mana_cost_widget.h" #include "libcockatrice/card/database/card_database_querier.h" @@ -183,7 +182,7 @@ void CardCompleterDelegate::drawManaSymbol(QPainter *p, QPoint centre, const QSt p->setBrush(Qt::NoBrush); p->drawEllipse(pip.adjusted(1, 1, -1, -1)); - QFont f = QtUtils::boldFont(themeManager->appFont(AppFont::CardText)); + QFont f = QtUtils::boldFont(p->font()); f.setPixelSize(qMax(radius - 1, 7)); p->setFont(f); @@ -430,7 +429,7 @@ void CardCompleterDelegate::paint(QPainter *painter, const QStyleOptionViewItem const QRect nameRect = showInfoBand ? QRect(textLeft, rect.top() + 2, textWidth, 20) : QRect(textLeft, rect.top() + 2, textWidth, rect.height() - 4); - QFont f = themeManager->appFont(AppFont::CardText); + QFont f = option.font; f.setPixelSize(13); f.setBold(true); @@ -452,7 +451,7 @@ void CardCompleterDelegate::paint(QPainter *painter, const QStyleOptionViewItem if (showInfoBand) { const QRect infoRect(textLeft, rect.top() + 22, textWidth, rect.height() - 24); - QFont f = themeManager->appFont(AppFont::CardText); + QFont f = option.font; f.setPixelSize(10); painter->setFont(f); diff --git a/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp b/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp index 1a17bb8be..8f1941412 100644 --- a/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp +++ b/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp @@ -1,6 +1,5 @@ #include "card_completer_styler.h" -#include "../../interface/theme_manager.h" #include "../cards/card_info_picture_enlarged_widget.h" #include "card_completer_delegate.h" #include "libcockatrice/card/database/card_database_querier.h" @@ -74,10 +73,6 @@ CardCompleterStyler::CardCompleterStyler(QCompleter *completer, QObject *parent) popup->viewport()->setMouseTracking(true); - // The delegate reads the CardText role at paint time, so a font change - // only needs a repaint of the popup. - connect(themeManager, &ThemeManager::fontsChanged, popup, [popup] { popup->viewport()->update(); }); - popup->installEventFilter(this); popup->viewport()->installEventFilter(this); diff --git a/cockatrice/src/interface/widgets/utility/report_utils.cpp b/cockatrice/src/interface/widgets/utility/report_utils.cpp index df2400b14..d7fc083c6 100644 --- a/cockatrice/src/interface/widgets/utility/report_utils.cpp +++ b/cockatrice/src/interface/widgets/utility/report_utils.cpp @@ -1,6 +1,5 @@ #include "report_utils.h" -#include "../../theme_manager.h" #include "libcockatrice/protocol/pb/serverinfo_report.pb.h" #include @@ -8,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -21,20 +19,6 @@ namespace report_utils { -QFont reportLogFont() -{ - if (themeManager->hasAppFont(AppFont::ChatLog)) { - return themeManager->appFont(AppFont::ChatLog); - } - QFont monoFont(QStringLiteral("monospace")); - monoFont.setStyleHint(QFont::Monospace); - const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); - if (systemPointSize > 0) { - monoFont.setPointSize(systemPointSize); - } - return monoFont; -} - namespace { QColor reportStatusColor(const QString &status) diff --git a/cockatrice/src/interface/widgets/utility/report_utils.h b/cockatrice/src/interface/widgets/utility/report_utils.h index 32cc19eac..679989486 100644 --- a/cockatrice/src/interface/widgets/utility/report_utils.h +++ b/cockatrice/src/interface/widgets/utility/report_utils.h @@ -1,7 +1,6 @@ #ifndef REPORT_UTILS_H #define REPORT_UTILS_H -#include #include #include @@ -12,12 +11,6 @@ class ServerInfo_Report; namespace report_utils { -/** @brief Font for the report chat-log views: the theme's ChatLog role when it - * sets one, otherwise the monospace font these views used before the - * role existed. Shared by the three report surfaces so they cannot - * drift apart. */ -QFont reportLogFont(); - QString formatReportCategory(const QString &category); QString formatReportTime(qint64 secondsSinceEpoch); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp index d3c087bc6..76c3d0908 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp @@ -1,8 +1,5 @@ #include "visual_deck_editor_placeholder_widget.h" -#include "../interface/theme_manager.h" - -#include #include #include #include @@ -31,12 +28,10 @@ VisualDeckEditorPlaceholderWidget::VisualDeckEditorPlaceholderWidget(QWidget *pa textLabel->setStyleSheet(R"( QLabel { color: palette(mid); + font-size: 14px; padding: 10px; } )"); - applyInterfaceDefaultFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, - &VisualDeckEditorPlaceholderWidget::applyInterfaceDefaultFont); mainLayout->addWidget(imageLabel); mainLayout->addWidget(textLabel); @@ -47,12 +42,4 @@ VisualDeckEditorPlaceholderWidget::VisualDeckEditorPlaceholderWidget(QWidget *pa void VisualDeckEditorPlaceholderWidget::retranslateUi() { textLabel->setText(tr("Add cards using the search bar or database tab to have them appear here")); -} - -void VisualDeckEditorPlaceholderWidget::applyInterfaceDefaultFont() -{ - // Family comes from the InterfaceDefault theme role; the 14px size stays caller-side. - QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault); - placeholderFont.setPixelSize(14); - textLabel->setFont(placeholderFont); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h index 794be671e..84a3142d8 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h @@ -15,9 +15,6 @@ public: explicit VisualDeckEditorPlaceholderWidget(QWidget *parent = nullptr); void retranslateUi(); -private slots: - void applyInterfaceDefaultFont(); - private: QVBoxLayout *mainLayout; QLabel *imageLabel; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp index 30e99f401..82ed6191c 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp @@ -1,7 +1,5 @@ #include "deck_preview_tag_addition_widget.h" -#include "../../interface/theme_manager.h" - #include #include #include @@ -28,10 +26,6 @@ DeckPreviewTagAdditionWidget::DeckPreviewTagAdditionWidget(QWidget *_parent, QSt // Adjust widget size setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - - // The tag text reads the InterfaceDefault role at paint time, so a font - // change only needs a repaint. - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); }); } QSize DeckPreviewTagAdditionWidget::sizeHint() const @@ -71,9 +65,8 @@ void DeckPreviewTagAdditionWidget::paintEvent(QPaintEvent *event) painter.setPen(borderPen); painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width - // Calculate font size based on widget height. Family comes from the - // InterfaceDefault theme role; the height-derived size stays as before. - QFont font = themeManager->appFont(AppFont::InterfaceDefault); + // Calculate font size based on widget height + QFont font = painter.font(); int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10 font.setPointSize(fontSize); painter.setFont(font); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp index 34809da2e..e62570241 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp @@ -1,7 +1,5 @@ #include "deck_preview_tag_display_widget.h" -#include "../../interface/theme_manager.h" - #include #include #include @@ -43,10 +41,6 @@ DeckPreviewTagDisplayWidget::DeckPreviewTagDisplayWidget(QWidget *parent, const // Adjust widget size setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - // The tag text reads the InterfaceDefault role at paint time, so a font - // change only needs a repaint. - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); }); - // Connect close button to the remove signal // connect(closeButton, &QPushButton::clicked, this, &DeckPreviewTagDisplayWidget::tagClosed); } @@ -123,9 +117,8 @@ void DeckPreviewTagDisplayWidget::paintEvent(QPaintEvent *event) painter.setPen(borderPen); painter.drawRect(rect().adjusted(0, 0, -1, -1)); - // Calculate font size based on widget height. Family comes from the - // InterfaceDefault theme role; the height-derived size stays as before. - QFont font = themeManager->appFont(AppFont::InterfaceDefault); + // Calculate font size based on widget height + QFont font = painter.font(); int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10 font.setPointSize(fontSize); painter.setFont(font); diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 79a802234..d546676bc 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -55,14 +55,6 @@ #include "widgets/server/handle_public_servers.h" #include "widgets/settings_page/abstract_settings_page.h" #include "widgets/tabs/api/commander_spellbook/handle_commander_brackets.h" -#ifdef WITH_GUI_TEST -#include "../game_graphics/pq_cockatrice_event_player.h" -#include "pqEventObserver.h" -#include "pqEventSource.h" -#include "pqEventTypes.h" -#include "pqTestUtility.h" -#include "recording.h" -#endif #include #include @@ -318,7 +310,6 @@ void MainWindow::actAbout() mb.setIconPixmap( themePixmap(QStringLiteral("cockatrice")).scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); mb.setTextInteractionFlags(Qt::TextBrowserInteraction); - mb.setObjectName("aboutDialog"); mb.exec(); } @@ -357,24 +348,6 @@ void MainWindow::actOpenSettingsFolder() QtUtils::openPathInFileManager(dir); } -#ifdef WITH_GUI_TEST -void MainWindow::actRecord() -{ - QString filename = QFileDialog::getSaveFileName(this, tr("Test File Name"), QString(), tr("XML Files (*.xml)")); - if (!filename.isEmpty()) { - testUtility->recordTests(filename); - } -} - -void MainWindow::actPlayRecording() -{ - QString filename = QFileDialog::getOpenFileName(this, tr("Test File Name"), QString(), tr("XML Files (*.xml)")); - if (!filename.isEmpty()) { - testUtility->playTests(filename); - } -} -#endif - void MainWindow::retranslateUi() { connectionController->refreshWindowTitle(); @@ -419,10 +392,6 @@ void MainWindow::retranslateUi() aViewLog->setText(tr("View &Debug Log")); aOpenSettingsFolder->setText(tr("Open Settings Folder")); aFirstRunWizard->setText(tr("Re-run Onboarding Wizard...")); -#ifdef WITH_GUI_TEST - aRecord->setText(tr("&Record Test...")); - aPlayRecording->setText(tr("&Play Test...")); -#endif aShow->setText(tr("Show/Hide")); @@ -432,33 +401,24 @@ void MainWindow::retranslateUi() void MainWindow::createActions() { aConnect = new QAction(this); - aConnect->setObjectName("actionConnect"); connect(aConnect, &QAction::triggered, this, &MainWindow::actConnect); aDisconnect = new QAction(this); - aDisconnect->setObjectName("actionDisconnect"); aDisconnect->setEnabled(false); connect(aDisconnect, &QAction::triggered, this, &MainWindow::actDisconnect); aSinglePlayer = new QAction(this); - aSinglePlayer->setObjectName("actionSinglePlayer"); connect(aSinglePlayer, &QAction::triggered, this, &MainWindow::actSinglePlayer); aWatchReplay = new QAction(this); - aWatchReplay->setObjectName("actionWatchReplay"); connect(aWatchReplay, &QAction::triggered, this, &MainWindow::actWatchReplay); aFullScreen = new QAction(this); - aFullScreen->setObjectName("actionFullScreen"); aFullScreen->setCheckable(true); connect(aFullScreen, &QAction::toggled, this, &MainWindow::actFullScreen); aRegister = new QAction(this); - aRegister->setObjectName("actionRegister"); connect(aRegister, &QAction::triggered, connectionController, &ConnectionController::registerToServer); aForgotPassword = new QAction(this); - aForgotPassword->setObjectName("actionForgotPassword"); connect(aForgotPassword, &QAction::triggered, connectionController, &ConnectionController::forgotPasswordRequest); aSettings = new QAction(this); - aSettings->setObjectName("actionSettings"); connect(aSettings, &QAction::triggered, this, &MainWindow::actSettings); aExit = new QAction(this); - aExit->setObjectName("actionExit"); connect(aExit, &QAction::triggered, this, &MainWindow::actExit); aManageSets = new QAction(QString(), this); @@ -495,12 +455,6 @@ void MainWindow::createActions() connect(aOpenSettingsFolder, &QAction::triggered, this, &MainWindow::actOpenSettingsFolder); aFirstRunWizard = new QAction(this); connect(aFirstRunWizard, &QAction::triggered, this, [this] { runFirstRunWizard(); }); -#ifdef WITH_GUI_TEST - aRecord = new QAction(this); - connect(aRecord, &QAction::triggered, this, &MainWindow::actRecord); - aPlayRecording = new QAction(this); - connect(aPlayRecording, &QAction::triggered, this, &MainWindow::actPlayRecording); -#endif aShow = new QAction(this); connect(aShow, &QAction::triggered, this, &MainWindow::actShow); @@ -541,9 +495,7 @@ void MainWindow::createActions() void MainWindow::createMenus() { - menuBar()->setObjectName("menubar"); cockatriceMenu = menuBar()->addMenu(QString()); - cockatriceMenu->setObjectName("cockatriceMenu"); cockatriceMenu->addAction(aConnect); cockatriceMenu->addAction(aDisconnect); cockatriceMenu->addAction(aRegister); @@ -559,7 +511,6 @@ void MainWindow::createMenus() cockatriceMenu->addAction(aExit); dbMenu = menuBar()->addMenu(QString()); - dbMenu->setObjectName("dbMenu"); dbMenu->addAction(aManageSets); dbMenu->addAction(aEditTokens); dbMenu->addSeparator(); @@ -570,10 +521,8 @@ void MainWindow::createMenus() dbMenu->addAction(aReloadCardDatabase); tabsMenu = menuBar()->addMenu(QString()); - tabsMenu->setObjectName("tabsMenu"); helpMenu = menuBar()->addMenu(QString()); - helpMenu->setObjectName("helpMenu"); helpMenu->addAction(aAbout); helpMenu->addAction(aTips); helpMenu->addSeparator(); @@ -586,19 +535,12 @@ void MainWindow::createMenus() helpMenu->addAction(aOpenSettingsFolder); helpMenu->addSeparator(); helpMenu->addAction(aFirstRunWizard); -#ifdef WITH_GUI_TEST - helpMenu->addSeparator(); - helpMenu->addAction(aRecord); - helpMenu->addAction(aPlayRecording); -#endif } MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), localServer(nullptr), bHasActivated(false), askedForDbUpdater(false), noConnect(false), + : QMainWindow(parent), localServer(nullptr), bHasActivated(false), askedForDbUpdater(false), cardUpdateProcess(nullptr), logviewDialog(nullptr) { - setObjectName("MainWindow"); - connect(&SettingsCache::instance().cacheStorage(), &CacheStorageSettings::pixmapCacheSizeChanged, this, &MainWindow::pixmapCacheSizeChanged); pixmapCacheSizeChanged(SettingsCache::instance().cacheStorage().getPixmapCacheSize()); @@ -684,14 +626,6 @@ MainWindow::MainWindow(QWidget *parent) // run startup check async QTimer::singleShot(0, this, &MainWindow::startupConfigCheck); QTimer::singleShot(0, this, &MainWindow::applyStartupDestination); - -#ifdef WITH_GUI_TEST - testUtility = new pqTestUtility(this); - testUtility->setRecordWithDialog(true); - testUtility->addEventObserver("xml", new XMLEventObserver(this)); - testUtility->addEventSource("xml", new XMLEventSource(this)); - testUtility->eventPlayer()->addWidgetEventPlayer(new pqCockatriceEventPlayer(this)); -#endif } void MainWindow::startupConfigCheck() @@ -846,11 +780,6 @@ void MainWindow::runFirstRunWizard(bool firstRun) */ void MainWindow::applyStartupDestination() { - if (noConnect) { - qCInfo(WindowMainStartupAutoconnectLog) << "Startup destination suppressed (--no-connect)"; - return; - } - // An explicit command-line connect takes precedence over the startup destination. if (!connectTo.isEmpty()) { return; @@ -1120,10 +1049,6 @@ void MainWindow::handleCockatriceLink(const QString &url) void MainWindow::attemptStartupAutoConnect() { - if (noConnect) { - qCInfo(WindowMainStartupAutoconnectLog) << "Auto-connect suppressed (--no-connect)"; - return; - } if (startupAutoConnectAttempted || skipStartupAutoConnect) { return; } diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 48d093a38..9e342e4b0 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -48,7 +48,6 @@ class QAction; class QToolButton; class QWidget; -class pqTestUtility; struct LocalGameOptions; inline Q_LOGGING_CATEGORY(WindowMainLog, "window_main"); @@ -105,11 +104,6 @@ private slots: void actUpdate(); void actViewLog(); void actOpenSettingsFolder(); -#ifdef WITH_GUI_TEST - void actRecord(); - void actPlayRecording(); -#endif - void actShow(); void showWindowIfHidden(); void onUrlChainFinished(bool connected); @@ -191,9 +185,6 @@ private: *aReloadCardDatabase; QAction *aTips, *aUpdate, *aCheckCardUpdates, *aCheckCardUpdatesBackground, *aFirstRunWizard, *aStatusBar, *aViewLog, *aOpenSettingsFolder; -#ifdef WITH_GUI_TEST - QAction *aRecord, *aPlayRecording; -#endif TabSupervisor *tabSupervisor; IntentUrlParser *urlParser; @@ -217,7 +208,6 @@ private: bool skipStartupAutoConnect = false; bool startupAutoConnectAttempted = false; bool firstRunWizardActive = false; - bool noConnect = false; ///< --no-connect: skip every startup connection QProcess *cardUpdateProcess; QByteArray cardUpdateOutputBuffer; QByteArray cardUpdateErrorOutput; ///< diagnostic output collected from the updater's stderr @@ -229,9 +219,6 @@ private: GameReplay *replay; DlgTipOfTheDay *tip; QUrl connectTo; -#ifdef WITH_GUI_TEST - pqTestUtility *testUtility; -#endif public: explicit MainWindow(QWidget *parent = nullptr); @@ -239,16 +226,6 @@ public: { connectTo = QUrl(QString("cockatrice://%1").arg(url)); } - /** - * @brief Suppresses every startup connection: --connect, auto-connect and the - * configured startup destination. Used by the GUI test harness so a - * playback run never dials out. - */ - void setNoConnect(bool value) - { - noConnect = value; - } - // When set, the window's own startup connection (--connect or auto-connect // on first activation) is skipped. Used for activation launches: the intent // chain triggered by a cockatrice:// URL owns the connection, and letting @@ -289,13 +266,6 @@ public: */ void requestInstallerClose(); -#ifdef WITH_GUI_TEST - pqTestUtility *getTestUtility() const - { - return testUtility; - } -#endif - protected: void closeEvent(QCloseEvent *event) override; void changeEvent(QEvent *event) override; diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index b4dc0610f..829a8ac42 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -39,10 +39,6 @@ #include "single_instance_manager.h" #include "version_string.h" -#ifdef WITH_GUI_TEST -#include "pqTestUtility.h" -#endif - #include #include #include @@ -66,12 +62,10 @@ #include #include #include -#include #include #include #include #include -#include #include #include #include @@ -349,16 +343,9 @@ int main(int argc, char *argv[]) parser.addOptions( {{{"c", "connect"}, QCoreApplication::translate("main", "Connect on startup"), "user:pass@host:port"}, {{"d", "debug-output"}, QCoreApplication::translate("main", "Debug to file")}, - {"simulate-crash", QCoreApplication::translate( - "main", "Show the crash-report dialog (saves a mock file when no DSN is configured)")}, - {"no-connect", QCoreApplication::translate("main", "Do not connect to any server on startup")} -#ifdef WITH_GUI_TEST - , - {{"t", "test"}, - QCoreApplication::translate("main", "Play back a QtTesting XML test file and exit"), - "test-file.xml"} -#endif - }); + {"simulate-crash", + QCoreApplication::translate("main", + "Show the crash-report dialog (saves a mock file when no DSN is configured)")}}); parser.process(app); @@ -496,9 +483,6 @@ int main(int argc, char *argv[]) if (parser.isSet("connect")) { ui.setConnectTo(parser.value("connect")); } - if (parser.isSet("no-connect")) { - ui.setNoConnect(true); - } qCInfo(MainLog) << "MainWindow constructor finished"; ui.setWindowIcon(themePixmap(QStringLiteral("cockatrice"))); @@ -539,27 +523,6 @@ int main(int argc, char *argv[]) handleActivation(file); } -#ifdef WITH_GUI_TEST - if (parser.isSet("test")) { - QString testFile = parser.value("test"); - qCInfo(MainLog) << "Playing test file:" << testFile; - QTimer::singleShot(0, &ui, [&ui, testFile]() { - bool success = ui.getTestUtility()->playTests(testFile); - qCInfo(MainLog) << "Test playback" << (success ? "succeeded" : "FAILED"); - qApp->exit(success ? 0 : 1); - - // An event that fails while a modal dialog is open aborts the dispatcher, - // but the dialog's nested event loop can then keep the application alive - // forever. Hard-exit after a grace period so a red test always terminates. - auto *watchdog = QThread::create([success] { - QThread::sleep(10); - ::_Exit(success ? 0 : 1); - }); - watchdog->start(); - }); - } -#endif - // Connect to future file/URL events from other instances QObject::connect(&instance, &SingleInstanceManager::filesReceived, [&handleActivation](const QStringList &files) { for (const QString &file : files) { diff --git a/external/qttesting b/external/qttesting deleted file mode 160000 index c7bb5d372..000000000 --- a/external/qttesting +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c7bb5d372133e94c75cbdc70b04e039b864ea379 diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp index 636c603c6..fa75e356a 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp @@ -50,12 +50,11 @@ namespace // The server warns that this account still uses the legacy password hash while this client // holds a scrypt verifier. Sending the verifier would downgrade the proof, so the client // stops; the login only succeeds after the saved verifier is gone and the user types the -// password, which also upgrades the account. Unticking "Save password" drops the verifier -// for the next connection. +// password, which also upgrades the account. constexpr const char *const LOGIN_ABORT_AUTH_DOWNGRADE = QT_TRANSLATE_NOOP( "RemoteClient", "The server still uses an old password format for this account, so the saved login on this computer cannot be " - "used. Untick 'Save password' in the Connect window and log in with your password."); + "used. Untick 'Save password' in the Connect window and connect; when it opens again, type your password."); // The account is still on the legacy hash and only the plaintext password can produce it. constexpr const char *const LOGIN_ABORT_PASSWORD_REQUIRED = QT_TRANSLATE_NOOP("RemoteClient", diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c87bec627..6acd225c2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -178,18 +178,6 @@ cockatrice_add_test(NAME user_data_backup_test SOURCES user_data_backup_test.cpp 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 warning_categories_test SOURCES warning_categories_test.cpp LIBS libcockatrice_utility) -cockatrice_add_test( - NAME - user_games_model_test - SOURCES - ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/widgets/server/user/user_games_model.cpp - user_games_model_test.cpp - LIBS - libcockatrice_network_client_abstract - INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src - ${CMAKE_SOURCE_DIR}/libcockatrice_network -) # The installer's holder probe is Windows-only, but the wording it produces and the # question which programs are the payload's own are plain C++ and are what a stuck user # has to act on, so they are tested everywhere. @@ -424,15 +412,6 @@ cockatrice_add_test( INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/cockatrice/src ) -cockatrice_add_test( - NAME - font_config_test - SOURCES - ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/theme_config.cpp - font_config_test.cpp - INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src -) cockatrice_add_test( NAME deck_hash_performance_test diff --git a/tests/font_config_test.cpp b/tests/font_config_test.cpp deleted file mode 100644 index c8b1fb10d..000000000 --- a/tests/font_config_test.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "interface/theme_config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace -{ -// Writes content to a file inside tmp, creating parents as needed. -QString writeTempFile(QTemporaryDir &tmp, const QString &relativePath, const QString &content) -{ - const QString filePath = QDir(tmp.path()).absoluteFilePath(relativePath); - EXPECT_TRUE(QDir().mkpath(QFileInfo(filePath).absolutePath())); - QFile file(filePath); - EXPECT_TRUE(file.open(QIODevice::WriteOnly | QIODevice::Text)); - QTextStream out(&file); - out << content; - file.close(); - return filePath; -} - -QFont makeFont(const QString &family, int pointSize) -{ - QFont font(family); - font.setPointSize(pointSize); - return font; -} -} // namespace - -TEST(FontConfigTest, FileNameIsFontsToml) -{ - EXPECT_EQ(FontConfig::fileName(), QStringLiteral("fonts.toml")); -} - -TEST(FontConfigTest, EmptyByDefault) -{ - FontConfig cfg; - EXPECT_FALSE(cfg.hasFonts()); - EXPECT_TRUE(cfg.toToml().isEmpty()); - EXPECT_EQ(cfg, FontConfig()); - EXPECT_EQ(cfg, FontConfig::fromFile("/nonexistent/path/fonts.toml")); - EXPECT_EQ(cfg, FontConfig::fromThemeDir(QString())); -} - -TEST(FontConfigTest, RoundTripPreservesFamilyAndSize) -{ - QTemporaryDir tmp; - ASSERT_TRUE(tmp.isValid()); - - FontConfig cfg; - cfg.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10); - cfg.fonts[AppFont::ChatLog] = makeFont("JetBrains Mono", 9); - cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 11); - cfg.fonts[AppFont::DeckList] = makeFont("Roboto", 12); - ASSERT_TRUE(cfg.hasFonts()); - - const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); - const FontConfig loaded = FontConfig::fromFile(filePath); - - EXPECT_EQ(loaded, cfg); - EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault].family(), QStringLiteral("Inter")); - EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault].pointSize(), 10); - EXPECT_EQ(loaded.fonts[AppFont::ChatLog].family(), QStringLiteral("JetBrains Mono")); - EXPECT_EQ(loaded.fonts[AppFont::ChatLog].pointSize(), 9); - EXPECT_EQ(loaded.fonts[AppFont::CardText].family(), QStringLiteral("Noto Serif")); - EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 11); - EXPECT_EQ(loaded.fonts[AppFont::DeckList].family(), QStringLiteral("Roboto")); - EXPECT_EQ(loaded.fonts[AppFont::DeckList].pointSize(), 12); -} - -TEST(FontConfigTest, RoundTripStripsWeightAndItalic) -{ - QTemporaryDir tmp; - ASSERT_TRUE(tmp.isValid()); - - QFont styled("Inter"); - styled.setPointSize(10); - styled.setBold(true); - styled.setItalic(true); - - FontConfig cfg; - cfg.fonts[AppFont::ChatLog] = styled; - - const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); - const FontConfig loaded = FontConfig::fromFile(filePath); - - ASSERT_TRUE(loaded.fonts.contains(AppFont::ChatLog)); - EXPECT_EQ(loaded.fonts[AppFont::ChatLog].family(), QStringLiteral("Inter")); - EXPECT_EQ(loaded.fonts[AppFont::ChatLog].pointSize(), 10); - EXPECT_FALSE(loaded.fonts[AppFont::ChatLog].bold()); - EXPECT_FALSE(loaded.fonts[AppFont::ChatLog].italic()); - EXPECT_EQ(loaded.fonts[AppFont::ChatLog], makeFont("Inter", 10)); -} - -TEST(FontConfigTest, ToTomlFormat) -{ - FontConfig cfg; - cfg.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10); - - const QString toml = cfg.toToml(); - EXPECT_TRUE(toml.contains(QStringLiteral("[Fonts]"))); - EXPECT_TRUE(toml.contains(QStringLiteral("InterfaceDefault"))); - EXPECT_TRUE(toml.contains(QStringLiteral("\"Inter, 10\""))); -} - -TEST(FontConfigTest, MalformedLinesAreSkipped) -{ - QTemporaryDir tmp; - ASSERT_TRUE(tmp.isValid()); - - const QString filePath = writeTempFile(tmp, "fonts.toml", - "# a comment\n" - "; another comment\n" - "\n" - "StrayBeforeSection = \"Inter, 10\"\n" - "\n" - "[Other]\n" - "ChatLog = \"ShouldBeIgnored, 10\"\n" - "\n" - "[Fonts]\n" - "InterfaceDefault = \"Inter, 10\"\n" - "ChatLog = \"JetBrains Mono\", 9 # inline comment\n" - "CardText = Noto Serif, 11\n" - "AppFont::DeckList = \"Roboto, 12\"\n" - "UnknownRole = \"Inter, 10\"\n" - "QPalette::Window = \"Inter, 10\"\n" - "NoComma = \"Inter\"\n" - "EmptyFamily = \", 10\"\n" - "ZeroSize = \"Inter, 0\"\n" - "NegativeSize = \"Inter, -3\"\n" - "NotANumber = \"Inter, big\"\n" - "NotAnEntry\n"); - - const FontConfig loaded = FontConfig::fromFile(filePath); - - ASSERT_TRUE(loaded.hasFonts()); - EXPECT_EQ(loaded.fonts.size(), 4); - EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault], makeFont("Inter", 10)); - EXPECT_EQ(loaded.fonts[AppFont::ChatLog], makeFont("JetBrains Mono", 9)); - EXPECT_EQ(loaded.fonts[AppFont::CardText], makeFont("Noto Serif", 11)); - EXPECT_EQ(loaded.fonts[AppFont::DeckList], makeFont("Roboto", 12)); -} - -TEST(FontConfigTest, FromThemeDirReadsFontsToml) -{ - QTemporaryDir tmp; - ASSERT_TRUE(tmp.isValid()); - - const QString themeDir = QDir(tmp.path()).absoluteFilePath("MyTheme"); - ASSERT_TRUE(QDir().mkpath(themeDir)); - - FontConfig cfg; - cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 11); - writeTempFile(tmp, "MyTheme/fonts.toml", cfg.toToml()); - - const FontConfig loaded = FontConfig::fromThemeDir(themeDir); - EXPECT_EQ(loaded, cfg); - - // A theme directory without fonts.toml loads as empty. - const QString bareDir = QDir(tmp.path()).absoluteFilePath("BareTheme"); - ASSERT_TRUE(QDir().mkpath(bareDir)); - EXPECT_FALSE(FontConfig::fromThemeDir(bareDir).hasFonts()); -} - -TEST(FontConfigTest, InequalityComparesRoles) -{ - FontConfig left; - left.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10); - - FontConfig right = left; - EXPECT_EQ(left, right); - EXPECT_FALSE(left != right); - - right.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 11); - EXPECT_NE(left, right); - - right = left; - right.fonts[AppFont::ChatLog] = makeFont("Inter", 10); - EXPECT_NE(left, right); -} - -// Every test brings its own main(): GTest is not guaranteed to be found on the build machines, and -// the fallback then links the bare gtest library without gtest_main. -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/user_games_model_test.cpp b/tests/user_games_model_test.cpp deleted file mode 100644 index 33c29d6d4..000000000 --- a/tests/user_games_model_test.cpp +++ /dev/null @@ -1,248 +0,0 @@ -#include "interface/widgets/server/user/user_games_model.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -RNG_Abstract *rng = nullptr; // required by the transitive server library; this suite sends no game commands - -namespace -{ -class GamesClient : public AbstractClient -{ -public: - QList sent; - - GamesClient() - { - setStatus(StatusLoggedIn); - } - - ~GamesClient() override - { - qDeleteAll(pendingCommands); - pendingCommands.clear(); - } - - void changeStatus(ClientStatus status) - { - setStatus(status); - } - - void answer(int request, Response response) - { - response.set_cmd_id(sent.at(request).cmd_id()); - ServerMessage message; - message.set_message_type(ServerMessage::RESPONSE); - *message.mutable_response() = response; - processProtocolItem(message); - } - -protected: - void sendCommandContainer(const CommandContainer &command) override - { - sent.append(command); - } -}; - -Response gamesResponse(std::initializer_list ids) -{ - Response response; - response.set_response_code(Response::RespOk); - auto *games = response.MutableExtension(Response_GetGamesOfUser::ext); - for (int id : ids) { - auto *game = games->add_game_list(); - game->set_game_id(id); - game->set_room_id(7); - } - return response; -} - -using State = UserGamesModel::State; - -TEST(UserGamesModel, OfflineUserDoesNotSendARequest) -{ - GamesClient client; - UserGamesModel model(&client); - model.setUser("Alice", false); - model.refresh(); - EXPECT_TRUE(client.sent.isEmpty()); - EXPECT_EQ(State::Offline, model.getState()); - EXPECT_FALSE(model.canRefresh()); - EXPECT_EQ(0, model.rowCount()); -} - -TEST(UserGamesModel, PresenceLossInvalidatesTheOutstandingResponse) -{ - GamesClient client; - UserGamesModel model(&client); - model.setUser("Alice", true); - EXPECT_EQ(State::Loading, model.getState()); - EXPECT_FALSE(model.canRefresh()); - model.setUser("Alice", false); - client.answer(0, gamesResponse({1})); - EXPECT_EQ(State::Offline, model.getState()); - EXPECT_EQ(0, model.rowCount()); - - model.setUser("Alice", true); - client.answer(1, gamesResponse({2})); - EXPECT_EQ(State::Ready, model.getState()); - EXPECT_TRUE(model.canRefresh()); - EXPECT_EQ(2, model.data(model.index(0), UserGamesModel::GameRole).value().game_id()); -} - -TEST(UserGamesModel, SwitchingBackToTheSameUserDoesNotAcceptTheirOldRequest) -{ - GamesClient client; - UserGamesModel model(&client); - model.setUser("Alice", true); - model.setUser("Bob", true); - model.setUser("Alice", true); - client.answer(2, gamesResponse({3})); - client.answer(0, gamesResponse({1})); - client.answer(1, gamesResponse({2})); - EXPECT_EQ(1, model.rowCount()); - EXPECT_EQ(3, model.data(model.index(0), UserGamesModel::GameRole).value().game_id()); -} - -TEST(UserGamesModel, RefreshSupersedesThePreviousRequest) -{ - GamesClient client; - UserGamesModel model(&client); - model.setUser("Alice", true); - model.refresh(); - client.answer(0, gamesResponse({1})); - EXPECT_EQ(State::Loading, model.getState()); - EXPECT_EQ(0, model.rowCount()); - client.answer(1, gamesResponse({2})); - EXPECT_EQ(State::Ready, model.getState()); - EXPECT_EQ(2, model.data(model.index(0), UserGamesModel::GameRole).value().game_id()); -} - -TEST(UserGamesModel, EmptySuccessAndMissingPayloadAreDifferentStates) -{ - GamesClient client; - UserGamesModel model(&client); - model.setUser("Alice", true); - client.answer(0, gamesResponse({})); - EXPECT_EQ(State::Empty, model.getState()); - EXPECT_TRUE(model.canRefresh()); - - model.refresh(); - Response missingPayload; - missingPayload.set_response_code(Response::RespOk); - client.answer(1, missingPayload); - EXPECT_EQ(State::Error, model.getState()); - EXPECT_TRUE(model.canRefresh()); -} - -TEST(UserGamesModel, FailedRequestsCanBeRetried) -{ - GamesClient client; - UserGamesModel model(&client); - model.setUser("Alice", true); - Response error; - error.set_response_code(Response::RespNameNotFound); - client.answer(0, error); - EXPECT_EQ(State::Error, model.getState()); - model.refresh(); - client.answer(1, gamesResponse({4})); - EXPECT_EQ(State::Ready, model.getState()); -} - -TEST(UserGamesModel, DisconnectRejectsLateRepliesAndReconnectFetchesAgain) -{ - GamesClient client; - UserGamesModel model(&client); - model.setUser("Alice", true); - client.changeStatus(StatusDisconnecting); - client.answer(0, gamesResponse({1})); - EXPECT_EQ(State::Disconnected, model.getState()); - EXPECT_FALSE(model.canRefresh()); - model.refresh(); - EXPECT_EQ(1, client.sent.size()); - client.changeStatus(StatusLoggedIn); - client.answer(1, gamesResponse({2})); - EXPECT_EQ(State::Ready, model.getState()); -} - -TEST(UserGamesModel, ClientDestructionClearsGamesAndDisablesRefresh) -{ - auto client = std::make_unique(); - UserGamesModel model(client.get()); - model.setUser("Alice", true); - client->answer(0, gamesResponse({1})); - client.reset(); - EXPECT_EQ(State::Disconnected, model.getState()); - EXPECT_EQ(0, model.rowCount()); - EXPECT_FALSE(model.canRefresh()); - model.refresh(); - EXPECT_EQ(State::Disconnected, model.getState()); -} - -TEST(UserGamesModel, ResetInvalidatesAnOpenGameActionAndPreservesTheModelContract) -{ - GamesClient client; - UserGamesModel model(&client); - QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); - model.setUser("Alice", true); - client.answer(0, gamesResponse({1, 2, 3})); - QPersistentModelIndex selected(model.index(1)); - EXPECT_TRUE(selected.isValid()); - model.refresh(); - EXPECT_FALSE(selected.isValid()); - EXPECT_EQ(0, model.rowCount()); - client.answer(1, gamesResponse({4})); - EXPECT_EQ(1, model.rowCount()); -} - -TEST(UserGamesModel, StateListenerCanSelectAnotherUserBeforeTheRequestIsSent) -{ - GamesClient client; - UserGamesModel model(&client); - bool switched = false; - QObject::connect(&model, &UserGamesModel::stateChanged, &model, [&] { - if (!switched && model.getState() == State::Loading) { - switched = true; - model.setUser("Bob", false); - } - }); - model.setUser("Alice", true); - EXPECT_TRUE(client.sent.isEmpty()); - EXPECT_EQ(State::Offline, model.getState()); -} - -TEST(UserGamesModel, TimedOutResponseCannotReplaceARetry) -{ - GamesClient client; - UserGamesModel model(&client, 50); - model.setUser("Alice", true); - QSignalSpy changes(&model, &UserGamesModel::stateChanged); - ASSERT_TRUE(changes.wait(1000)); - ASSERT_EQ(State::Error, model.getState()); - EXPECT_TRUE(model.canRefresh()); - model.refresh(); - client.answer(0, gamesResponse({1})); - EXPECT_EQ(State::Loading, model.getState()); - client.answer(1, gamesResponse({2})); - EXPECT_EQ(State::Ready, model.getState()); - EXPECT_EQ(2, model.data(model.index(0), UserGamesModel::GameRole).value().game_id()); -} -} // namespace - -int main(int argc, char **argv) -{ - QCoreApplication app(argc, argv); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -}