Compare commits

...

56 Commits

Author SHA1 Message Date
BruebachL
ffc55aff10 [VDE] Add shortcut to increment cards [Alt + LMB] (#6555)
Took 13 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-24 11:28:06 +01:00
tooomm
2b372c14e4 Docs: Use doxygen-awesome-css theme (#6512)
* add doxygen-theme-css submodule

* enable theme and disable not needed code references

* hide nav sync button

* css and cleanup

* Move comments to dedicated README to not fail config check
2026-01-24 11:22:43 +01:00
BruebachL
12b5525a2d [TabArchidekt] Cleaner filters, infinite scrolling, and a "go back button" (#6545)
* [TabArchidekt] Cleaner filters, infinite scrolling, and a "go back button"

Took 46 minutes

Took 5 seconds

* Fix infinite scroll triggering in detail view.

Took 25 minutes

Took 3 seconds

* Use setLabelText() so it's white

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-24 11:21:12 +01:00
RickyRister
3c48d92663 [DeckEditor] Show info in PrintingSelector dock when override printings enabled (#6554)
* don't hide printing selector dock

* extract warning message to separate file

* create printing disabled info widget
2026-01-24 02:20:16 -08:00
BruebachL
948ec9e042 [VDE] Accurately represent card amounts (#6547) 2026-01-23 08:47:08 -05:00
RickyRister
5a274fdbed [DeckListModel] Mark all cards in token zone as legal (#6551) 2026-01-23 02:35:36 -08:00
RickyRister
bfeb3a7ca9 [DeckListModel] Refactor to use forEachCard in legality check (#6550) 2026-01-23 01:05:25 -08:00
RickyRister
d363ec5154 [VDS] Fix crash when tab is opened before card database is loaded (#6553) 2026-01-23 09:58:46 +01:00
RickyRister
999733fc0f [VDE] Fix right click to remove card not working (#6549)
* fix typo

* fix crash
2026-01-23 09:18:05 +01:00
RickyRister
8d274c1924 [DeckListModel] Correctly refresh legality on add card (#6537) 2026-01-22 23:34:24 -05:00
RickyRister
2e1a0bec93 [CardInfo] Refactor: add getLegalityProp method (#6536) 2026-01-22 23:33:21 -05:00
RickyRister
39ddaa0c35 [VDS] Reload deck on hover if file has been modified since last load (#6507)
* add reload to DeckLoader

* [VDS] Reload deck on hover if file has been modified since last load

* fix version incompatibility
2026-01-22 23:31:39 -05:00
RickyRister
d9b9c79112 [VDS] Add option to hide color identity (#6533) 2026-01-19 00:38:48 -08:00
RickyRister
485d5a8b48 [DeckListModel] Fix exception precedence in legality check (#6535) 2026-01-19 00:28:13 -08:00
RickyRister
f7e71a0868 [DeckList] Add optional restrictToZone param to getZoneNodes (#6534) 2026-01-19 00:27:58 -08:00
RickyRister
af2995ba96 [VDS] Ignore tokens when calculating color identity (#6532) 2026-01-18 20:51:57 -05:00
BruebachL
f7ffcc58fe [Sample hand widget] Create container widget before declaring it as parent (#6530) 2026-01-17 12:18:13 -05:00
RickyRister
792f077071 [VDE] Use splitter in sample hand widget (#6528)
* [VDE] Use splitter in sample hand widget

* remove unused code
2026-01-16 17:23:41 -08:00
RickyRister
9c07c7a963 [TabGame] Automatically sync view menu actions (#6529) 2026-01-16 17:22:48 -08:00
RickyRister
d579c82cb9 [DeckLoader] Make save/load methods static (#6476)
* const

* [DeckLoader] make methods static

* use static methods

* add docs

* add docs
2026-01-16 13:20:36 -05:00
RickyRister
c7c7bf550a [TabGame] Don't create replay dock if not replay tab (#6527)
* [TabGame] Don't create replay dock if not replay tab

* use replayDock to determine if replay tab

* null check replayManager in dtor
2026-01-16 09:45:10 -08:00
RickyRister
84483c56d7 [TabDeckEditor] Generalize visibility filter and extract it to a separate file (#6526)
* create class

* use new class in old code
2026-01-16 10:12:46 -05:00
RickyRister
1b71519ec6 [VDE] Make sample hand widget look nicer (#6525) 2026-01-16 10:11:39 -05:00
RickyRister
154b9ace92 [TabDeckEditor] Move cardDatabase dock action to top of menu (#6523) 2026-01-16 10:10:36 -05:00
RickyRister
93f0715d02 [TabDeckEditor] Save cardDatabase dock size in settings (#6524) 2026-01-15 21:54:50 -05:00
RickyRister
57e6c91689 [TabDeckEditor] Automatically sync view menu actions (#6522) 2026-01-15 21:05:19 -05:00
github-actions[bot]
6213ccff48 Update translation files (#6521)
Co-authored-by: github-actions <github-actions@github.com>
2026-01-15 06:53:55 +01:00
BruebachL
c075deeb2d [Placeholder images] Update color. (#6519)
Took 19 minutes

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 14:47:27 +01:00
BruebachL
29f60c4a67 [VDE] Placeholder image for deck view if deck is empty (#6516)
* [VDE] A stab at things

Took 14 minutes

Took 10 minutes

Took 5 minutes

Took 4 minutes


Took 41 seconds

Took 10 minutes

Took 3 minutes

* [VDE] Use placeholder image for deck view if deck is empty.

Took 15 minutes

Took 9 seconds

Took 5 seconds

* Sort CMakeList correctly.

Took 35 seconds

Took 23 seconds

* Visibility updates got lost in the rebase.

Took 7 minutes

* Same treatment for printing selector.

Took 42 minutes

* Actually add file.

Took 4 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 14:41:54 +01:00
RickyRister
c553e15036 [TabDeckEditor] Fix bug in #6499 causing view menu actions to sometimes not work (#6518)
* remove a special case

* fix
2026-01-14 14:20:12 +01:00
BruebachL
a4eef648bc [VDD] Move main type and format filter to quick settings (#6511)
* [VDD] Reorder quick filters

Took 1 hour 10 minutes

Took 5 seconds


Took 49 seconds

* [VDD] Use Font Awesome Icons

Took 49 minutes

Took 5 seconds

* [VDD] Shuffle some widgets around, label things.

Took 31 minutes

Took 5 seconds

* Change buttons to be push rather than toggle.

Took 17 minutes

Took 9 seconds

* Reduce margins, retranslate button texts.

Took 15 minutes

Took 9 seconds

* Actually do it, don't commit the commented out testing version lol

Took 3 minutes

* Start sets in include, correct subtype include/exact match logic.

Took 12 minutes

* Block sync.

Took 16 minutes

Took 8 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 11:56:09 +01:00
RickyRister
47720ff286 [ColorIdentityWidget] Refactor (#6506)
* [ColorIdentityWidget] Refactor and add setter

* rename manaCost field

* nvm, just refactor for now

* use QtUtils

* move clearLayout into populate

* add back cardInfo constructor
2026-01-14 02:41:03 -08:00
BruebachL
289b139be9 [DeckAnalytics] Enforce WUBRGC ordering for analytics. (#6509)
* [DeckAnalytics] Enforce WUBRGC ordering for analytics.

Took 6 minutes

Took 7 seconds

* Include QSet

Took 51 seconds

* Move include out of namespace.

Took 6 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 11:25:45 +01:00
RickyRister
21d60ec3f1 Reduce padding in settings popup (#6504)
* Reduce padding in settings popup

* reduce padding in PrintingSelectors settings

* reduce padding in one of the VDD filter settings
2026-01-14 01:49:07 -08:00
BruebachL
ed1115f4c0 [HomeTab] Add setting to display card info in bottom right for non-theme backgrounds (#6513)
* [HomeTab] Add setting to display card info in bottom right for non-theme backgrounds

Took 43 minutes

Took 9 seconds

* [HomeTab] Also hide shuffle frequency setting on theme background source.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 10:05:45 +01:00
BruebachL
cc5e2ab10a [VDE] Change sort quick settings button icon from gear to sort arrow (#6514)
* [VDE] Change sort quick settings button icon from gear to sort arrow

Took 12 minutes

* Actually include the icon.

Took 4 minutes

Took 13 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-14 10:05:38 +01:00
RickyRister
b19312be70 [TabDeckEditor] Consolidate dockWidget management (#6499) 2026-01-14 09:48:26 +01:00
BruebachL
a0d1359860 [VDE] Minor cleanups, possibly fullscreen width-lock fix (#6438)
* Refactor some constructor things to their own methods.

* Saner size policies, no manual resize management.


Took 15 seconds

Took 23 seconds

* VDE doesn't need to manually resize either.

Took 6 minutes

* Add plate comments and re-order .cpp to be more structured.

Took 9 minutes

Took 30 seconds

* Add plate comments and re-order DeckCardZoneDisplay.cpp to be more structured

Took 7 minutes

Took 5 seconds

* Add plate comments and re-order CardGroupDisplayWidget.cpp to be more structured

Took 7 minutes

Took 4 minutes

* Include declaration.

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-13 09:08:03 +01:00
transifex-integration[bot]
52547bbfe8 Updates for project Cockatrice and language de (#6508)
* Translate oracle/oracle_en@source.ts in de

100% translated source file: 'oracle/oracle_en@source.ts'
on 'de'.

* Translate oracle/oracle_en@source.ts in de

100% translated source file: 'oracle/oracle_en@source.ts'
on 'de'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-01-11 14:41:16 +01:00
BruebachL
9ab398f08d [Doxygen] Add documentation for beta channel. (#6510)
* [Doxygen] Add documentation for beta channel.

Took 36 minutes

* [Doxygen] Use default theme.

Took 13 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-11 12:48:31 +01:00
RickyRister
0deaa9d9b4 [DeckEditor] Don't change widget focus when adding card (#6503) 2026-01-09 18:27:54 -08:00
RickyRister
7c7755b61d [VDE] Fix crash vy adding null check for card in PrintingSelector (#6500) 2026-01-06 22:41:40 -08:00
github-actions[bot]
6340c4a6b7 Update translation source strings (#6465) 2026-01-06 19:35:53 +01:00
RickyRister
0a2fdb05ad [VDS] Try to fix memory leak by properly parenting widgets (#6498)
* [VDS] Try to fix memory leak by properly parenting widgets

* format
2026-01-06 11:42:35 +01:00
dependabot[bot]
b86853b65c Bump actions/cache from 4 to 5 (#6496) 2026-01-05 21:18:21 +01:00
RickyRister
192dac0396 [DeckListModel] Consolidate methods and signals for card change (#6466) 2026-01-05 18:28:59 +01:00
RickyRister
85c9d8a9ff [DeckEditor] Fix tokens being added to maindeck (#6495) 2026-01-05 01:18:38 -08:00
RickyRister
ee2699413c [TabDeckEditor] Make card database a dock widget (#6472)
* [TabDeckEditor] Make card database a dock widget

* delete eventFilter implementation in abstract
2026-01-05 00:06:22 -08:00
RickyRister
d50297bbe6 [AnalyticsPanel] Use cogwheel icon for configure button (#6494) 2026-01-05 00:03:22 -08:00
RickyRister
489ce416c3 [VDS] Add search query option for comments (#6477) 2026-01-05 08:31:10 +01:00
RickyRister
731c487ccb [ServerGame] null check participant in getPlayer (#6493) 2026-01-05 01:43:40 -05:00
RickyRister
2d5e8deb75 [Server_AbstractParticipant] Rename bool getters (#6492)
* [Server_AbstractParticipant] Rename bool getters

* reformat
2026-01-05 00:34:32 -05:00
RickyRister
746f2af044 [DeckListModel] optimize by iterating over cardNodes instead of ExactCards (#6485)
* [DeckListModel] optimize by iterating over cardNodes instead of ExactCards

* fix build failure

* another optimization

* fix build failure
2026-01-03 19:19:04 -08:00
RickyRister
f16c552d97 [PrintingSelector] Don't refresh display if "bump cards to top" is off (#6486) 2026-01-04 01:08:39 +01:00
Bruno Alexandre Rosa
72a85b58cf ci: make fat qt libs thin (#6281)
* ci: strip fat qt binaries

* parallelize

* cache thin qt

* print libs

* change qt install dir in the action

* move qt install logic to separate job

* lookup only

* debug: show contents of QTDIR

* enableCrossOsArchive also when saving

* check one dir up

* change install dir

* keep debugging

* try deleting cache

* force delete cache

* pass gh_token

* pass missing params

* use api

* change cache key, disable cross os archive

* move job directly to steps

* add comments

* set cache param directly

* address comments

* fixup

* Update .ci/thin_macos_qtlib.sh

* resolve qt version

* move resolution to separate script

* use single line for run:

* improve error handling in new scripts

---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2026-01-04 01:00:05 +01:00
transifex-integration[bot]
b88a98b09a Translate cockatrice/cockatrice_en@source.ts in fr (#6488)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-01-03 18:58:59 +01:00
186 changed files with 62894 additions and 44451 deletions

View File

@@ -156,6 +156,18 @@ function ccachestatsverbose() {
# Compile
if [[ $RUNNER_OS == macOS ]]; then
# QTDIR is needed for macOS since we actually only use the cached thin Qt binaries instead of the install-qt-action,
# which sets a few environment variables
if QTDIR=$(find "$GITHUB_WORKSPACE/Qt" -depth -maxdepth 2 -name macos -type d -print -quit); then
echo "found QTDIR at $QTDIR"
else
echo "could not find QTDIR!"
exit 2
fi
# the qtdir is located at Qt/[qtversion]/macos
# we use find to get the first subfolder with the name "macos"
# this works independent of the qt version as there should be only one version installed on the runner at a time
export QTDIR
if [[ $TARGET_MACOS_VERSION ]]; then
# CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version

View File

@@ -0,0 +1,40 @@
#!/bin/bash
# This script is used to resolve the latest patch version of Qt using aqtinstall.
# It interprets wildcards to get the latest patch version. E.g. "6.6.*" -> "6.6.3".
# This script is meant to be used by the ci enironment.
# It uses the runner's GITHUB_OUTPUT env variable.
# Usage example: .ci/resolve_latest_aqt_qt_version.sh "6.6.*"
qt_spec=$1
if [[ ! $qt_spec ]]; then
echo "usage: $0 [version]"
exit 2
fi
# If version is already specific (no wildcard), use it as-is
if [[ $qt_spec != *"*" ]]; then
echo "version $qt_spec is already resolved"
echo "version=$qt_spec" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! hash aqt; then
echo "aqt could not be found, has aqtinstall been installed?"
exit 2
fi
# Resolve latest patch
if ! qt_resolved=$(aqt list-qt mac desktop --spec "$qt_spec" --latest-version); then
exit 1
fi
echo "resolved $qt_spec to $qt_resolved"
if [[ ! $qt_resolved ]]; then
echo "Error: Could not resolve Qt version for $qt_spec"
exit 1
fi
echo "version=$qt_resolved" >> "$GITHUB_OUTPUT"

25
.ci/thin_macos_qtlib.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
# The macos binaries from aqt are fat (universal), so we thin them to the target architecture to reduce the size of
# the packages and caches using lipo.
# This script is meant to be used by the ci enironment on macos runners only.
# It uses the runner's GITHUB_WORKSPACE env variable.
arch=$(uname -m)
nproc=$(sysctl -n hw.ncpu)
function thin() {
local libfile=$1
if [[ $(file -b --mime-type "$libfile") == application/x-mach-binary* ]]; then
echo "Processing $libfile"
lipo "$libfile" -thin "$arch" -output "$libfile"
fi
return 0
}
export -f thin # export to allow use in xargs
export arch
set -eo pipefail
echo "::group::Thinning Qt libraries to $arch using $nproc cores"
find "$GITHUB_WORKSPACE/Qt" -type f -print0 | xargs -0 -n1 -P"$nproc" -I{} bash -c "thin '{}'"
echo "::endgroup::"

View File

@@ -262,7 +262,6 @@ jobs:
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false # qt caches take too much space for macOS (1.1Gi)
cmake_generator: Ninja
use_ccache: 1
@@ -278,7 +277,6 @@ jobs:
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false
cmake_generator: Ninja
use_ccache: 1
@@ -294,7 +292,6 @@ jobs:
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false
cmake_generator: Ninja
use_ccache: 1
@@ -307,7 +304,6 @@ jobs:
qt_version: 6.6.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: false
cmake_generator: Ninja
use_ccache: 1
@@ -320,7 +316,6 @@ jobs:
artifact_name: Windows7-installer
qt_version: 5.15.*
qt_arch: win64_msvc2019_64
cache_qt: true
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
@@ -334,7 +329,6 @@ jobs:
qt_version: 6.6.*
qt_arch: win64_msvc2019_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
cache_qt: true
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
@@ -375,13 +369,57 @@ jobs:
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-
- name: Install Qt ${{matrix.qt_version}}
- name: Install aqtinstall
if: matrix.os == 'macOS'
run: pipx install aqtinstall
# Checking if there's a newer, uncached version of Qt available to install via aqtinstall
- name: Resolve latest Qt patch version
if: matrix.os == 'macOS'
id: resolve_qt_version
shell: bash
# Ouputs the version of Qt to install via aqtinstall
run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}"
- name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS'
id: restore_qt
uses: actions/cache/restore@v5
with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
# Using jurplel/install-qt-action to install Qt without using brew
# qt build using vcpkg either just fails or takes too long to build
- name: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
cache: false
version: ${{ steps.resolve_qt_version.outputs.version }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
dir: ${{github.workspace}}
- name: Thin Qt libraries (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh
- name: Cache thin Qt libraries (${{ matrix.soc }} macOS)
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
- name: Install Qt ${{matrix.qt_version}} (Windows)
if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4
with:
version: ${{matrix.qt_version}}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: ${{matrix.cache_qt}}
cache: true
- name: Setup vcpkg cache
id: vcpkg-cache

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg.git
[submodule "doxygen-awesome-css"]
path = doc/doxygen/theme
url = https://github.com/jothepro/doxygen-awesome-css.git

View File

@@ -54,7 +54,7 @@ PROJECT_NUMBER = $(COCKATRICE_REF)
# for a project that appears at the top of each page and should give viewers a
# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF = "A cross-platform virtual tabletop for multiplayer card games"
PROJECT_BRIEF = "A virtual tabletop for multiplayer card games"
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
@@ -1068,6 +1068,8 @@ RECURSIVE = YES
EXCLUDE = build/ \
cmake/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
vcpkg/ \
webclient/
@@ -1430,7 +1432,9 @@ HTML_STYLESHEET =
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET = doc/doxygen/css/doxygen_style.css
HTML_EXTRA_STYLESHEET = doc/doxygen/theme/doxygen-awesome.css \
doc/doxygen/css/hide_nav_sync.css \
doc/doxygen/css/cockatrice_docs_style.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@@ -1453,7 +1457,7 @@ HTML_EXTRA_FILES = doc/doxygen/js/graph_toggle.js
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = AUTO_DARK
HTML_COLORSTYLE = LIGHT
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
@@ -1764,7 +1768,7 @@ ECLIPSE_DOC_ID = org.doxygen.Project
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
DISABLE_INDEX = YES
DISABLE_INDEX = NO
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information. If the tag

View File

@@ -47,6 +47,7 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp
src/interface/widgets/dialogs/dlg_update.cpp
src/interface/widgets/dialogs/dlg_view_log.cpp
src/interface/widgets/dialogs/override_printing_warning.cpp
src/interface/widgets/dialogs/tip_of_the_day.cpp
src/filters/deck_filter_string.cpp
src/filters/filter_builder.cpp
@@ -170,6 +171,7 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
@@ -177,6 +179,7 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
src/interface/widgets/deck_editor/deck_state_manager.cpp
src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp
src/interface/widgets/general/background_sources.cpp
src/interface/widgets/general/display/background_plate_widget.cpp
src/interface/widgets/general/display/banner_widget.cpp
@@ -202,6 +205,7 @@ set(cockatrice_SOURCES
src/interface/widgets/printing_selector/printing_selector.cpp
src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_search_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
@@ -225,8 +229,11 @@ set(cockatrice_SOURCES
src/interface/widgets/utility/custom_line_edit.cpp
src/interface/widgets/utility/get_text_with_max.cpp
src/interface/widgets/utility/sequence_edit.cpp
src/interface/widgets/utility/visibility_change_listener.cpp
src/interface/widgets/utility/visibility_change_listener.h
src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp
@@ -235,6 +242,7 @@ set(cockatrice_SOURCES
src/interface/widgets/visual_database_display/visual_database_display_widget.cpp
src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp

View File

@@ -15,18 +15,23 @@
<file>resources/icons/arrow_top_green.svg</file>
<file>resources/icons/arrow_up_green.svg</file>
<file>resources/icons/arrow_undo.svg</file>
<file>resources/icons/circle_half_stroke.svg</file>
<file>resources/icons/clearsearch.svg</file>
<file>resources/icons/cogwheel.svg</file>
<file>resources/icons/conceded.svg</file>
<file>resources/icons/decrement.svg</file>
<file>resources/icons/delete.svg</file>
<file>resources/icons/dragon.svg</file>
<file>resources/icons/dropdown_collapsed.svg</file>
<file>resources/icons/dropdown_expanded.svg</file>
<file>resources/icons/floppy_disk.svg</file>
<file>resources/icons/forgot_password.svg</file>
<file>resources/icons/gear.svg</file>
<file>resources/icons/increment.svg</file>
<file>resources/icons/info.svg</file>
<file>resources/icons/lock.svg</file>
<file>resources/icons/not_ready_start.svg</file>
<file>resources/icons/pen_to_square.svg</file>
<file>resources/icons/pencil.svg</file>
<file>resources/icons/pin.svg</file>
<file>resources/icons/player.svg</file>
@@ -34,10 +39,13 @@
<file>resources/icons/reload.svg</file>
<file>resources/icons/remove_row.svg</file>
<file>resources/icons/rename.svg</file>
<file>resources/icons/scale_balanced.svg</file>
<file>resources/icons/scales.svg</file>
<file>resources/icons/scroll.svg</file>
<file>resources/icons/search.svg</file>
<file>resources/icons/settings.svg</file>
<file>resources/icons/share.svg</file>
<file>resources/icons/sort_arrow_down.svg</file>
<file>resources/icons/spectator.svg</file>
<file>resources/icons/swap.svg</file>
<file>resources/icons/sync.svg</file>
@@ -52,6 +60,8 @@
<file>resources/icons/mana/W.svg</file>
<file>resources/backgrounds/home.png</file>
<file>resources/backgrounds/card_triplet.svg</file>
<file>resources/backgrounds/placeholder_printing_selector.svg</file>
<file>resources/config/general.svg</file>
<file>resources/config/appearance.svg</file>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="250"
id="svg13"
height="231.66667"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg">
<defs
id="defs13" />
<g
transform="matrix(1.705559,0,0,1.705559,-18.310328,-4.2419088)"
id="g13">
<path
d="M 90.069854,3.479957 C 89.356513,1.2235709 86.980392,-0.01102897 84.723451,0.70218215 L 3.4767601,26.377781 C 1.2199188,27.090982 -0.01486587,29.46663 0.69839437,31.723116 L 33.512365,135.52112 c 0.713341,2.25639 3.089462,3.49099 5.346403,2.77777 l 81.246672,-25.6756 c 2.25684,-0.71319 3.49163,-3.08884 2.77837,-5.34533 L 90.074852,3.479957 Z"
style="display:none;fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path1" />
<path
d="m 110.61293,7.4983294 c -0.36657,-2.337853 -2.53055,-3.9150142 -4.86886,-3.5484627 L 21.563382,17.14452 c -2.338314,0.366502 -3.915784,2.529976 -3.549207,4.867929 L 34.876507,129.55893 c 0.366577,2.33786 2.530549,3.91502 4.868863,3.54847 l 84.18069,-13.19466 c 2.33831,-0.3665 3.91578,-2.52997 3.5492,-4.86793 L 110.61093,7.4983294 Z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path4" />
<path
d="m 130.53623,15.555064 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 41.067426 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 V 124.41102 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208344 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7" />
<path
d="m 149.43988,26.480639 c 0.38018,-2.335754 -1.1846,-4.508374 -3.52082,-4.88852 L 61.817351,7.9076636 C 59.481136,7.5275576 57.308066,9.0920839 56.927894,11.427736 L 39.439773,118.87426 c -0.380182,2.33576 1.184602,4.50838 3.520816,4.88852 l 84.102711,13.68346 c 2.33622,0.38011 4.50929,-1.18442 4.88946,-3.52007 L 149.43688,26.479639 Z"
style="display:inline;fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
<svg
version="1.1"
width="172.65051"
id="svg13"
height="213.30714"
xml:space="preserve"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"><defs
id="defs13" /><g
transform="matrix(1.705559,0,0,1.705559,-97.653345,-68.741256)"
id="g13"><path
d="m 151.48519,45.063813 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 62.016385 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 V 153.91977 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208345 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7" /><path
d="m 154.70135,48.441704 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 65.232545 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 V 157.29767 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208345 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7-5" /><path
d="m 157.98403,51.75453 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 68.515228 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 v 108.85596 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208342 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7-6" /></g><path
d="m 196.24576,207.42361 c 0,0.11213 0,0.22413 0,0.33621 -0.0498,4.54511 -4.18399,7.63329 -8.72909,7.63329 h -12.19086 c -3.29988,0 -5.97713,2.67727 -5.97713,5.97712 0,0.4234 0.0498,0.83433 0.12449,1.23279 0.26149,1.27014 0.80939,2.49046 1.34485,3.72325 0.75959,1.71843 1.50674,3.4244 1.50674,5.22998 0,3.95986 -2.68971,7.55859 -6.64956,7.72046 -0.43583,0.0128 -0.87166,0.025 -1.31995,0.025 -17.60761,0 -31.878,-14.27038 -31.878,-31.878 0,-17.60761 14.28284,-31.878 31.89046,-31.878 17.60762,0 31.87801,14.27039 31.87801,31.878 z m -47.81703,3.98475 c 0,-2.20407 -1.78067,-3.98475 -3.98473,-3.98475 -2.20407,0 -3.98477,1.78068 -3.98477,3.98475 0,2.20406 1.7807,3.98475 3.98477,3.98475 2.20406,0 3.98473,-1.78069 3.98473,-3.98475 z m 0,-11.95426 c 2.20407,0 3.98477,-1.78068 3.98477,-3.98475 0,-2.20407 -1.7807,-3.98475 -3.98477,-3.98475 -2.20405,0 -3.98473,1.78068 -3.98473,3.98475 0,2.20407 1.78068,3.98475 3.98473,3.98475 z m 19.92376,-11.95424 c 0,-2.20408 -1.78068,-3.98477 -3.98473,-3.98477 -2.20407,0 -3.98477,1.78069 -3.98477,3.98477 0,2.20405 1.7807,3.98474 3.98477,3.98474 2.20405,0 3.98473,-1.78069 3.98473,-3.98474 z m 11.95426,11.95424 c 2.20407,0 3.98475,-1.78068 3.98475,-3.98475 0,-2.20407 -1.78068,-3.98475 -3.98475,-3.98475 -2.20406,0 -3.98475,1.78068 -3.98475,3.98475 0,2.20407 1.78069,3.98475 3.98475,3.98475 z"
id="path1"
style="display:none;fill:#3b3b3b;fill-opacity:1;stroke:#000000;stroke-width:2.53798;stroke-dasharray:none;stroke-opacity:1" /><path
d="M 126.20915,54.574783 82.324247,83.8512 c -5.76807,3.845383 -9.435059,10.089163 -10.029703,16.90777 12.348823,2.53716 22.081191,12.26955 24.638191,24.63819 6.838435,-0.59465 13.062395,-4.26163 16.907775,-10.0297 l 29.2566,-43.904722 c 1.32804,-2.001984 2.04162,-4.340923 2.04162,-6.75915 0,-6.719506 -5.45092,-12.170428 -12.17043,-12.170428 -2.3984,0 -4.75718,0.713573 -6.75915,2.041623 z M 88.052677,131.81933 c 0,-12.26953 -9.930593,-22.20012 -22.200138,-22.20012 -12.269532,0 -22.200126,9.93059 -22.200126,22.20012 0,0.77305 0.03966,1.54609 0.118929,2.2993 0.356787,3.46877 -2.021792,7.21505 -5.510393,7.21505 h -0.951431 c -3.508409,0 -6.342895,2.83447 -6.342895,6.3429 0,3.5084 2.834486,6.34289 6.342895,6.34289 h 28.543021 c 12.269545,0 22.200138,-9.93059 22.200138,-22.20014 z"
id="path1-2"
style="fill:#989898;fill-opacity:1;stroke-width:0.198215" /></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -29,6 +29,11 @@ searches are case insensitive.
<dt><u>F</u>ormat:</dt>
<dd>[f:standard](#f:standard) <small>(Any deck with format set to standard)</small></dd>
<dt><u>C</u>omments:</dt>
<dd>[c:good](#c:good) <small>(Any deck with comments containing the word good)</small></dd>
<dd>[c:good c:deck](#c:good c:deck) <small>(Any deck with comments containing the words good and deck)</small></dd>
<dd>[c:"good deck"](#c:%22good deck%22) <small>(Any deck with comments containing the exact phrase "good deck")</small></dd>
<dt>Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):</dt>
<dd><a href="#[[plains]]">[[plains]]</a> <small>(Any deck that contains at least one card with "plains" in its name)</small></dd>
<dd><a href="#[[t:legendary]]">[[t:legendary]]</a> <small>(Any deck that contains at least one legendary)</small></dd>
@@ -44,4 +49,4 @@ searches are case insensitive.
<dt>Grouping:</dt>
<dd><a href="#red -([[]]:100 or aggro)">red -([[]]:100 or aggro)</a> <small>(Any deck that has red in its filename but is not 100 cards or has aggro in its filename)</small></dd>
</dl>
</dl>

View File

@@ -67,4 +67,4 @@ In this list of examples below, each entry has an explanation and can be clicked
<dd>[o:/counter target .* spell/](#o:/counter target .* spell/) <small>(Any card text with "counter target *something* spell")</small></dd>
<dd>[o:/for each .* and\/or .*/](#o:/for each .* and\/or .*/) <small>(/'s can be escaped with a \)</small></dd>
</dl>
</dl>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M512 320C512 214 426 128 320 128L320 512C426 512 512 426 512 320zM64 320C64 178.6 178.6 64 320 64C461.4 64 576 178.6 576 320C576 461.4 461.4 576 320 576C178.6 576 64 461.4 64 320z"/></svg>

After

Width:  |  Height:  |  Size: 410 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M352 188.5L300.1 175.5C293.6 173.9 288.8 168.4 288.1 161.7C287.4 155 290.9 148.6 296.8 145.6L337.6 125.2L294.3 92.7C288.8 88.6 286.5 81.4 288.7 74.8C290.9 68.2 297.1 64 304 64L464 64C494.2 64 522.7 78.2 540.8 102.4L598.4 179.2C604.6 187.5 608 197.6 608 208C608 234.5 586.5 256 560 256L538.5 256C521.5 256 505.2 249.3 493.2 237.3L479.9 224L447.9 224L447.9 245.5C447.9 270.3 460.7 293.4 481.7 306.6L588.3 373.2C620.4 393.3 639.9 428.4 639.9 466.3C639.9 526.9 590.8 576.1 530.1 576.1L32.3 576C29 576 25.7 575.6 22.7 574.6C13.5 571.8 6 565 2.3 556C1 552.7 .1 549.1 0 545.3C-.2 541.6 .3 538 1.3 534.6C4.1 525.4 10.9 517.9 19.9 514.2C22.9 513 26.1 512.2 29.4 512L433.3 476C441.6 475.3 448 468.3 448 459.9C448 455.6 446.3 451.5 443.3 448.5L398.9 404.1C368.9 374.1 352 333.4 352 291L352 188.5zM512 136.3C512 136.2 512 136.1 512 136C512 135.9 512 135.8 512 135.7L512 136.3zM510.7 143.7L464.3 132.1C464.1 133.4 464 134.7 464 136C464 149.3 474.7 160 488 160C498.6 160 507.5 153.2 510.7 143.7zM130.9 180.5C147.2 166 171.3 164.3 189.4 176.4L320 263.4L320 290.9C320 323.7 328.4 355.7 344 383.9L112 383.9C105.3 383.9 99.3 379.7 97 373.5C94.7 367.3 96.5 360.2 101.6 355.8L171 296.3L18.4 319.8C11.4 320.9 4.5 317.2 1.5 310.8C-1.5 304.4 .1 296.8 5.4 292L130.9 180.5z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 237.3C544 220.3 537.3 204 525.3 192L448 114.7C436 102.7 419.7 96 402.7 96L160 96zM192 192C192 174.3 206.3 160 224 160L384 160C401.7 160 416 174.3 416 192L416 256C416 273.7 401.7 288 384 288L224 288C206.3 288 192 273.7 192 256L192 192zM320 352C355.3 352 384 380.7 384 416C384 451.3 355.3 480 320 480C284.7 480 256 451.3 256 416C256 380.7 284.7 352 320 352z"/></svg>

After

Width:  |  Height:  |  Size: 693 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M535.6 85.7C513.7 63.8 478.3 63.8 456.4 85.7L432 110.1L529.9 208L554.3 183.6C576.2 161.7 576.2 126.3 554.3 104.4L535.6 85.7zM236.4 305.7C230.3 311.8 225.6 319.3 222.9 327.6L193.3 416.4C190.4 425 192.7 434.5 199.1 441C205.5 447.5 215 449.7 223.7 446.8L312.5 417.2C320.7 414.5 328.2 409.8 334.4 403.7L496 241.9L398.1 144L236.4 305.7zM160 128C107 128 64 171 64 224L64 480C64 533 107 576 160 576L416 576C469 576 512 533 512 480L512 384C512 366.3 497.7 352 480 352C462.3 352 448 366.3 448 384L448 480C448 497.7 433.7 512 416 512L160 512C142.3 512 128 497.7 128 480L128 224C128 206.3 142.3 192 160 192L256 192C273.7 192 288 177.7 288 160C288 142.3 273.7 128 256 128L160 128z"/></svg>

After

Width:  |  Height:  |  Size: 899 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M384 96L512 96C529.7 96 544 110.3 544 128C544 145.7 529.7 160 512 160L398.4 160C393.2 185.8 375.5 207.1 352 217.3L352 512L512 512C529.7 512 544 526.3 544 544C544 561.7 529.7 576 512 576L128 576C110.3 576 96 561.7 96 544C96 526.3 110.3 512 128 512L288 512L288 217.3C264.5 207 246.8 185.7 241.6 160L128 160C110.3 160 96 145.7 96 128C96 110.3 110.3 96 128 96L256 96C270.6 76.6 293.8 64 320 64C346.2 64 369.4 76.6 384 96zM439.6 384L584.4 384L512 259.8L439.6 384zM512 480C449.1 480 396.8 446 386 401.1C383.4 390.1 387 378.8 392.7 369L487.9 205.8C492.9 197.2 502.1 192 512 192C521.9 192 531.1 197.3 536.1 205.8L631.3 369C637 378.8 640.6 390.1 638 401.1C627.2 445.9 574.9 480 512 480zM126.8 259.8L54.4 384L199.3 384L126.8 259.8zM.9 401.1C-1.7 390.1 1.9 378.8 7.6 369L102.8 205.8C107.8 197.2 117 192 126.9 192C136.8 192 146 197.3 151 205.8L246.2 369C251.9 378.8 255.5 390.1 252.9 401.1C242.1 445.9 189.8 480 126.9 480C64 480 11.7 446 .9 401.1z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M32 176C32 134.5 63.6 100.4 104 96.4L104 96L384 96C437 96 480 139 480 192L480 368L304 368C264.2 368 232 400.2 232 440L232 500C232 524.3 212.3 544 188 544C163.7 544 144 524.3 144 500L144 272L80 272C53.5 272 32 250.5 32 224L32 176zM268.8 544C275.9 530.9 280 515.9 280 500L280 440C280 426.7 290.7 416 304 416L552 416C565.3 416 576 426.7 576 440L576 464C576 508.2 540.2 544 496 544L268.8 544zM112 144C94.3 144 80 158.3 80 176L80 224L144 224L144 176C144 158.3 129.7 144 112 144z"/></svg>

After

Width:  |  Height:  |  Size: 704 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M278.6 438.6L182.6 534.6C170.1 547.1 149.8 547.1 137.3 534.6L41.3 438.6C28.8 426.1 28.8 405.8 41.3 393.3C53.8 380.8 74.1 380.8 86.6 393.3L128 434.7L128 128C128 110.3 142.3 96 160 96C177.7 96 192 110.3 192 128L192 434.7L233.4 393.3C245.9 380.8 266.2 380.8 278.7 393.3C291.2 405.8 291.2 426.1 278.7 438.6zM352 544C334.3 544 320 529.7 320 512C320 494.3 334.3 480 352 480L384 480C401.7 480 416 494.3 416 512C416 529.7 401.7 544 384 544L352 544zM352 416C334.3 416 320 401.7 320 384C320 366.3 334.3 352 352 352L448 352C465.7 352 480 366.3 480 384C480 401.7 465.7 416 448 416L352 416zM352 288C334.3 288 320 273.7 320 256C320 238.3 334.3 224 352 224L512 224C529.7 224 544 238.3 544 256C544 273.7 529.7 288 512 288L352 288zM352 160C334.3 160 320 145.7 320 128C320 110.3 334.3 96 352 96L576 96C593.7 96 608 110.3 608 128C608 145.7 593.7 160 576 160L352 160z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -239,6 +239,7 @@ SettingsCache::SettingsCache()
homeTabBackgroundSource = settings->value("home/background", "themed").toString();
homeTabBackgroundShuffleFrequency = settings->value("home/background/shuffleTimer", 0).toInt();
homeTabDisplayCardName = settings->value("home/background/displayCardName", true).toBool();
tabVisualDeckStorageOpen = settings->value("tabs/visualDeckStorage", true).toBool();
tabServerOpen = settings->value("tabs/server", true).toBool();
@@ -309,6 +310,7 @@ SettingsCache::SettingsCache()
visualDeckStorageDefaultTagsList =
settings->value("interface/visualdeckstoragedefaulttagslist", defaultTags).toStringList();
visualDeckStorageSearchFolderNames = settings->value("interface/visualdeckstoragesearchfoldernames", true).toBool();
visualDeckStorageShowColorIdentity = settings->value("interface/visualdeckstorageshowcoloridentity", true).toBool();
visualDeckStorageShowBannerCardComboBox =
settings->value("interface/visualdeckstorageshowbannercardcombobox", true).toBool();
visualDeckStorageShowTagsOnDeckPreviews =
@@ -594,6 +596,13 @@ void SettingsCache::setHomeTabBackgroundShuffleFrequency(int _frequency)
emit homeTabBackgroundShuffleFrequencyChanged();
}
void SettingsCache::setHomeTabDisplayCardName(QT_STATE_CHANGED_T _displayCardName)
{
homeTabDisplayCardName = static_cast<bool>(_displayCardName);
settings->setValue("home/background/displayCardName", homeTabDisplayCardName);
emit homeTabDisplayCardNameChanged();
}
void SettingsCache::setTabVisualDeckStorageOpen(bool value)
{
tabVisualDeckStorageOpen = value;
@@ -821,6 +830,13 @@ void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T val
settings->setValue("interface/visualdeckstoragesearchfoldernames", visualDeckStorageSearchFolderNames);
}
void SettingsCache::setVisualDeckStorageShowColorIdentity(QT_STATE_CHANGED_T value)
{
visualDeckStorageShowColorIdentity = value;
settings->setValue("interface/visualdeckstorageshowcoloridentity", visualDeckStorageShowColorIdentity);
emit visualDeckStorageShowColorIdentityChanged(visualDeckStorageShowColorIdentity);
}
void SettingsCache::setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox)
{
visualDeckStorageShowBannerCardComboBox = _showBannerCardComboBox;

View File

@@ -143,6 +143,7 @@ signals:
void themeChanged();
void homeTabBackgroundSourceChanged();
void homeTabBackgroundShuffleFrequencyChanged();
void homeTabDisplayCardNameChanged();
void picDownloadChanged();
void showStatusBarChanged(bool state);
void showGameSelectorFilterToolbarChanged(bool state);
@@ -157,6 +158,7 @@ signals:
void deckEditorTagsWidgetVisibleChanged(bool _visible);
void visualDeckStorageShowTagFilterChanged(bool _visible);
void visualDeckStorageDefaultTagsListChanged();
void visualDeckStorageShowColorIdentityChanged(bool _visible);
void visualDeckStorageShowBannerCardComboBoxChanged(bool _visible);
void visualDeckStorageShowTagsOnDeckPreviewsChanged(bool _visible);
void visualDeckStorageCardSizeChanged();
@@ -222,6 +224,7 @@ private:
bool showTipsOnStartup;
QList<int> seenTips;
int homeTabBackgroundShuffleFrequency;
bool homeTabDisplayCardName;
bool mbDownloadSpoilers;
int updateReleaseChannel;
int maxFontSize;
@@ -249,6 +252,7 @@ private:
bool deckEditorTagsWidgetVisible;
int visualDeckStorageSortingOrder;
bool visualDeckStorageShowFolders;
bool visualDeckStorageShowColorIdentity;
bool visualDeckStorageShowBannerCardComboBox;
bool visualDeckStorageShowTagsOnDeckPreviews;
bool visualDeckStorageShowTagFilter;
@@ -413,6 +417,10 @@ public:
{
return homeTabBackgroundShuffleFrequency;
}
[[nodiscard]] bool getHomeTabDisplayCardName() const
{
return homeTabDisplayCardName;
}
[[nodiscard]] bool getTabVisualDeckStorageOpen() const
{
return tabVisualDeckStorageOpen;
@@ -615,6 +623,10 @@ public:
{
return visualDeckStorageSearchFolderNames;
}
[[nodiscard]] bool getVisualDeckStorageShowColorIdentity() const
{
return visualDeckStorageShowColorIdentity;
}
[[nodiscard]] bool getVisualDeckStorageShowBannerCardComboBox() const
{
return visualDeckStorageShowBannerCardComboBox;
@@ -1001,6 +1013,7 @@ public slots:
void setThemeName(const QString &_themeName);
void setHomeTabBackgroundSource(const QString &_backgroundSource);
void setHomeTabBackgroundShuffleFrequency(int _frequency);
void setHomeTabDisplayCardName(QT_STATE_CHANGED_T _displayCardName);
void setTabVisualDeckStorageOpen(bool value);
void setTabServerOpen(bool value);
void setTabAccountOpen(bool value);
@@ -1038,6 +1051,7 @@ public slots:
void setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags);
void setVisualDeckStorageDefaultTagsList(QStringList _defaultTagsList);
void setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T value);
void setVisualDeckStorageShowColorIdentity(QT_STATE_CHANGED_T value);
void setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox);
void setVisualDeckStorageShowTagsOnDeckPreviews(QT_STATE_CHANGED_T _showTags);
void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize);
@@ -1110,5 +1124,4 @@ public slots:
void setMaxFontSize(int _max);
void setRoundCardCorners(bool _roundCardCorners);
};
#endif

View File

@@ -13,7 +13,7 @@ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws*
ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / GenericQuery
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / CommentQuery / GenericQuery
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
@@ -25,6 +25,7 @@ DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String
FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String
PathQuery <- [Pp] 'ath'? [:] String
FormatQuery <- [Ff] 'ormat'? [:] String
CommentQuery <- [Cc] ('omment' 's'?)? [:] String
GenericQuery <- String
@@ -166,6 +167,14 @@ static void setupParserRules()
};
};
search["CommentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto value = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
auto comments = deck->deckLoader->getDeck().deckList.getComments();
return comments.contains(value, Qt::CaseInsensitive);
};
};
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {

View File

@@ -260,16 +260,15 @@ void DeckViewContainer::loadLocalDeck()
void DeckViewContainer::loadDeckFromFile(const QString &filePath)
{
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(filePath);
DeckLoader deck(this);
bool success = deck.loadFromFile(filePath, fmt, true);
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromFile(filePath, fmt, true);
if (!success) {
if (!deckOpt) {
QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded."));
return;
}
loadDeckFromDeckList(deck.getDeck().deckList);
loadDeckFromDeckList(deckOpt.value().deckList);
}
void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck)

View File

@@ -29,24 +29,26 @@ DeckLoader::DeckLoader(QObject *parent) : QObject(parent)
{
}
bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest)
std::optional<LoadedDeck>
DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
qCWarning(DeckLoaderLog) << "File does not exist:" << fileName;
return std::nullopt;
}
bool result = false;
DeckList deckList = DeckList();
DeckList deckList;
switch (fmt) {
case DeckFileFormat::PlainText:
result = deckList.loadFromFile_Plain(&file);
break;
case DeckFileFormat::Cockatrice: {
result = deckList.loadFromFile_Native(&file);
qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result;
if (!result) {
qCInfo(DeckLoaderLog) << "Retrying as plain format";
qCInfo(DeckLoaderLog) << "Failed to load " << fileName
<< "as cockatrice format; retrying as plain format";
file.seek(0);
result = deckList.loadFromFile_Plain(&file);
fmt = DeckFileFormat::PlainText;
@@ -58,120 +60,128 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm
break;
}
if (result) {
loadedDeck.deckList = deckList;
loadedDeck.lastLoadInfo = {
.fileName = fileName,
.fileFormat = fmt,
};
if (userRequest) {
updateLastLoadedTimestamp(fileName, fmt);
}
emit deckLoaded();
if (!result) {
qCWarning(DeckLoaderLog) << "Failed to load " << fileName << "as" << fmt;
return std::nullopt;
}
qCInfo(DeckLoaderLog) << "Deck was loaded -" << result;
return result;
}
LoadedDeck::LoadInfo lastLoadInfo = {
.fileName = fileName,
.fileFormat = fmt,
};
LoadedDeck loadedDeck = {deckList, lastLoadInfo};
bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest)
{
auto *watcher = new QFutureWatcher<bool>(this);
connect(watcher, &QFutureWatcher<bool>::finished, this, [this, watcher, fileName, fmt, userRequest]() {
const bool result = watcher->result();
watcher->deleteLater();
if (result) {
loadedDeck.lastLoadInfo = {
.fileName = fileName,
.fileFormat = fmt,
};
if (userRequest) {
updateLastLoadedTimestamp(fileName, fmt);
}
emit deckLoaded();
}
emit loadFinished(result);
});
QFuture<bool> future = QtConcurrent::run([=, this]() {
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
switch (fmt) {
case DeckFileFormat::PlainText:
return loadedDeck.deckList.loadFromFile_Plain(&file);
case DeckFileFormat::Cockatrice: {
bool result = false;
result = loadedDeck.deckList.loadFromFile_Native(&file);
if (!result) {
file.seek(0);
return loadedDeck.deckList.loadFromFile_Plain(&file);
}
return result;
}
default:
return false;
break;
}
});
watcher->setFuture(future);
return true; // Return immediately to indicate the async task was started
}
bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
{
bool result = loadedDeck.deckList.loadFromString_Native(nativeString);
if (result) {
loadedDeck.lastLoadInfo = {
.remoteDeckId = remoteDeckId,
};
emit deckLoaded();
if (userRequest) {
updateLastLoadedTimestamp(loadedDeck);
}
return result;
qCDebug(DeckLoaderLog) << "Loaded deck" << fileName << "with userRequest:" << userRequest;
return loadedDeck;
}
bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt)
void DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QFuture<void> future = QtConcurrent::run([=, this] {
std::optional<LoadedDeck> deckOpt = loadFromFile(fileName, fmt, userRequest);
if (deckOpt) {
loadedDeck = deckOpt.value();
}
emit loadFinished(deckOpt.has_value());
});
}
bool DeckLoader::reload()
{
QString lastFileName = loadedDeck.lastLoadInfo.fileName;
if (lastFileName.isEmpty()) {
return false;
}
std::optional<LoadedDeck> deck = loadFromFile(lastFileName, loadedDeck.lastLoadInfo.fileFormat, false);
if (!deck) {
return false;
}
bool result = false;
switch (fmt) {
case DeckFileFormat::PlainText:
result = loadedDeck.deckList.saveToFile_Plain(&file);
break;
case DeckFileFormat::Cockatrice:
result = loadedDeck.deckList.saveToFile_Native(&file);
qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result;
break;
loadedDeck = *deck;
return true;
}
std::optional<LoadedDeck> DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
{
DeckList deckList;
bool success = deckList.loadFromString_Native(nativeString);
if (!success) {
qCWarning(DeckLoaderLog) << "Failed to load remote deck with id" << remoteDeckId << ":" << nativeString;
return std::nullopt;
}
if (result) {
loadedDeck.lastLoadInfo = {
.fileName = fileName,
.fileFormat = fmt,
};
qCInfo(DeckLoaderLog) << "Deck was saved -" << result;
LoadedDeck::LoadInfo lastLoadInfo = {.remoteDeckId = remoteDeckId};
LoadedDeck loadedDeck = {deckList, lastLoadInfo};
qCDebug(DeckLoaderLog) << "Loaded remote deck with id" << remoteDeckId;
return loadedDeck;
}
std::optional<LoadedDeck::LoadInfo>
DeckLoader::saveToFile(const DeckList &deck, const QString &fileName, DeckFileFormat::Format fmt)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qCWarning(DeckLoaderLog) << "Could not create or open file:" << fileName;
return std::nullopt;
}
bool success = false;
switch (fmt) {
case DeckFileFormat::PlainText:
success = deck.saveToFile_Plain(&file);
break;
case DeckFileFormat::Cockatrice:
success = deck.saveToFile_Native(&file);
break;
}
file.flush();
file.close();
return result;
qCInfo(DeckLoaderLog) << "Saved deck to " << fileName << "with format" << fmt << "-" << success;
if (!success) {
return std::nullopt;
}
LoadedDeck::LoadInfo lastLoadInfo = {fileName, fmt};
return lastLoadInfo;
}
bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt)
bool DeckLoader::saveToFile(const LoadedDeck &deck)
{
auto opt = saveToFile(deck.deckList, deck.lastLoadInfo.fileName, deck.lastLoadInfo.fileFormat);
return opt.has_value();
}
bool DeckLoader::saveToNewFile(LoadedDeck &deck, const QString &fileName, DeckFileFormat::Format fmt)
{
std::optional<LoadedDeck::LoadInfo> infoOpt = saveToFile(deck.deckList, fileName, fmt);
if (infoOpt) {
deck.lastLoadInfo = infoOpt.value();
}
return infoOpt.has_value();
}
/**
* @brief Updates the lastLoadedTimestamp field in the file corresponding to the deck, without changing the
* FileModificationTime of the file.
*/
bool DeckLoader::updateLastLoadedTimestamp(LoadedDeck &deck)
{
QString fileName = deck.lastLoadInfo.fileName;
QFileInfo fileInfo(fileName);
if (!fileInfo.exists()) {
qCWarning(DeckLoaderLog) << "File does not exist:" << fileName;
@@ -190,24 +200,19 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileForm
bool result = false;
// Perform file modifications
switch (fmt) {
switch (deck.lastLoadInfo.fileFormat) {
case DeckFileFormat::PlainText:
result = loadedDeck.deckList.saveToFile_Plain(&file);
result = deck.deckList.saveToFile_Plain(&file);
break;
case DeckFileFormat::Cockatrice:
loadedDeck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
result = loadedDeck.deckList.saveToFile_Native(&file);
deck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
result = deck.deckList.saveToFile_Native(&file);
break;
}
file.close(); // Close the file to ensure changes are flushed
if (result) {
loadedDeck.lastLoadInfo = {
.fileName = fileName,
.fileFormat = fmt,
};
// Re-open the file and set the original timestamp
if (!file.open(QIODevice::ReadWrite)) {
qCWarning(DeckLoaderLog) << "Failed to re-open file to set timestamp:" << fileName;
@@ -434,8 +439,13 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
}
}
bool DeckLoader::convertToCockatriceFormat(const QString &fileName)
bool DeckLoader::convertToCockatriceFormat(LoadedDeck &deck)
{
QString fileName = deck.lastLoadInfo.fileName;
if (fileName.isEmpty()) {
return false;
}
// Change the file extension to .cod
QFileInfo fileInfo(fileName);
QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod");
@@ -453,7 +463,7 @@ bool DeckLoader::convertToCockatriceFormat(const QString &fileName)
switch (DeckFileFormat::getFormatFromName(fileName)) {
case DeckFileFormat::PlainText:
// Save in Cockatrice's native format
result = loadedDeck.deckList.saveToFile_Native(&file);
result = deck.deckList.saveToFile_Native(&file);
break;
case DeckFileFormat::Cockatrice:
qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed.";
@@ -474,7 +484,7 @@ bool DeckLoader::convertToCockatriceFormat(const QString &fileName)
} else {
qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName;
}
loadedDeck.lastLoadInfo = {
deck.lastLoadInfo = {
.fileName = newFileName,
.fileFormat = DeckFileFormat::Cockatrice,
};

View File

@@ -20,7 +20,6 @@ class DeckLoader : public QObject
{
Q_OBJECT
signals:
void deckLoaded();
void loadFinished(bool success);
public:
@@ -53,11 +52,67 @@ public:
return loadedDeck.lastLoadInfo.isEmpty();
}
bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false);
bool loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest);
bool loadFromRemote(const QString &nativeString, int remoteDeckId);
bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt);
bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt);
/**
* @brief Asynchronously loads a deck from a local file into this DeckLoader.
* The `loadFinished` signal will be emitted when the load finishes.
* Once the loading finishes, the deck can be accessed with `getDeck`
* @param fileName The file to load
* @param fmt The format of the file to load
* @param userRequest Whether the load was manually requested by the user, instead of being done in the background.
*/
void loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest);
/**
* @brief Loads the file that the lastLoadInfo currently points to into this instance.
* No-ops if the lastLoadInfo is missing the required info or the load fails.
* @return Whether the loaded succeeded.
*/
bool reload();
/**
* @brief Loads a deck from a local file.
* @param fileName The file to load
* @param fmt The format of the file to load
* @param userRequest Whether the load was manually requested by the user, instead of being done in the background.
* @return An optional containing the LoadedDeck, or empty if the load failed.
*/
static std::optional<LoadedDeck>
loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false);
/**
* @brief Loads a deck from the response of a remote deck request
* @param nativeString The deck string, in cod format
* @param remoteDeckId The remote deck id
* @return An optional containing the LoadedDeck, or empty if the load failed.
*/
static std::optional<LoadedDeck> loadFromRemote(const QString &nativeString, int remoteDeckId);
/**
* @brief Saves a DeckList to a local file.
* @param deck The DeckList
* @param fileName The file to write to
* @param fmt The deck file format to use
* @return An optional containing the LoadInfo for the new file, or empty if the save failed.
*/
static std::optional<LoadedDeck::LoadInfo>
saveToFile(const DeckList &deck, const QString &fileName, DeckFileFormat::Format fmt);
/**
* @brief Saves a LoadedDeck to a local file.
* Uses the lastLoadInfo in the LoadedDeck to determine where to save to.
* @param deck The LoadedDeck to save. Should have valid lastLoadInfo.
* @return Whether the save succeeded.
*/
static bool saveToFile(const LoadedDeck &deck);
/**
* @brief Saves a LoadedDeck to a new local file.
* @param deck The LoadedDeck to save. Will update the lastLoadInfo.
* @param fileName The file to write to
* @param fmt The deck file format to use
* @return Whether the save succeeded.
*/
static bool saveToNewFile(LoadedDeck &deck, const QString &fileName, DeckFileFormat::Format fmt);
static QString exportDeckToDecklist(const DeckList &deckList, DecklistWebsite website);
@@ -74,7 +129,13 @@ public:
*/
static void printDeckList(QPrinter *printer, const DeckList &deckList);
bool convertToCockatriceFormat(const QString &fileName);
/**
* Converts the given deck's file to the cockatrice file format.
* Uses the lastLoadInfo in the LoadedDeck to determine the current name of the file and where to save to.
* @param deck The deck to convert. Should have valid lastLoadInfo. Will update the lastLoadInfo.
* @return Whether the conversion succeeded.
*/
static bool convertToCockatriceFormat(LoadedDeck &deck);
LoadedDeck &getDeck()
{
@@ -90,6 +151,7 @@ public:
}
private:
static bool updateLastLoadedTimestamp(LoadedDeck &deck);
static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node);
static void saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList);

View File

@@ -8,30 +8,10 @@
#include <QRegularExpression>
#include <QResizeEvent>
#include <QSize>
#include <libcockatrice/utility/qt_utils.h>
ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, CardInfoPtr _card) : QWidget(parent), card(_card)
{
layout = new QHBoxLayout(this);
layout->setSpacing(5); // Small spacing between icons
layout->setContentsMargins(0, 0, 0, 0);
layout->setAlignment(Qt::AlignCenter); // Ensure icons are centered
setLayout(layout);
// Define the full WUBRG set (White, Blue, Black, Red, Green)
QString fullColorIdentity = "WUBRG";
if (card) {
manaCost = card->getColors(); // Get mana cost string
QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string
populateManaSymbolWidgets();
}
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this,
&ColorIdentityWidget::toggleUnusedVisibility);
}
ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString _manaCost)
: QWidget(parent), card(nullptr), manaCost(_manaCost)
ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, const QString &_colorIdentity)
: QWidget(parent), colorIdentity(_colorIdentity)
{
layout = new QHBoxLayout(this);
layout->setSpacing(5); // Small spacing between icons
@@ -45,12 +25,21 @@ ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString _manaCost)
&ColorIdentityWidget::toggleUnusedVisibility);
}
ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, const CardInfoPtr &card)
: ColorIdentityWidget(parent, card->getColors())
{
}
void ColorIdentityWidget::populateManaSymbolWidgets()
{
// Define the full WUBRG set (White, Blue, Black, Red, Green)
QString fullColorIdentity = "WUBRG";
QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string
QStringList symbols = parseColorIdentity(colorIdentity); // Parse mana cost string
// clear old layout
QtUtils::clearLayoutRec(layout);
// populate mana symbols
if (SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities()) {
for (const QString symbol : fullColorIdentity) {
auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol));
@@ -64,15 +53,18 @@ void ColorIdentityWidget::populateManaSymbolWidgets()
}
}
void ColorIdentityWidget::setColorIdentity(const QString &_colorIdentity)
{
if (colorIdentity == _colorIdentity) {
return;
}
colorIdentity = _colorIdentity;
populateManaSymbolWidgets();
}
void ColorIdentityWidget::toggleUnusedVisibility()
{
if (layout != nullptr) {
QLayoutItem *item;
while ((item = layout->takeAt(0)) != nullptr) {
item->widget()->deleteLater(); // Delete the widget
delete item; // Delete the layout item
}
}
populateManaSymbolWidgets();
}
@@ -97,12 +89,12 @@ void ColorIdentityWidget::resizeEvent(QResizeEvent *event)
}
}
QStringList ColorIdentityWidget::parseColorIdentity(const QString &cmc)
QStringList ColorIdentityWidget::parseColorIdentity(const QString &manaString)
{
QStringList symbols;
// Handle split costs (e.g., "3U // 4UU")
QStringList splitCosts = cmc.split(" // ");
QStringList splitCosts = manaString.split(" // ");
for (const QString &part : splitCosts) {
QRegularExpression regex(R"(\{([^}]+)\}|(\d+)|([WUBRGCSPX]))");
QRegularExpressionMatchIterator matches = regex.globalMatch(part);

View File

@@ -15,19 +15,20 @@ class ColorIdentityWidget : public QWidget
{
Q_OBJECT
public:
explicit ColorIdentityWidget(QWidget *parent, CardInfoPtr card);
explicit ColorIdentityWidget(QWidget *parent, QString manaCost);
explicit ColorIdentityWidget(QWidget *parent, const QString &_colorIdentity = "");
explicit ColorIdentityWidget(QWidget *parent, const CardInfoPtr &card);
void populateManaSymbolWidgets();
QStringList parseColorIdentity(const QString &manaString);
static QStringList parseColorIdentity(const QString &manaString);
public slots:
void setColorIdentity(const QString &_colorIdentity);
void resizeEvent(QResizeEvent *event) override;
void toggleUnusedVisibility();
private:
CardInfoPtr card;
QString manaCost;
QString colorIdentity;
QHBoxLayout *layout;
};

View File

@@ -37,6 +37,35 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent,
&CardGroupDisplayWidget::onSelectionChanged);
}
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval);
connect(deckListModel, &QAbstractItemModel::dataChanged, this, &CardGroupDisplayWidget::onDataChanged);
}
// Just here so it can get overwritten in subclasses.
void CardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
}
// =====================================================================================================================
// User Interaction
// =====================================================================================================================
void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event)
{
QWidget::mousePressEvent(event);
if (selectionModel) {
selectionModel->clearSelection();
}
}
void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
{
emit cardClicked(event, card);
}
void CardGroupDisplayWidget::onHover(const ExactCard &card)
{
emit cardHovered(card);
}
void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
@@ -53,8 +82,11 @@ void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected,
auto it = indexToWidgetMap.find(QPersistentModelIndex(idx));
if (it != indexToWidgetMap.end()) {
if (auto displayWidget = qobject_cast<CardInfoPictureWithTextOverlayWidget *>(it.value())) {
displayWidget->setHighlighted(true);
// Highlight all copies of this card
for (auto widget : it.value()) {
if (auto displayWidget = qobject_cast<CardInfoPictureWithTextOverlayWidget *>(widget)) {
displayWidget->setHighlighted(true);
}
}
}
}
@@ -68,29 +100,51 @@ void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected,
auto it = indexToWidgetMap.find(QPersistentModelIndex(idx));
if (it != indexToWidgetMap.end()) {
if (auto displayWidget = qobject_cast<CardInfoPictureWithTextOverlayWidget *>(it.value())) {
displayWidget->setHighlighted(false);
// Un-highlight all copies of this card
for (auto widget : it.value()) {
if (auto displayWidget = qobject_cast<CardInfoPictureWithTextOverlayWidget *>(widget)) {
displayWidget->setHighlighted(false);
}
}
}
}
}
}
void CardGroupDisplayWidget::clearAllDisplayWidgets()
void CardGroupDisplayWidget::refreshSelectionForIndex(const QPersistentModelIndex &persistent)
{
for (auto idx : indexToWidgetMap.keys()) {
auto displayWidget = indexToWidgetMap.value(idx);
removeFromLayout(displayWidget);
indexToWidgetMap.remove(idx);
delete displayWidget;
if (!selectionModel || !indexToWidgetMap.contains(persistent)) {
return;
}
// Convert persistent index to regular index for selection check
QModelIndex idx = QModelIndex(persistent);
// Check if this index is selected
// We need to check against the selection model's model (which might be a proxy)
bool isSelected = false;
if (auto proxyModel = qobject_cast<QAbstractProxyModel *>(selectionModel->model())) {
// Map source index to proxy
QModelIndex proxyIdx = proxyModel->mapFromSource(idx);
isSelected = selectionModel->isSelected(proxyIdx);
} else {
isSelected = selectionModel->isSelected(idx);
}
// Apply selection state to all widgets for this index
for (auto widget : indexToWidgetMap[persistent]) {
if (auto displayWidget = qobject_cast<CardInfoPictureWithTextOverlayWidget *>(widget)) {
displayWidget->setHighlighted(isSelected);
}
}
}
// =====================================================================================================================
// Display Widget Management
// =====================================================================================================================
QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex index)
{
if (indexToWidgetMap.contains(index)) {
return indexToWidgetMap[index];
}
auto cardName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
auto cardProviderId =
index.sibling(index.row(), DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::EditRole).toString();
@@ -103,7 +157,8 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex i
connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &CardGroupDisplayWidget::onHover);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, &CardInfoPictureWidget::setScaleFactor);
indexToWidgetMap.insert(index, widget);
indexToWidgetMap[index].append(widget);
return widget;
}
@@ -130,10 +185,33 @@ void CardGroupDisplayWidget::updateCardDisplays()
// 4. persist the source index
QPersistentModelIndex persistent(sourceIndex);
addToLayout(constructWidgetForIndex(persistent));
// Get the card amount
int cardAmount =
sourceIndex.sibling(sourceIndex.row(), DeckListModelColumns::CARD_AMOUNT).data(Qt::EditRole).toInt();
// Create multiple widgets for the card count
for (int copy = 0; copy < cardAmount; ++copy) {
addToLayout(constructWidgetForIndex(persistent));
}
}
}
void CardGroupDisplayWidget::clearAllDisplayWidgets()
{
auto it = indexToWidgetMap.begin();
while (it != indexToWidgetMap.end()) {
for (auto displayWidget : it.value()) {
removeFromLayout(displayWidget);
delete displayWidget;
}
it = indexToWidgetMap.erase(it);
}
}
// =====================================================================================================================
// DeckListModel Signal Responses
// =====================================================================================================================
void CardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first, int last)
{
if (!trackedIndex.isValid()) {
@@ -148,7 +226,13 @@ void CardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first
// Persist the index
QPersistentModelIndex persistent(child);
insertIntoLayout(constructWidgetForIndex(persistent), row);
// Get the card amount for the newly added card
int cardAmount = child.sibling(child.row(), DeckListModelColumns::CARD_AMOUNT).data(Qt::EditRole).toInt();
// Insert multiple copies
for (int copy = 0; copy < cardAmount; ++copy) {
insertIntoLayout(constructWidgetForIndex(persistent), row);
}
}
}
}
@@ -157,18 +241,100 @@ void CardGroupDisplayWidget::onCardRemoval(const QModelIndex &parent, int first,
{
Q_UNUSED(first);
Q_UNUSED(last);
if (parent == trackedIndex) {
for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) {
if (!idx.isValid()) {
removeFromLayout(indexToWidgetMap.value(idx));
indexToWidgetMap.value(idx)->deleteLater();
indexToWidgetMap.remove(idx);
if (parent != trackedIndex) {
return;
}
// Use iterator so we can remove while iterating
auto it = indexToWidgetMap.begin();
while (it != indexToWidgetMap.end()) {
const QPersistentModelIndex &idx = it.key();
bool shouldRemove = !idx.isValid() || it.value().isEmpty();
if (shouldRemove) {
// Clean up widgets
for (auto widget : it.value()) {
removeFromLayout(widget);
widget->deleteLater();
}
// Erase and advance iterator
it = indexToWidgetMap.erase(it);
} else {
++it;
}
}
if (!trackedIndex.isValid()) {
emit cleanupRequested(this);
}
}
void CardGroupDisplayWidget::onDataChanged(const QModelIndex &topLeft,
const QModelIndex &bottomRight,
const QVector<int> &roles)
{
if (topLeft.parent() != trackedIndex && bottomRight.parent() != trackedIndex) {
return;
}
// Check if CARD_AMOUNT column changed
bool amountChanged = (topLeft.column() <= DeckListModelColumns::CARD_AMOUNT &&
bottomRight.column() >= DeckListModelColumns::CARD_AMOUNT) ||
roles.isEmpty() || roles.contains(Qt::EditRole);
if (!amountChanged) {
return;
}
// For each affected row, adjust widget count
for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
QModelIndex idx = deckListModel->index(row, 0, trackedIndex);
if (!idx.isValid()) {
continue;
}
QPersistentModelIndex persistent(idx);
int newAmount = idx.sibling(idx.row(), DeckListModelColumns::CARD_AMOUNT).data(Qt::EditRole).toInt();
// Get current widget count
int currentWidgetCount = indexToWidgetMap.contains(persistent) ? indexToWidgetMap.value(persistent).count() : 0;
if (newAmount == currentWidgetCount) {
// Still refresh selection even if count didn't change
refreshSelectionForIndex(persistent);
continue;
}
if (newAmount < currentWidgetCount) {
// Remove excess widgets
int toRemove = currentWidgetCount - newAmount;
for (int i = 0; i < toRemove; ++i) {
if (!indexToWidgetMap[persistent].isEmpty()) {
QWidget *widget = indexToWidgetMap[persistent].takeLast();
removeFromLayout(widget);
widget->deleteLater();
}
}
// If all widgets removed, clean up the map entry
if (indexToWidgetMap[persistent].isEmpty()) {
indexToWidgetMap.remove(persistent);
}
} else {
// Add new widgets
int toAdd = newAmount - currentWidgetCount;
for (int i = 0; i < toAdd; ++i) {
addToLayout(constructWidgetForIndex(persistent));
}
}
if (!trackedIndex.isValid()) {
emit cleanupRequested(this);
}
// Always refresh selection state after modifying widgets
refreshSelectionForIndex(persistent);
}
}
@@ -178,27 +344,4 @@ void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSort
clearAllDisplayWidgets();
updateCardDisplays();
}
void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event)
{
QWidget::mousePressEvent(event);
if (selectionModel) {
selectionModel->clearSelection();
}
}
void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
{
emit cardClicked(event, card);
}
void CardGroupDisplayWidget::onHover(const ExactCard &card)
{
emit cardHovered(card);
}
void CardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
}

View File

@@ -33,12 +33,13 @@ public:
int bannerOpacity,
CardSizeWidget *cardSizeWidget);
void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void refreshSelectionForIndex(const QPersistentModelIndex &persistent);
void clearAllDisplayWidgets();
DeckListModel *deckListModel;
QItemSelectionModel *selectionModel;
QPersistentModelIndex trackedIndex;
QHash<QPersistentModelIndex, QWidget *> indexToWidgetMap;
QMap<QPersistentModelIndex, QList<QWidget *>> indexToWidgetMap;
QString zoneName;
QString cardGroupCategory;
QString activeGroupCriteria;
@@ -53,6 +54,7 @@ public slots:
virtual void updateCardDisplays();
virtual void onCardAddition(const QModelIndex &parent, int first, int last);
virtual void onCardRemoval(const QModelIndex &parent, int first, int last);
void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
void onActiveSortCriteriaChanged(QStringList activeSortCriteria);
void resizeEvent(QResizeEvent *event) override;

View File

@@ -31,13 +31,17 @@ FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent,
layout->addWidget(flowWidget);
// Clear all existing widgets
for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) {
FlatCardGroupDisplayWidget::removeFromLayout(indexToWidgetMap.value(idx));
indexToWidgetMap.value(idx)->deleteLater();
for (auto widget : indexToWidgetMap.value(idx)) {
FlatCardGroupDisplayWidget::removeFromLayout(widget);
widget->deleteLater();
}
indexToWidgetMap.remove(idx);
}
FlatCardGroupDisplayWidget::updateCardDisplays();
disconnect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition);
disconnect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval);

View File

@@ -30,9 +30,12 @@ OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *pare
layout->addWidget(overlapWidget);
// Clear all existing widgets
for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) {
OverlappedCardGroupDisplayWidget::removeFromLayout(indexToWidgetMap.value(idx));
indexToWidgetMap.value(idx)->deleteLater();
for (auto widget : indexToWidgetMap.value(idx)) {
OverlappedCardGroupDisplayWidget::removeFromLayout(widget);
widget->deleteLater();
}
indexToWidgetMap.remove(idx);
}

View File

@@ -47,7 +47,7 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT
originalPos = this->pos();
// Create the animation
animation = new QPropertyAnimation(this, "pos");
animation = new QPropertyAnimation(this, "pos", this);
animation->setDuration(200); // 200ms animation duration
animation->setEasingCurve(QEasingCurve::OutQuad);

View File

@@ -2,6 +2,7 @@
#include "card_group_display_widgets/flat_card_group_display_widget.h"
#include "card_group_display_widgets/overlapped_card_group_display_widget.h"
#include "libcockatrice/card/database/card_database_manager.h"
#include <QResizeEvent>
#include <libcockatrice/models/deck_list/deck_list_model.h>
@@ -22,6 +23,7 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
displayType(_displayType), bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity),
cardSizeWidget(_cardSizeWidget)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout = new QVBoxLayout(this);
setLayout(layout);
@@ -45,6 +47,20 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &DeckCardZoneDisplayWidget::onCategoryRemoval);
}
// =====================================================================================================================
// User Interaction
// =====================================================================================================================
void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
{
emit cardClicked(event, card, zoneName);
}
void DeckCardZoneDisplayWidget::onHover(const ExactCard &card)
{
emit cardHovered(card);
}
void DeckCardZoneDisplayWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
for (auto &range : selected) {
@@ -68,17 +84,9 @@ void DeckCardZoneDisplayWidget::onSelectionChanged(const QItemSelection &selecte
}
}
void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget)
{
cardGroupLayout->removeWidget(displayWidget);
displayWidget->setParent(nullptr);
for (auto idx : indexToWidgetMap.keys()) {
if (!idx.isValid()) {
indexToWidgetMap.remove(idx);
}
}
delete displayWidget;
}
// =====================================================================================================================
// Display Widget Management
// =====================================================================================================================
void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index)
{
@@ -140,6 +148,45 @@ void DeckCardZoneDisplayWidget::displayCards()
}
}
void DeckCardZoneDisplayWidget::refreshDisplayType(const DisplayType &_displayType)
{
displayType = _displayType;
QLayoutItem *item;
while ((item = cardGroupLayout->takeAt(0)) != nullptr) {
if (item->widget()) {
item->widget()->deleteLater();
} else if (item->layout()) {
item->layout()->deleteLater();
}
delete item;
}
indexToWidgetMap.clear();
// We gotta wait for all the deleteLater's to finish so we fire after the next event cycle
auto timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, [this]() { displayCards(); });
timer->start();
}
void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget)
{
cardGroupLayout->removeWidget(displayWidget);
displayWidget->setParent(nullptr);
for (auto idx : indexToWidgetMap.keys()) {
if (!idx.isValid()) {
indexToWidgetMap.remove(idx);
}
}
delete displayWidget;
}
// =====================================================================================================================
// DeckListModel Signal Responses
// =====================================================================================================================
void DeckCardZoneDisplayWidget::onCategoryAddition(const QModelIndex &parent, int first, int last)
{
if (!trackedIndex.isValid()) {
@@ -172,48 +219,6 @@ void DeckCardZoneDisplayWidget::onCategoryRemoval(const QModelIndex &parent, int
}
}
void DeckCardZoneDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
for (QObject *child : layout->children()) {
QWidget *widget = qobject_cast<QWidget *>(child);
if (widget) {
widget->setMaximumWidth(width());
}
}
}
void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
{
emit cardClicked(event, card, zoneName);
}
void DeckCardZoneDisplayWidget::onHover(const ExactCard &card)
{
emit cardHovered(card);
}
void DeckCardZoneDisplayWidget::refreshDisplayType(const DisplayType &_displayType)
{
displayType = _displayType;
QLayoutItem *item;
while ((item = cardGroupLayout->takeAt(0)) != nullptr) {
if (item->widget()) {
item->widget()->deleteLater();
} else if (item->layout()) {
item->layout()->deleteLater();
}
delete item;
}
indexToWidgetMap.clear();
// We gotta wait for all the deleteLater's to finish so we fire after the next event cycle
auto timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, [this]() { displayCards(); });
timer->start();
}
void DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged(QString _activeGroupCriteria)
{
activeGroupCriteria = _activeGroupCriteria;
@@ -230,10 +235,13 @@ QList<QString> DeckCardZoneDisplayWidget::getGroupCriteriaValueList()
{
QList<QString> groupCriteriaValues;
QList<ExactCard> cardsInZone = deckListModel->getCardsForZone(zoneName);
QList<const DecklistCardNode *> nodes = deckListModel->getCardNodesForZone(zoneName);
for (const ExactCard &cardInZone : cardsInZone) {
groupCriteriaValues.append(cardInZone.getInfo().getProperty(activeGroupCriteria));
for (auto node : nodes) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
if (info) {
groupCriteriaValues.append(info->getProperty(activeGroupCriteria));
}
}
groupCriteriaValues.removeDuplicates();

View File

@@ -40,7 +40,6 @@ public:
QPersistentModelIndex trackedIndex;
QString zoneName;
void addCardsToOverlapWidget();
void resizeEvent(QResizeEvent *event) override;
public slots:
void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);

View File

@@ -19,7 +19,8 @@ AbstractAnalyticsPanelWidget::AbstractAnalyticsPanelWidget(QWidget *parent, Deck
bannerAndSettingsLayout->addWidget(bannerWidget, 1);
// config button
configureButton = new QPushButton(tr("Configure"), this);
configureButton = new QPushButton(this);
configureButton->setIcon(QPixmap("theme:icons/cogwheel"));
configureButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
connect(configureButton, &QPushButton::clicked, this, &AbstractAnalyticsPanelWidget::applyConfigFromDialog);
bannerAndSettingsLayout->addWidget(configureButton, 0);

View File

@@ -22,6 +22,7 @@ DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatistics
{
controls = new QWidget(this);
controlLayout = new QHBoxLayout(controls);
controlLayout->setContentsMargins(11, 0, 11, 0);
labelPrefix = new QLabel(this);
controlLayout->addWidget(labelPrefix);
@@ -65,6 +66,7 @@ DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatistics
resultTable = new QTableWidget(this);
resultTable->setColumnCount(3);
resultTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
resultTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
layout->addWidget(resultTable);
// Connections
@@ -170,7 +172,7 @@ void DrawProbabilityWidget::updateFilterOptions()
const auto nodes = analyzer->getModel()->getCardNodes();
for (auto *node : nodes) {
CardInfoPtr info = CardDatabaseManager::query()->getCard({node->getName()}).getCardPtr();
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
if (!info) {
continue;
}

View File

@@ -8,6 +8,7 @@
#include <QDialog>
#include <QListWidget>
#include <libcockatrice/utility/color.h>
namespace
{
@@ -71,14 +72,16 @@ void ManaBaseWidget::updateDisplay()
// Choose display mode
if (config.displayType == "bar") {
QHash<QString, QColor> colors = {{"W", QColor(248, 231, 185)}, {"U", QColor(14, 104, 171)},
{"B", QColor(21, 11, 0)}, {"R", QColor(211, 32, 42)},
{"G", QColor(0, 115, 62)}, {"C", QColor(150, 150, 150)}};
const QList<QPair<QString, int>> sortedColors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(mapSorted);
static const QHash<QString, QColor> colorMap = {
{"W", QColor(248, 231, 185)}, {"U", QColor(14, 104, 171)}, {"B", QColor(21, 11, 0)},
{"R", QColor(211, 32, 42)}, {"G", QColor(0, 115, 62)}, {"C", QColor(150, 150, 150)},
};
for (auto color : manaMap.keys()) {
QString label = QString("%1 %2 (%3)").arg(color).arg(manaMap[color]).arg(cardCount.value(color));
for (const auto &[color, count] : sortedColors) {
QString label = QString("%1 %2 (%3)").arg(color).arg(count).arg(cardCount.value(color));
BarWidget *bar = new BarWidget(label, manaMap[color], highest, colors.value(color, Qt::gray), this);
BarWidget *bar = new BarWidget(label, count, highest, colorMap.value(color, Qt::gray), this);
barLayout->addWidget(bar);
}

View File

@@ -12,89 +12,98 @@ DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent,
DeckListStatisticsAnalyzerConfig _config)
: QObject(parent), model(_model), config(_config)
{
connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::analyze);
connect(model, &DeckListModel::cardsChanged, this, &DeckListStatisticsAnalyzer::analyze);
}
void DeckListStatisticsAnalyzer::analyze()
{
clearData();
QList<ExactCard> cards = model->getCards();
QList<const DecklistCardNode *> nodes = model->getCardNodes();
for (auto card : cards) {
auto info = card.getInfo();
const int cmc = info.getCmc().toInt();
for (auto node : nodes) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
if (!info) {
continue;
}
const int amount = node->getNumber();
QStringList copiesOfName;
for (int i = 0; i < amount; i++) {
copiesOfName.append(node->getName());
}
// Convert once
QStringList types = info.getMainCardType().split(' ');
QStringList subtypes = info.getCardType().split('-').last().split(" ");
QString colors = info.getColors();
int power = info.getPowTough().split("/").first().toInt();
int toughness = info.getPowTough().split("/").last().toInt();
const int cmc = info->getCmc().toInt();
QStringList types = info->getMainCardType().split(' ');
QStringList subtypes = info->getCardType().split('-').last().split(" ");
QString colors = info->getColors();
int power = info->getPowTough().split("/").first().toInt();
int toughness = info->getPowTough().split("/").last().toInt();
// For each copy of card
// ---------------- Mana Curve ----------------
if (config.computeManaCurve) {
manaCurveMap[cmc]++;
manaCurveMap[cmc] += amount;
}
// per-type curve
for (auto &t : types) {
manaCurveByType[t][cmc]++;
manaCurveCardsByType[t][cmc].append(info.getName());
manaCurveByType[t][cmc] += amount;
manaCurveCardsByType[t][cmc] << copiesOfName;
}
// Per-subtype curve
for (auto &st : subtypes) {
manaCurveBySubtype[st][cmc]++;
manaCurveCardsBySubtype[st][cmc].append(info.getName());
manaCurveBySubtype[st][cmc] += amount;
manaCurveCardsBySubtype[st][cmc] << copiesOfName;
}
// per-color curve
for (auto &c : colors) {
manaCurveByColor[c][cmc]++;
manaCurveCardsByColor[c][cmc].append(info.getName());
manaCurveByColor[c][cmc] += amount;
manaCurveCardsByColor[c][cmc] << copiesOfName;
}
// Power/toughness
manaCurveByPower[QString::number(power)][cmc]++;
manaCurveCardsByPower[QString::number(power)][cmc].append(info.getName());
manaCurveByToughness[QString::number(toughness)][cmc]++;
manaCurveCardsByToughness[QString::number(toughness)][cmc].append(info.getName());
manaCurveByPower[QString::number(power)][cmc] += amount;
manaCurveCardsByPower[QString::number(power)][cmc] << copiesOfName;
manaCurveByToughness[QString::number(toughness)][cmc] += amount;
manaCurveCardsByToughness[QString::number(toughness)][cmc] << copiesOfName;
// ========== Category Counts ===========
for (auto &t : types) {
typeCount[t]++;
typeCount[t] += amount;
}
for (auto &st : subtypes) {
subtypeCount[st]++;
subtypeCount[st] += amount;
}
for (auto &c : colors) {
colorCount[c]++;
colorCount[c] += amount;
}
manaValueCount[cmc]++;
manaValueCount[cmc] += amount;
// ---------------- Mana Base ----------------
if (config.computeManaBase) {
auto prod = determineManaProduction(info.getText());
auto prod = determineManaProduction(info->getText());
for (auto it = prod.begin(); it != prod.end(); ++it) {
if (it.value() > 0) {
productionPipCount[it.key()] += it.value();
productionCardCount[it.key()]++;
productionPipCount[it.key()] += it.value() * amount;
productionCardCount[it.key()] += amount;
}
manaBaseMap[it.key()] += it.value();
manaBaseMap[it.key()] += it.value() * amount;
}
}
// ---------------- Devotion ----------------
if (config.computeDevotion) {
auto devo = countManaSymbols(info.getManaCost());
auto devo = countManaSymbols(info->getManaCost());
for (auto &d : devo) {
if (d.second > 0) {
devotionPipCount[QString(d.first)] += d.second;
devotionCardCount[QString(d.first)]++;
devotionPipCount[QString(d.first)] += d.second * amount;
devotionCardCount[QString(d.first)] += amount;
}
manaDevotionMap[d.first] += d.second;
manaDevotionMap[d.first] += d.second * amount;
}
}
}

View File

@@ -0,0 +1,64 @@
#include "deck_editor_card_database_dock_widget.h"
DeckEditorCardDatabaseDockWidget::DeckEditorCardDatabaseDockWidget(AbstractTabDeckEditor *parent) : QDockWidget(parent)
{
setObjectName("databaseDisplayDock");
setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
createDatabaseDisplayDock(parent);
retranslateUi();
}
void DeckEditorCardDatabaseDockWidget::createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor)
{
databaseDisplayWidget = new DeckEditorDatabaseDisplayWidget(this, deckEditor);
auto *frame = new QVBoxLayout;
frame->setObjectName("databaseDisplayFrame");
frame->addWidget(databaseDisplayWidget);
auto *dockContents = new QWidget();
dockContents->setObjectName("databaseDisplayDockContents");
dockContents->setLayout(frame);
setWidget(dockContents);
installEventFilter(deckEditor);
// connect signals
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, deckEditor,
&AbstractTabDeckEditor::updateCard);
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, deckEditor,
&AbstractTabDeckEditor::actAddCard);
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, deckEditor,
&AbstractTabDeckEditor::actAddCardToSideboard);
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromMainDeck, deckEditor,
&AbstractTabDeckEditor::actDecrementCard);
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromSideboard, deckEditor,
&AbstractTabDeckEditor::actDecrementCardFromSideboard);
}
CardDatabase *DeckEditorCardDatabaseDockWidget::getDatabase() const
{
return databaseDisplayWidget->databaseModel->getDatabase();
}
void DeckEditorCardDatabaseDockWidget::retranslateUi()
{
setWindowTitle(tr("Card Database"));
}
void DeckEditorCardDatabaseDockWidget::setFilterTree(FilterTree *filterTree)
{
databaseDisplayWidget->setFilterTree(filterTree);
}
void DeckEditorCardDatabaseDockWidget::clearAllDatabaseFilters()
{
databaseDisplayWidget->clearAllDatabaseFilters();
}
void DeckEditorCardDatabaseDockWidget::highlightAllSearchEdit()
{
databaseDisplayWidget->searchEdit->setSelection(0, databaseDisplayWidget->searchEdit->text().length());
}

View File

@@ -0,0 +1,32 @@
#ifndef COCKATRICE_DECK_EDITOR_CARD_DATABASE_DOCK_WIDGET_H
#define COCKATRICE_DECK_EDITOR_CARD_DATABASE_DOCK_WIDGET_H
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include <QDockWidget>
class AbstractTabDeckEditor;
class CardDatabase;
class DeckEditorDatabaseDisplayWidget;
class FilterTree;
class DeckEditorCardDatabaseDockWidget : public QDockWidget
{
public:
explicit DeckEditorCardDatabaseDockWidget(AbstractTabDeckEditor *parent);
DeckEditorDatabaseDisplayWidget *databaseDisplayWidget;
CardDatabase *getDatabase() const;
void setFilterTree(FilterTree *filterTree);
public slots:
void retranslateUi();
void clearAllDatabaseFilters();
void highlightAllSearchEdit();
private:
void createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor);
};
#endif // COCKATRICE_DECK_EDITOR_CARD_DATABASE_DOCK_WIDGET_H

View File

@@ -31,7 +31,6 @@ void DeckEditorCardInfoDockWidget::createCardInfoDock()
setWidget(cardInfoDockContents);
installEventFilter(deckEditor);
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
}
void DeckEditorCardInfoDockWidget::updateCard(const ExactCard &_card)

View File

@@ -21,10 +21,10 @@ static bool canBeCommander(const CardInfo &cardInfo)
cardInfo.getText().contains("can be your commander", Qt::CaseInsensitive);
}
DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent)
: QWidget(parent), deckEditor(parent)
DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor)
: QWidget(parent), deckEditor(deckEditor)
{
setObjectName("centralWidget");
setObjectName("databaseDisplayWidget");
centralFrame = new QVBoxLayout(this);
centralFrame->setObjectName("centralFrame");
@@ -187,10 +187,8 @@ void DeckEditorDatabaseDisplayWidget::databaseCustomMenu(QPoint point)
QAction *addToDeck, *addToSideboard, *selectPrinting, *edhRecCommander, *edhRecCard;
addToDeck = menu.addAction(tr("Add to Deck"));
addToSideboard = menu.addAction(tr("Add to Sideboard"));
if (!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) {
selectPrinting = menu.addAction(tr("Select Printing"));
connect(selectPrinting, &QAction::triggered, this, [this, card] { deckEditor->showPrintingSelector(); });
}
selectPrinting = menu.addAction(tr("Select Printing"));
connect(selectPrinting, &QAction::triggered, this, [this, card] { deckEditor->showPrintingSelector(); });
if (canBeCommander(card.getInfo())) {
edhRecCommander = menu.addAction(tr("Show on EDHRec (Commander)"));
connect(edhRecCommander, &QAction::triggered, this,

View File

@@ -23,7 +23,7 @@ class DeckEditorDatabaseDisplayWidget : public QWidget
Q_OBJECT
public:
explicit DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent);
explicit DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor);
AbstractTabDeckEditor *deckEditor;
SearchLineEdit *searchEdit;
CardDatabaseModel *databaseModel;

View File

@@ -46,7 +46,6 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
installEventFilter(deckEditor);
connect(this, &DeckEditorDeckDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
createDeckDock();
}
@@ -154,7 +153,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
bannerCardLabel->setText(tr("Banner Card"));
bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
bannerCardComboBox = new QComboBox(this);
connect(getModel(), &DeckListModel::dataChanged, this, [this]() {
connect(getModel(), &DeckListModel::cardNodesChanged, this, [this]() {
// Delay the update to avoid race conditions
QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox);
});
@@ -459,12 +458,15 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
}
}
void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex)
void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus)
{
deckView->clearSelection();
deckView->setCurrentIndex(newCardIndex);
recursiveExpand(newCardIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
if (!preserveWidgetFocus) {
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
}
}
void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
@@ -597,13 +599,12 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodeSourceIndices() con
return selectedRows;
}
void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString &_zoneName)
void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString &zoneName)
{
if (!card) {
return;
}
QString zoneName = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : _zoneName;
deckStateManager->addCard(card, zoneName);
}
@@ -712,14 +713,12 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool i
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
{
if (!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) {
QMenu menu;
QMenu menu;
QAction *selectPrinting = menu.addAction(tr("Select Printing"));
connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector);
QAction *selectPrinting = menu.addAction(tr("Select Printing"));
connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector);
menu.exec(deckView->mapToGlobal(point));
}
menu.exec(deckView->mapToGlobal(point));
}
void DeckEditorDeckDockWidget::refreshShortcuts()

View File

@@ -100,7 +100,7 @@ private slots:
void writeComments();
void writeBannerCard(int);
void applyActiveGroupCriteria();
void setSelectedIndex(const QModelIndex &newCardIndex);
void setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus);
void updateHash();
void refreshShortcuts();
void updateShowBannerCardComboBox(bool visible);

View File

@@ -80,7 +80,6 @@ void DeckEditorFilterDockWidget::createFiltersDock()
setWidget(filterDockContents);
installEventFilter(deckEditor);
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
}
void DeckEditorFilterDockWidget::filterViewCustomContextMenu(const QPoint &point)

View File

@@ -1,6 +1,8 @@
#include "deck_editor_printing_selector_dock_widget.h"
#include "../../../client/settings/cache_settings.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "printing_disabled_info_widget.h"
#include <QVBoxLayout>
@@ -14,6 +16,11 @@ DeckEditorPrintingSelectorDockWidget::DeckEditorPrintingSelectorDockWidget(Abstr
setFloating(false);
createPrintingSelectorDock();
printingDisabledInfoWidget = new PrintingDisabledInfoWidget(this);
setVisibleWidget(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference());
connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this,
&DeckEditorPrintingSelectorDockWidget::setVisibleWidget);
retranslateUi();
}
@@ -26,13 +33,11 @@ void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock()
printingSelectorFrame->setObjectName("printingSelectorFrame");
printingSelectorFrame->addWidget(printingSelector);
auto *printingSelectorDockContents = new QWidget();
printingSelectorDockContents = new QWidget();
printingSelectorDockContents->setObjectName("printingSelectorDockContents");
printingSelectorDockContents->setLayout(printingSelectorFrame);
setWidget(printingSelectorDockContents);
installEventFilter(deckEditor);
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
connect(printingSelector, &PrintingSelector::prevCardRequested, deckEditor->getDeckDockWidget(),
&DeckEditorDeckDockWidget::selectPrevCard);
connect(printingSelector, &PrintingSelector::nextCardRequested, deckEditor->getDeckDockWidget(),
@@ -43,3 +48,13 @@ void DeckEditorPrintingSelectorDockWidget::retranslateUi()
{
setWindowTitle(tr("Printing Selector"));
}
void DeckEditorPrintingSelectorDockWidget::setVisibleWidget(bool overridePrintings)
{
if (overridePrintings) {
setWidget(printingDisabledInfoWidget);
} else {
setWidget(printingSelectorDockContents);
printingSelector->updateDisplay();
}
}

View File

@@ -12,7 +12,9 @@
#include <QDockWidget>
class PrintingDisabledInfoWidget;
class TabDeckEditor;
class DeckEditorPrintingSelectorDockWidget : public QDockWidget
{
Q_OBJECT
@@ -20,10 +22,16 @@ public:
explicit DeckEditorPrintingSelectorDockWidget(AbstractTabDeckEditor *parent);
void createPrintingSelectorDock();
void retranslateUi();
PrintingSelector *printingSelector;
private:
AbstractTabDeckEditor *deckEditor;
QWidget *printingSelectorDockContents;
PrintingDisabledInfoWidget *printingDisabledInfoWidget;
private slots:
void setVisibleWidget(bool overridePrintings);
};
#endif // DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H

View File

@@ -11,8 +11,7 @@ DeckStateManager::DeckStateManager(QObject *parent)
setModified(true);
emit historyChanged();
});
connect(deckListModel, &DeckListModel::rowsInserted, this, &DeckStateManager::uniqueCardsChanged);
connect(deckListModel, &DeckListModel::rowsRemoved, this, &DeckStateManager::uniqueCardsChanged);
connect(deckListModel, &DeckListModel::cardNodesChanged, this, &DeckStateManager::uniqueCardsChanged);
}
const DeckList &DeckStateManager::getDeckList() const
@@ -174,14 +173,16 @@ QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zone
return {};
}
QString zone = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : zoneName;
QString reason = tr("Added (%1): %2 (%3) %4")
.arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(),
.arg(zone, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(),
card.getPrinting().getProperty("num"));
QModelIndex idx = modifyDeck(reason, [&card, &zoneName](auto model) { return model->addCard(card, zoneName); });
QModelIndex idx = modifyDeck(reason, [&card, &zone](auto model) { return model->addCard(card, zone); });
if (idx.isValid()) {
emit focusIndexChanged(idx);
emit focusIndexChanged(idx, true);
}
return idx;
@@ -200,17 +201,19 @@ QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString
return {};
}
bool success = offsetCountAtIndex(idx, false);
bool success = offsetCountAtIndex(idx, -1);
if (!success) {
return {};
}
if (idx.isValid()) {
emit focusIndexChanged(idx);
// old index is no longer safe since rows could have been removed
QModelIndex newIdx = deckListModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
if (newIdx.isValid()) {
emit focusIndexChanged(newIdx, true);
}
return idx;
return newIdx;
}
static bool doSwapCard(DeckListModel *model,

View File

@@ -290,8 +290,9 @@ signals:
/**
* The selected card on any views connected to this deck should be changed to this index.
* @param index The model index
* @param preserveWidgetFocus Whether to keep the widget focus unchanged
*/
void focusIndexChanged(QModelIndex index);
void focusIndexChanged(QModelIndex index, bool preserveWidgetFocus);
};
#endif // COCKATRICE_DECK_STATE_MANAGER_H

View File

@@ -0,0 +1,56 @@
#include "printing_disabled_info_widget.h"
#include "../dialogs/override_printing_warning.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QStyle>
#include <qguiapplication.h>
PrintingDisabledInfoWidget::PrintingDisabledInfoWidget(QWidget *parent) : QWidget(parent)
{
auto layout = new QVBoxLayout(this);
layout->setObjectName("PrintingDisabledInfoWidgetFrame");
setLayout(layout);
QLabel *imageLabel = new QLabel(this);
imageLabel->setAlignment(Qt::AlignCenter);
QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxWarning);
imageLabel->setPixmap(icon.pixmap({60, 60}));
textLabel = new QLabel(this);
textLabel->setWordWrap(true);
textLabel->setAlignment(Qt::AlignCenter);
settingsButton = new QPushButton(this);
connect(settingsButton, &QPushButton::clicked, this, &PrintingDisabledInfoWidget::disableOverridePrintings);
auto buttonLayout = new QHBoxLayout(this);
buttonLayout->addStretch();
buttonLayout->addWidget(settingsButton);
buttonLayout->addStretch();
layout->addStretch();
layout->addWidget(imageLabel);
layout->addWidget(textLabel);
layout->addLayout(buttonLayout);
layout->addStretch();
retranslateUi();
}
void PrintingDisabledInfoWidget::retranslateUi()
{
textLabel->setText(
tr("The Printing Selector is disabled because you have currently enabled the setting to override all "
"selected printings with personal set preferences.\n\n"
"This setting means you'll only see the default printing for each card, instead of being able to select a "
"printing, and will not see the printings other people have selected.\n\n"));
settingsButton->setText(tr("Enable printings again"));
}
void PrintingDisabledInfoWidget::disableOverridePrintings()
{
OverridePrintingWarning::execMessageBox(this, false);
}

View File

@@ -0,0 +1,24 @@
#ifndef COCKATRICE_PRINTING_DISABLED_INFO_WIDGET_H
#define COCKATRICE_PRINTING_DISABLED_INFO_WIDGET_H
#include <QWidget>
class QPushButton;
class QLabel;
class PrintingDisabledInfoWidget : public QWidget
{
Q_OBJECT
QLabel *textLabel;
QPushButton *settingsButton;
private slots:
void disableOverridePrintings();
public:
explicit PrintingDisabledInfoWidget(QWidget *parent);
void retranslateUi();
};
#endif // COCKATRICE_PRINTING_DISABLED_INFO_WIDGET_H

View File

@@ -152,7 +152,7 @@ static bool swapPrinting(DeckListModel *model, const QString &modifiedSet, const
return false;
}
int amount = model->data(idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT), Qt::DisplayRole).toInt();
model->removeRow(idx.row(), idx.parent());
model->removeCardAtIndex(idx);
CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(cardName);
PrintingInfo printing = CardDatabaseManager::query()->getSpecificPrinting(cardName, modifiedSet, "");
for (int i = 0; i < amount; i++) {

View File

@@ -13,6 +13,7 @@
#include "../interface/widgets/utility/get_text_with_max.h"
#include "../interface/widgets/utility/sequence_edit.h"
#include "../main.h"
#include "override_printing_warning.h"
#include <../../client/settings/card_counter_settings.h>
#include <QAbstractButton>
@@ -438,6 +439,7 @@ AppearanceSettingsPage::AppearanceSettingsPage()
connect(&homeTabBackgroundSourceBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this]() {
auto type = homeTabBackgroundSourceBox.currentData().value<BackgroundSources::Type>();
SettingsCache::instance().setHomeTabBackgroundSource(BackgroundSources::toId(type));
updateHomeTabSettingsVisibility();
});
homeTabBackgroundShuffleFrequencySpinBox.setRange(0, 3600);
@@ -446,6 +448,12 @@ AppearanceSettingsPage::AppearanceSettingsPage()
connect(&homeTabBackgroundShuffleFrequencySpinBox, qOverload<int>(&QSpinBox::valueChanged),
&SettingsCache::instance(), &SettingsCache::setHomeTabBackgroundShuffleFrequency);
homeTabDisplayCardNameCheckBox.setChecked(settings.getHomeTabDisplayCardName());
connect(&homeTabDisplayCardNameCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings,
&SettingsCache::setHomeTabDisplayCardName);
updateHomeTabSettingsVisibility();
auto *themeGrid = new QGridLayout;
themeGrid->addWidget(&themeLabel, 0, 0);
themeGrid->addWidget(&themeBox, 0, 1);
@@ -454,6 +462,8 @@ AppearanceSettingsPage::AppearanceSettingsPage()
themeGrid->addWidget(&homeTabBackgroundSourceBox, 2, 1);
themeGrid->addWidget(&homeTabBackgroundShuffleFrequencyLabel, 3, 0);
themeGrid->addWidget(&homeTabBackgroundShuffleFrequencySpinBox, 3, 1);
themeGrid->addWidget(&homeTabDisplayCardNameLabel, 4, 0);
themeGrid->addWidget(&homeTabDisplayCardNameCheckBox, 4, 1);
themeGroupBox = new QGroupBox;
themeGroupBox->setLayout(themeGrid);
@@ -650,6 +660,17 @@ void AppearanceSettingsPage::openThemeLocation()
}
}
void AppearanceSettingsPage::updateHomeTabSettingsVisibility()
{
bool visible =
SettingsCache::instance().getHomeTabBackgroundSource() != BackgroundSources::toId(BackgroundSources::Theme);
homeTabBackgroundShuffleFrequencyLabel.setVisible(visible);
homeTabBackgroundShuffleFrequencySpinBox.setVisible(visible);
homeTabDisplayCardNameLabel.setVisible(visible);
homeTabDisplayCardNameCheckBox.setVisible(visible);
}
void AppearanceSettingsPage::showShortcutsChanged(QT_STATE_CHANGED_T value)
{
SettingsCache::instance().setShowShortcuts(value);
@@ -660,32 +681,9 @@ void AppearanceSettingsPage::overrideAllCardArtWithPersonalPreferenceToggled(QT_
{
bool enable = static_cast<bool>(value);
QString message;
if (enable) {
message = tr("Enabling this feature will disable the use of the Printing Selector.\n\n"
"You will not be able to manage printing preferences on a per-deck basis, "
"or see printings other people have selected for their decks.\n\n"
"You will have to use the Set Manager, available through Card Database -> Manage Sets.\n\n"
"Are you sure you would like to enable this feature?");
} else {
message =
tr("Disabling this feature will enable the Printing Selector.\n\n"
"You can now choose printings on a per-deck basis in the Deck Editor and configure which printing "
"gets added to a deck by default by pinning it in the Printing Selector.\n\n"
"You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector"
" (other sort orders like alphabetical or release date are available).\n\n"
"Are you sure you would like to disable this feature?");
}
bool accepted = OverridePrintingWarning::execMessageBox(this, enable);
QMessageBox::StandardButton result =
QMessageBox::question(this, tr("Confirm Change"), message, QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::Yes) {
SettingsCache::instance().setOverrideAllCardArtWithPersonalPreference(value);
// Caches are now invalid.
CardPictureLoader::clearPixmapCache();
CardPictureLoader::clearNetworkCache();
} else {
if (!accepted) {
// If user cancels, revert the checkbox/state back
QTimer::singleShot(0, this, [this, enable]() {
overrideAllCardArtWithPersonalPreferenceCheckBox.blockSignals(true);
@@ -729,6 +727,7 @@ void AppearanceSettingsPage::retranslateUi()
homeTabBackgroundSourceLabel.setText(tr("Home tab background source:"));
homeTabBackgroundShuffleFrequencyLabel.setText(tr("Home tab background shuffle frequency:"));
homeTabBackgroundShuffleFrequencySpinBox.setSpecialValueText(tr("Disabled"));
homeTabDisplayCardNameLabel.setText(tr("Display card name of background in bottom right:"));
menuGroupBox->setTitle(tr("Menu settings"));
showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus"));

View File

@@ -103,6 +103,7 @@ class AppearanceSettingsPage : public AbstractSettingsPage
private slots:
void themeBoxChanged(int index);
void openThemeLocation();
void updateHomeTabSettingsVisibility();
void showShortcutsChanged(QT_STATE_CHANGED_T enabled);
void overrideAllCardArtWithPersonalPreferenceToggled(QT_STATE_CHANGED_T enabled);
@@ -117,6 +118,8 @@ private:
QComboBox homeTabBackgroundSourceBox;
QLabel homeTabBackgroundShuffleFrequencyLabel;
QSpinBox homeTabBackgroundShuffleFrequencySpinBox;
QLabel homeTabDisplayCardNameLabel;
QCheckBox homeTabDisplayCardNameCheckBox;
QLabel minPlayersForMultiColumnLayoutLabel;
QLabel maxFontSizeForCardsLabel;
QCheckBox showShortcutsCheckBox;

View File

@@ -0,0 +1,38 @@
#include "override_printing_warning.h"
#include "../../card_picture_loader/card_picture_loader.h"
#include "../../client/settings/cache_settings.h"
bool OverridePrintingWarning::execMessageBox(QWidget *parent, bool enable)
{
QString message;
if (enable) {
message =
QObject::tr("Enabling this feature will disable the use of the Printing Selector.\n\n"
"You will not be able to manage printing preferences on a per-deck basis, "
"or see printings other people have selected for their decks.\n\n"
"You will have to use the Set Manager, available through Card Database -> Manage Sets.\n\n"
"Are you sure you would like to enable this feature?");
} else {
message = QObject::tr(
"Disabling this feature will enable the Printing Selector.\n\n"
"You can now choose printings on a per-deck basis in the Deck Editor and configure which printing "
"gets added to a deck by default by pinning it in the Printing Selector.\n\n"
"You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector"
" (other sort orders like alphabetical or release date are available).\n\n"
"Are you sure you would like to disable this feature?");
}
QMessageBox::StandardButton result =
QMessageBox::question(parent, QObject::tr("Confirm Change"), message, QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::Yes) {
SettingsCache::instance().setOverrideAllCardArtWithPersonalPreference(static_cast<Qt::CheckState>(enable));
// Caches are now invalid.
CardPictureLoader::clearPixmapCache();
CardPictureLoader::clearNetworkCache();
return true;
}
return false;
}

View File

@@ -0,0 +1,20 @@
#ifndef COCKATRICE_OVERRIDE_PRINTING_WARN_H
#define COCKATRICE_OVERRIDE_PRINTING_WARN_H
#include <QMessageBox>
namespace OverridePrintingWarning
{
/**
* @brief Pops up the warning message for the changing the override printing setting.
* If the user clicks accept, then this also handles changing the setting and resetting the card image cache.
*
* @param parent The parent widget
* @param enable Whether the user is trying to enable or disable the setting
* @return Whether the user clicked accept. All other ways of closing the window returns false.
*/
bool execMessageBox(QWidget *parent, bool enable);
} // namespace OverridePrintingWarning
#endif // COCKATRICE_OVERRIDE_PRINTING_WARN_H

View File

@@ -1,18 +1,21 @@
#include "color_bar.h"
#include "libcockatrice/utility/color.h"
#include <QLinearGradient>
#include <QMouseEvent>
#include <QPainter>
#include <QToolTip>
ColorBar::ColorBar(const QMap<QString, int> &_colors, QWidget *parent) : QWidget(parent), colors(_colors)
ColorBar::ColorBar(const QMap<QString, int> &_colors, QWidget *parent)
: QWidget(parent), colors(GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors))
{
setMouseTracking(true);
}
void ColorBar::setColors(const QMap<QString, int> &_colors)
{
colors = _colors;
colors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors);
update();
}
@@ -27,8 +30,8 @@ void ColorBar::paintEvent(QPaintEvent *)
return;
int total = 0;
for (int v : colors.values())
total += v;
for (const auto &pair : colors)
total += pair.second;
// Prevent divide-by-zero
if (total == 0)
@@ -50,15 +53,9 @@ void ColorBar::paintEvent(QPaintEvent *)
// Clip to inside the border
p.setClipRect(bounds.adjusted(2, 2, -2, -2));
// Ensure predictable order
QList<QString> sortedKeys = colors.keys();
std::sort(sortedKeys.begin(), sortedKeys.end()); // Sort alphabetically
// Draw each color segment in the sorted order
for (const QString &key : sortedKeys) {
int value = colors[key];
double ratio = double(value) / total;
// Draw segments IN ORDER
for (const auto &[key, value] : colors) {
const double ratio = double(value) / total;
if (ratio <= minRatioThreshold) {
continue;
}
@@ -122,20 +119,21 @@ void ColorBar::mouseMoveEvent(QMouseEvent *event)
QString ColorBar::tooltipForPosition(int x) const
{
int total = 0;
for (int v : colors.values())
total += v;
for (const auto &pair : colors)
total += pair.second;
if (total == 0)
return {};
int pos = 0;
for (auto it = colors.cbegin(); it != colors.cend(); ++it) {
const double ratio = double(it.value()) / total;
for (const auto &[key, value] : colors) {
const double ratio = double(value) / total;
const int segmentWidth = int(ratio * width());
if (x >= pos && x < pos + segmentWidth) {
const double percent = (100.0 * it.value()) / total;
return QString("%1: %2 cards (%3%)").arg(it.key()).arg(it.value()).arg(QString::number(percent, 'f', 1));
const double percent = (100.0 * value) / total;
return QString("%1: %2 cards (%3%)").arg(key).arg(value).arg(QString::number(percent, 'f', 1));
}
pos += segmentWidth;

View File

@@ -100,7 +100,7 @@ protected:
private:
/// Map of color keys to counts used for rendering.
QMap<QString, int> colors;
QList<QPair<QString, int>> colors;
/// True if the mouse is currently inside the widget.
bool isHovered = false;

View File

@@ -1,18 +1,21 @@
#include "color_pie.h"
#include "libcockatrice/utility/color.h"
#include <QMouseEvent>
#include <QPainter>
#include <QToolTip>
#include <QtMath>
ColorPie::ColorPie(const QMap<QString, int> &_colors, QWidget *parent) : QWidget(parent), colors(_colors)
ColorPie::ColorPie(const QMap<QString, int> &_colors, QWidget *parent)
: QWidget(parent), colors(GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors))
{
setMouseTracking(true);
}
void ColorPie::setColors(const QMap<QString, int> &_colors)
{
colors = _colors;
colors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors);
update();
}
@@ -28,8 +31,8 @@ void ColorPie::paintEvent(QPaintEvent *)
}
int total = 0;
for (int v : colors.values()) {
total += v;
for (const auto &pair : colors) {
total += pair.second;
}
if (total == 0) {
@@ -41,24 +44,18 @@ void ColorPie::paintEvent(QPaintEvent *)
int w = width();
int h = height();
int size = qMin(w, h) - 40; // leave space for labels
int size = qMin(w, h) - 40;
QRectF rect((w - size) / 2.0, (h - size) / 2.0, size, size);
// Draw border
// Border
p.setPen(QPen(Qt::black, 1));
p.setBrush(Qt::NoBrush);
p.drawEllipse(rect);
// Sorted keys for predictable order
QList<QString> sortedKeys = colors.keys();
std::sort(sortedKeys.begin(), sortedKeys.end());
double startAngle = 0.0;
for (const QString &key : sortedKeys) {
int value = colors[key];
for (const auto &[key, value] : colors) {
double ratio = double(value) / total;
if (ratio <= minRatioThreshold) {
continue;
}
@@ -67,20 +64,18 @@ void ColorPie::paintEvent(QPaintEvent *)
QColor base = colorFromName(key);
// Gradient
QRadialGradient grad(rect.center(), size / 2);
grad.setColorAt(0, base.lighter(130));
grad.setColorAt(1, base.darker(130));
p.setBrush(grad);
p.setPen(Qt::NoPen);
// Draw slice
p.drawPie(rect, int(startAngle * 16), int(spanAngle * 16));
// Draw percent label
double midAngle = startAngle + spanAngle / 2;
// Percent label
double midAngle = startAngle + spanAngle / 2.0;
double rad = qDegreesToRadians(midAngle);
double labelRadius = size / 2 + 15; // slightly outside the pie
double labelRadius = size / 2 + 15;
QPointF center = rect.center();
QPointF labelPos(center.x() + labelRadius * qCos(rad), center.y() - labelRadius * qSin(rad));
@@ -147,10 +142,13 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const
}
int total = 0;
for (int v : colors.values())
total += v;
if (total == 0)
for (const auto &pair : colors) {
total += pair.second;
}
if (total == 0) {
return {};
}
int w = width();
int h = height();
@@ -158,9 +156,9 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const
QPointF center(w / 2.0, h / 2.0);
QPointF v = pt - center;
double distance = std::sqrt(v.x() * v.x() + v.y() * v.y());
double distance = std::hypot(v.x(), v.y());
if (distance > size / 2.0)
return {}; // outside pie
return {};
double angle = std::atan2(-v.y(), v.x()) * 180.0 / M_PI;
if (angle < 0) {
@@ -169,16 +167,19 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const
double acc = 0.0;
QList<QString> keys = colors.keys();
std::sort(keys.begin(), keys.end());
for (const auto &[key, value] : colors) {
double ratio = double(value) / total;
if (ratio <= minRatioThreshold) {
continue;
}
for (const QString &key : keys) {
double span = (double(colors[key]) / total) * 360.0;
double span = ratio * 360.0;
if (angle >= acc && angle < acc + span) {
double percent = (100.0 * colors[key]) / total;
return QString("%1: %2 cards (%3%)").arg(key).arg(colors[key]).arg(QString::number(percent, 'f', 1));
double percent = (100.0 * value) / total;
return QString("%1: %2 cards (%3%)").arg(key).arg(value).arg(QString::number(percent, 'f', 1));
}
acc += span;
}

View File

@@ -31,7 +31,7 @@ protected:
void mouseMoveEvent(QMouseEvent *event) override;
private:
QMap<QString, int> colors;
QList<QPair<QString, int>> colors;
bool isHovered = false;
const double minRatioThreshold = 0.01; // skip tiny slices

View File

@@ -44,6 +44,8 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
&HomeWidget::initializeBackgroundFromSource);
connect(&SettingsCache::instance(), &SettingsCache::homeTabBackgroundShuffleFrequencyChanged, this,
&HomeWidget::onBackgroundShuffleFrequencyChanged);
// Lambda is cleaner to read than overloading this
connect(&SettingsCache::instance(), &SettingsCache::homeTabDisplayCardNameChanged, this, [this] { repaint(); });
}
void HomeWidget::initializeBackgroundFromSource()
@@ -76,10 +78,9 @@ void HomeWidget::initializeBackgroundFromSource()
void HomeWidget::loadBackgroundSourceDeck()
{
DeckLoader deckLoader = DeckLoader(this);
deckLoader.loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", DeckFileFormat::Cockatrice,
false);
backgroundSourceDeck = deckLoader.getDeck().deckList;
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromFile(
SettingsCache::instance().getDeckPath() + "background.cod", DeckFileFormat::Cockatrice, false);
backgroundSourceDeck = deckOpt.has_value() ? deckOpt.value().deckList : DeckList();
}
void HomeWidget::updateRandomCard()
@@ -297,6 +298,7 @@ QPair<QColor, QColor> HomeWidget::extractDominantColors(const QPixmap &pixmap)
void HomeWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
background = background.scaled(size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
@@ -308,13 +310,47 @@ void HomeWidget::paintEvent(QPaintEvent *event)
painter.drawPixmap(topLeft, background);
// Draw translucent black overlay with rounded corners
QRectF overlayRect(5, 5, width() - 10, height() - 10); // 5px inset
QRectF overlayRect(5, 5, width() - 10, height() - 10);
QPainterPath roundedRectPath;
roundedRectPath.addRoundedRect(overlayRect, 20, 20); // 20px corner radius
roundedRectPath.addRoundedRect(overlayRect, 20, 20);
QColor semiTransparentBlack(0, 0, 0, static_cast<int>(255 * 0.33)); // 33% opacity
painter.setRenderHint(QPainter::Antialiasing);
QColor semiTransparentBlack(0, 0, 0, static_cast<int>(255 * 0.33));
painter.fillPath(roundedRectPath, semiTransparentBlack);
// Card name overlay (bottom-right)
QString cardName;
ExactCard card = backgroundSourceCard->getCard();
if (card) {
cardName = card.getCardPtr()->getName() + " (" + card.getPrinting().getSet()->getCorrectedShortName() + ") " +
card.getPrinting().getProperty("num");
}
if (!cardName.isEmpty() && SettingsCache::instance().getHomeTabDisplayCardName()) {
QFont font = painter.font();
font.setPointSize(14);
font.setBold(true);
painter.setFont(font);
QFontMetrics fm(font);
constexpr int padding = 10;
constexpr int margin = 15;
QRect textRect = fm.boundingRect(cardName);
QRect bgRect(width() - textRect.width() - padding * 2 - margin,
height() - textRect.height() - padding * 2 - margin, textRect.width() + padding * 2,
textRect.height() + padding * 2);
// Background bubble
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 0, 0, 160));
painter.drawRoundedRect(bgRect, 8, 8);
// Text
painter.setPen(Qt::white);
painter.drawText(bgRect.adjusted(padding, padding, -padding, -padding), Qt::AlignRight | Qt::AlignVCenter,
cardName);
}
QWidget::paintEvent(event);
}

View File

@@ -152,7 +152,7 @@ static QModelIndex addAndReplacePrintings(DeckListModel *model,
// Check if a card without a providerId already exists in the deckModel and replace it, if so.
if (existing.isValid() && existing != newCardIndex && replaceProviderless) {
model->offsetCountAtIndex(newCardIndex, extraCopies);
model->removeRow(existing.row(), existing.parent());
model->removeCardAtIndex(existing);
}
// Set Index and Focus as if the user had just clicked the new card and modify the deckEditor saveState
@@ -198,7 +198,7 @@ void CardAmountWidget::addPrinting(const QString &zone)
});
if (newCardIndex.isValid()) {
emit deckStateManager->focusIndexChanged(newCardIndex);
emit deckStateManager->focusIndexChanged(newCardIndex, false);
}
}

View File

@@ -8,6 +8,7 @@
#include "printing_selector_card_search_widget.h"
#include "printing_selector_card_selection_widget.h"
#include "printing_selector_card_sorting_widget.h"
#include "printing_selector_placeholder_widget.h"
#include <QBoxLayout>
#include <QScrollBar>
@@ -34,6 +35,8 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
placeholderWidget = new PrintingSelectorPlaceholderWidget(this);
sortToolBar = new PrintingSelectorCardSortingWidget(this);
sortToolBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
@@ -70,8 +73,13 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck
layout->addWidget(sortAndOptionsContainer);
layout->addWidget(placeholderWidget);
layout->addWidget(flowWidget);
// Initially show placeholder, hide flowWidget
placeholderWidget->setVisible(true);
flowWidget->setVisible(false);
cardSelectionBar = new PrintingSelectorCardSelectionWidget(this, deckStateManager);
cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible());
layout->addWidget(cardSelectionBar);
@@ -90,8 +98,10 @@ void PrintingSelector::retranslateUi()
void PrintingSelector::printingsInDeckChanged()
{
// Delay the update to avoid race conditions
QTimer::singleShot(100, this, &PrintingSelector::updateDisplay);
if (SettingsCache::instance().getBumpSetsWithCardsInDeckToTop()) {
// Delay the update to avoid race conditions
QTimer::singleShot(100, this, &PrintingSelector::updateDisplay);
}
}
/**
@@ -120,6 +130,10 @@ static QMap<QString, QPair<int, int>> tallyUuidCounts(const DeckListModel *model
void PrintingSelector::updateCardAmounts()
{
if (selectedCard.isNull()) {
return;
}
auto map = tallyUuidCounts(deckStateManager->getModel(), selectedCard->getName());
emit cardAmountsChanged(map);
}
@@ -133,7 +147,16 @@ void PrintingSelector::updateDisplay()
widgetLoadingBufferTimer->deleteLater();
widgetLoadingBufferTimer = new QTimer(this);
flowWidget->clearLayout();
if (selectedCard != nullptr) {
if (selectedCard.isNull()) {
// Show placeholder, hide flowWidget
placeholderWidget->setVisible(true);
flowWidget->setVisible(false);
setWindowTitle(tr("Printing Selector"));
} else {
// Hide placeholder, show flowWidget
placeholderWidget->setVisible(false);
flowWidget->setVisible(true);
setWindowTitle(selectedCard->getName());
}
getAllSetsForCurrentCard();
@@ -147,6 +170,8 @@ void PrintingSelector::updateDisplay()
void PrintingSelector::setCard(const CardInfoPtr &newCard)
{
if (newCard.isNull()) {
selectedCard = newCard;
updateDisplay();
return;
}
@@ -223,4 +248,4 @@ void PrintingSelector::getAllSetsForCurrentCard()
void PrintingSelector::toggleVisibilityNavigationButtons(bool _state)
{
cardSelectionBar->setVisible(_state);
}
}

View File

@@ -10,6 +10,7 @@
#include "../cards/card_size_widget.h"
#include "../general/layout_containers/flow_widget.h"
#include "../quick_settings/settings_button_widget.h"
#include "printing_selector_placeholder_widget.h"
#include <QCheckBox>
#include <QLabel>
@@ -70,6 +71,7 @@ private:
QCheckBox *navigationCheckBox;
PrintingSelectorCardSortingWidget *sortToolBar;
PrintingSelectorCardSearchWidget *searchBar;
PrintingSelectorPlaceholderWidget *placeholderWidget;
FlowWidget *flowWidget;
CardSizeWidget *cardSizeWidget;
PrintingSelectorCardSelectionWidget *cardSelectionBar;

View File

@@ -21,6 +21,7 @@ PrintingSelectorCardSortingWidget::PrintingSelectorCardSortingWidget(PrintingSel
{
setMinimumWidth(300);
sortToolBar = new QHBoxLayout(this);
sortToolBar->setContentsMargins(11, 0, 11, 0);
sortOptionsSelector = new QComboBox(this);
sortOptionsSelector->setFocusPolicy(Qt::StrongFocus);

View File

@@ -0,0 +1,42 @@
#include "printing_selector_placeholder_widget.h"
PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *parent) : QWidget(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mainLayout = new QVBoxLayout(this);
mainLayout->setAlignment(Qt::AlignCenter);
setLayout(mainLayout);
// Image label with the background image
imageLabel = new QLabel(this);
imageLabel->setAlignment(Qt::AlignCenter);
imageLabel->setStyleSheet(R"(
QLabel {
background-image: url(theme:backgrounds/placeholder_printing_selector.svg);
background-repeat: no-repeat;
background-position: center;
}
)");
imageLabel->setFixedSize(300, 300);
textLabel = new QLabel(this);
textLabel->setAlignment(Qt::AlignCenter);
textLabel->setWordWrap(true);
textLabel->setStyleSheet(R"(
QLabel {
color: palette(mid);
font-size: 14px;
padding: 10px;
}
)");
mainLayout->addWidget(imageLabel);
mainLayout->addWidget(textLabel);
retranslateUi();
}
void PrintingSelectorPlaceholderWidget::retranslateUi()
{
textLabel->setText(tr("Select a card to view its available printings"));
}

View File

@@ -0,0 +1,23 @@
#ifndef COCKATRICE_PRINTING_SELECTOR_PLACEHOLDER_WIDGET_H
#define COCKATRICE_PRINTING_SELECTOR_PLACEHOLDER_WIDGET_H
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
class PrintingSelectorPlaceholderWidget : public QWidget
{
Q_OBJECT
public:
explicit PrintingSelectorPlaceholderWidget(QWidget *parent = nullptr);
private:
QVBoxLayout *mainLayout;
QLabel *imageLabel;
QLabel *textLabel;
void retranslateUi();
};
#endif // COCKATRICE_PRINTING_SELECTOR_PLACEHOLDER_WIDGET_H

View File

@@ -8,7 +8,6 @@
SettingsButtonWidget::SettingsButtonWidget(QWidget *parent)
: QWidget(parent), button(new QToolButton(this)), popup(new SettingsPopupWidget(nullptr))
{
button->setIcon(QPixmap("theme:icons/cogwheel"));
button->setCheckable(true);
button->setFixedSize(32, 32);
@@ -36,6 +35,21 @@ void SettingsButtonWidget::setButtonIcon(QPixmap iconMap)
button->setIcon(iconMap);
}
void SettingsButtonWidget::setButtonText(const QString &buttonText)
{
// 🔓 unlock size constraints
button->setMinimumSize(QSize());
button->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
button->setText(buttonText);
button->setFixedHeight(32);
button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
button->setMinimumWidth(button->sizeHint().width());
}
void SettingsButtonWidget::togglePopup()
{
if (popup->isVisible()) {

View File

@@ -22,6 +22,7 @@ public:
void addSettingsWidget(QWidget *toAdd) const;
void removeSettingsWidget(QWidget *toRemove) const;
void setButtonIcon(QPixmap iconMap);
void setButtonText(const QString &buttonText);
protected:
void mousePressEvent(QMouseEvent *event) override;

View File

@@ -10,6 +10,7 @@ SettingsPopupWidget::SettingsPopupWidget(QWidget *parent) : QWidget(parent, Qt::
{
// Main layout for the popup itself
layout = new QVBoxLayout(this);
layout->setContentsMargins(5, 5, 5, 5);
// Container for the content (with or without scroll area)
containerWidget = new QWidget();

View File

@@ -18,6 +18,7 @@
#include "../interface/widgets/dialogs/dlg_load_deck.h"
#include "../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h"
#include "../interface/widgets/dialogs/dlg_load_deck_from_website.h"
#include "../utility/visibility_change_listener.h"
#include "tab_supervisor.h"
#include <QAction>
@@ -55,40 +56,55 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta
deckStateManager = new DeckStateManager(this);
databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this);
cardDatabaseDockWidget = new DeckEditorCardDatabaseDockWidget(this);
deckDockWidget = new DeckEditorDeckDockWidget(this);
cardInfoDockWidget = new DeckEditorCardInfoDockWidget(this);
filterDockWidget = new DeckEditorFilterDockWidget(this);
printingSelectorDockWidget = new DeckEditorPrintingSelectorDockWidget(this);
connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, [this] {
printingSelectorDockWidget->setHidden(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference());
});
// Connect deck signals to this tab
connect(deckStateManager, &DeckStateManager::isModifiedChanged, this, &AbstractTabDeckEditor::onDeckModified);
connect(deckDockWidget, &DeckEditorDeckDockWidget::selectedCardChanged, this, &AbstractTabDeckEditor::updateCard);
// Connect database display signals to this tab
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this,
&AbstractTabDeckEditor::updateCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, this,
&AbstractTabDeckEditor::actAddCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, this,
&AbstractTabDeckEditor::actAddCardToSideboard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromMainDeck, this,
&AbstractTabDeckEditor::actDecrementCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromSideboard, this,
&AbstractTabDeckEditor::actDecrementCardFromSideboard);
// Connect filter signals
connect(filterDockWidget, &DeckEditorFilterDockWidget::clearAllDatabaseFilters, databaseDisplayDockWidget,
&DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters);
connect(filterDockWidget, &DeckEditorFilterDockWidget::clearAllDatabaseFilters, cardDatabaseDockWidget,
&DeckEditorCardDatabaseDockWidget::clearAllDatabaseFilters);
// Connect shortcut changes
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&AbstractTabDeckEditor::refreshShortcuts);
}
void AbstractTabDeckEditor::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget)
{
QMenu *menu = _viewMenu->addMenu(QString());
QAction *aVisible = menu->addAction(QString());
aVisible->setCheckable(true);
QAction *aFloating = menu->addAction(QString());
aFloating->setCheckable(true);
aFloating->setEnabled(false);
// user interaction
connect(aVisible, &QAction::triggered, widget, [widget](bool checked) { widget->setVisible(checked); });
connect(aFloating, &QAction::triggered, this, [widget](bool checked) { widget->setFloating(checked); });
// sync aFloating's enabled state with aVisible's checked state
connect(aVisible, &QAction::toggled, aFloating, [aFloating](bool checked) { aFloating->setEnabled(checked); });
// sync aFloating with dockWidget's floating state
connect(widget, &QDockWidget::topLevelChanged, aFloating,
[aFloating](bool topLevel) { aFloating->setChecked(topLevel); });
// sync aVisible with dockWidget's visible state
auto filter = new VisibilityChangeListener(widget);
connect(filter, &VisibilityChangeListener::visibilityChanged, aVisible,
[aVisible](bool visible) { aVisible->setChecked(visible); });
dockToActions.insert(widget, {menu, aVisible, aFloating});
}
/**
* @brief Updates the card info dock and printing selector.
* @param card The card to display.
@@ -122,7 +138,7 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, const QString &
{
deckStateManager->addCard(card, zoneName);
databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length());
cardDatabaseDockWidget->highlightAllSearchEdit();
}
/**
@@ -179,8 +195,8 @@ void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck)
deckStateManager->replaceDeck(_deck);
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(_deck.deckList.getCardRefList()));
aDeckDockVisible->setChecked(true);
deckDockWidget->setVisible(aDeckDockVisible->isChecked());
dockToActions.value(deckDockWidget).aVisible->setChecked(true);
deckDockWidget->setVisible(dockToActions.value(deckDockWidget).aVisible->isChecked());
}
/** @brief Creates a new deck. Handles opening in new tab if needed. */
@@ -294,13 +310,13 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo
{
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName);
auto l = DeckLoader(this);
if (l.loadFromFile(fileName, fmt, true)) {
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromFile(fileName, fmt, true);
if (deckOpt) {
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(l.getDeck());
emit openDeckEditor(deckOpt.value());
} else {
deckMenu->setSaveStatus(false);
openDeck(l.getDeck());
openDeck(deckOpt.value());
}
} else {
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName));
@@ -336,9 +352,7 @@ bool AbstractTabDeckEditor::actSaveDeck()
if (loadedDeck.lastLoadInfo.fileName.isEmpty())
return actSaveDeckAs();
auto deckLoader = DeckLoader(this);
deckLoader.setDeck(loadedDeck);
if (deckLoader.saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) {
if (DeckLoader::saveToFile(loadedDeck)) {
deckStateManager->setModified(false);
return true;
}
@@ -355,14 +369,14 @@ bool AbstractTabDeckEditor::actSaveDeck()
*/
bool AbstractTabDeckEditor::actSaveDeckAs()
{
LoadedDeck loadedDeck = deckStateManager->toLoadedDeck();
DeckList deckList = deckStateManager->getDeckList();
QFileDialog dialog(this, tr("Save deck"));
dialog.setDirectory(SettingsCache::instance().getDeckPath());
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setDefaultSuffix("cod");
dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS);
dialog.selectFile(loadedDeck.deckList.getName().trimmed());
dialog.selectFile(deckList.getName().trimmed());
if (!dialog.exec())
return false;
@@ -370,16 +384,15 @@ bool AbstractTabDeckEditor::actSaveDeckAs()
QString fileName = dialog.selectedFiles().at(0);
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName);
DeckLoader deckLoader = DeckLoader(this);
deckLoader.setDeck(loadedDeck);
if (!deckLoader.saveToFile(fileName, fmt)) {
std::optional<LoadedDeck::LoadInfo> infoOpt = DeckLoader::saveToFile(deckList, fileName, fmt);
if (!infoOpt) {
QMessageBox::critical(
this, tr("Error"),
tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
return false;
}
deckStateManager->setLastLoadInfo({.fileName = fileName, .fileFormat = fmt});
deckStateManager->setLastLoadInfo(infoOpt.value());
deckStateManager->setModified(false);
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName);
@@ -544,21 +557,21 @@ void AbstractTabDeckEditor::actExportDeckDecklistXyz()
/** @brief Analyzes the deck using DeckStats. */
void AbstractTabDeckEditor::actAnalyzeDeckDeckstats()
{
auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this);
auto *interface = new DeckStatsInterface(*cardDatabaseDockWidget->getDatabase(), this);
interface->analyzeDeck(deckStateManager->getDeckList());
}
/** @brief Analyzes the deck using TappedOut. */
void AbstractTabDeckEditor::actAnalyzeDeckTappedout()
{
auto *interface = new TappedOutInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this);
auto *interface = new TappedOutInterface(*cardDatabaseDockWidget->getDatabase(), this);
interface->analyzeDeck(deckStateManager->getDeckList());
}
/** @brief Applies a new filter tree to the database display. */
void AbstractTabDeckEditor::filterTreeChanged(FilterTree *filterTree)
{
databaseDisplayDockWidget->setFilterTree(filterTree);
cardDatabaseDockWidget->setFilterTree(filterTree);
}
/**
@@ -571,43 +584,6 @@ void AbstractTabDeckEditor::closeEvent(QCloseEvent *event)
event->accept();
}
/**
* @brief Event filter for dock visibility and geometry changes.
* @param o Object sending the event.
* @param e Event.
* @return False always.
*/
bool AbstractTabDeckEditor::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Close) {
if (o == cardInfoDockWidget) {
aCardInfoDockVisible->setChecked(false);
aCardInfoDockFloating->setEnabled(false);
} else if (o == deckDockWidget) {
aDeckDockVisible->setChecked(false);
aDeckDockFloating->setEnabled(false);
} else if (o == filterDockWidget) {
aFilterDockVisible->setChecked(false);
aFilterDockFloating->setEnabled(false);
} else if (o == printingSelectorDockWidget) {
aPrintingSelectorDockVisible->setChecked(false);
aPrintingSelectorDockFloating->setEnabled(false);
}
}
if (o == this && e->type() == QEvent::Hide) {
LayoutsSettings &layouts = SettingsCache::instance().layouts();
layouts.setDeckEditorLayoutState(saveState());
layouts.setDeckEditorGeometry(saveGeometry());
layouts.setDeckEditorCardSize(cardInfoDockWidget->size());
layouts.setDeckEditorFilterSize(filterDockWidget->size());
layouts.setDeckEditorDeckSize(deckDockWidget->size());
layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size());
}
return false;
}
/** @brief Shows a confirmation dialog before closing. */
bool AbstractTabDeckEditor::confirmClose()
{

View File

@@ -8,6 +8,7 @@
#ifndef TAB_GENERIC_DECK_EDITOR_H
#define TAB_GENERIC_DECK_EDITOR_H
#include "../interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h"
#include "../interface/widgets/deck_editor/deck_editor_card_info_dock_widget.h"
#include "../interface/widgets/deck_editor/deck_editor_database_display_widget.h"
#include "../interface/widgets/deck_editor/deck_editor_deck_dock_widget.h"
@@ -27,7 +28,7 @@ class CardInfoFrameWidget;
class DeckLoader;
class DeckEditorMenu;
class DeckEditorCardInfoDockWidget;
class DeckEditorDatabaseDisplayWidget;
class DeckEditorCardDatabaseDockWidget;
class DeckEditorDeckDockWidget;
class DeckEditorFilterDockWidget;
class DeckEditorPrintingSelectorDockWidget;
@@ -126,7 +127,7 @@ public:
// UI Elements
DeckStateManager *deckStateManager;
DeckEditorMenu *deckMenu; ///< Menu for deck operations
DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget; ///< Database dock
DeckEditorCardDatabaseDockWidget *cardDatabaseDockWidget; ///< Database dock
DeckEditorCardInfoDockWidget *cardInfoDockWidget; ///< Card info dock
DeckEditorDeckDockWidget *deckDockWidget; ///< Deck dock
DeckEditorFilterDockWidget *filterDockWidget; ///< Filter dock
@@ -168,9 +169,6 @@ public slots:
/** @brief Shows the printing selector dock. Pure virtual. */
virtual void showPrintingSelector() = 0;
/** @brief Slot for when a dock's top-level state changes. Pure virtual. */
virtual void dockTopLevelChanged(bool topLevel) = 0;
signals:
/** @brief Emitted when a deck should be opened in a new editor tab. */
void openDeckEditor(const LoadedDeck &deck);
@@ -245,15 +243,6 @@ protected slots:
/** @brief Handles dock close events. */
void closeEvent(QCloseEvent *event) override;
/** @brief Event filter for dock state changes. */
bool eventFilter(QObject *o, QEvent *e) override;
/** @brief Slot triggered when a dock visibility changes. Pure virtual. */
virtual void dockVisibleTriggered() = 0;
/** @brief Slot triggered when a dock floating state changes. Pure virtual. */
virtual void dockFloatingTriggered() = 0;
private:
/** @brief Sets the deck for this tab.
* @param _deck The deck object.
@@ -277,6 +266,22 @@ protected:
NEW_TAB ///< Open deck in a new tab
};
/**
* @brief The actions associated with managing a QDockWidget
*/
struct DockActions
{
QMenu *menu; ///< The menu containing the actions
QAction *aVisible; ///< The menu action that toggles visibility
QAction *aFloating; ///< The menu action that toggles floating
};
/**
* @brief registers a QDockWidget as a managed dock widget. Creates the associated actions and menu, adds them to
* the viewMenu, and connects those actions to the tab's slots.
*/
void registerDockWidget(QMenu *_viewMenu, QDockWidget *widget);
/** @brief Confirms deck open action based on settings and modified state.
* @param openInSameTabIfBlank Whether to reuse same tab if blank.
* @return Selected DeckOpenLocation.
@@ -295,11 +300,11 @@ protected:
virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation);
// UI Menu Elements
QMenu *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *printingSelectorDockMenu;
QMenu *viewMenu;
QAction *aResetLayout;
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating;
QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating;
QMap<QDockWidget *, DockActions> dockToActions;
};
#endif // TAB_GENERIC_DECK_EDITOR_H

View File

@@ -20,12 +20,22 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
layout = new QVBoxLayout(this);
setLayout(layout);
openInEditorButton = new QPushButton(this);
layout->addWidget(openInEditorButton);
navigationContainer = new QWidget(this);
navigationContainerLayout = new QHBoxLayout(navigationContainer);
homeButton = new QPushButton(navigationContainer);
navigationContainerLayout->addWidget(homeButton);
connect(homeButton, &QPushButton::clicked, this, &ArchidektApiResponseDeckDisplayWidget::requestSearch);
openInEditorButton = new QPushButton(navigationContainer);
navigationContainerLayout->addWidget(openInEditorButton);
connect(openInEditorButton, &QPushButton::clicked, this,
&ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor);
layout->addWidget(navigationContainer);
displayOptionsWidget = new VisualDeckDisplayOptionsWidget(this);
layout->addWidget(displayOptionsWidget);
@@ -80,6 +90,7 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
void ArchidektApiResponseDeckDisplayWidget::retranslateUi()
{
homeButton->setText(tr("Back to results"));
openInEditorButton->setText(tr("Open Deck in Deck Editor"));
}

View File

@@ -49,6 +49,7 @@ signals:
* @param url URL of the deck on Archidekt.
*/
void requestNavigation(QString url);
void requestSearch();
/**
* @brief Emitted when the deck should be opened in the deck editor.
@@ -102,9 +103,12 @@ private slots:
void onGroupCriteriaChange(const QString &activeGroupCriteria);
private:
ArchidektApiResponseDeck response; ///< API deck data container
CardSizeWidget *cardSizeSlider; ///< Slider for adjusting card sizes
QVBoxLayout *layout; ///< Main vertical layout
ArchidektApiResponseDeck response; ///< API deck data container
CardSizeWidget *cardSizeSlider; ///< Slider for adjusting card sizes
QVBoxLayout *layout; ///< Main vertical layout
QWidget *navigationContainer;
QHBoxLayout *navigationContainerLayout;
QPushButton *homeButton;
QPushButton *openInEditorButton; ///< Button to open deck in editor
VisualDeckDisplayOptionsWidget *displayOptionsWidget; ///< Controls grouping/sorting/display
QScrollArea *scrollArea; ///< Scrollable area for deck zones

View File

@@ -213,7 +213,7 @@ void ArchidektApiResponseDeckEntryDisplayWidget::updateScaledPreview()
int textMaxWidth = int(newWidth * 0.7); // allow 70% of width for text
QFontMetrics fm(previewWidget->topLeftLabel->font());
QString elided = fm.elidedText(response.getName(), Qt::ElideRight, textMaxWidth);
previewWidget->topLeftLabel->setText(elided);
previewWidget->topLeftLabel->setLabelText(elided);
previewWidget->topLeftLabel->setToolTip(response.getName());
setFixedWidth(newWidth);

View File

@@ -35,6 +35,20 @@ ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsD
layout->addWidget(flowWidget);
}
void ArchidektApiResponseDeckListingsDisplayWidget::append(const ArchidektDeckListingApiResponse &data)
{
for (const auto &deckListing : data.results) {
auto cardListDisplayWidget =
new ArchidektApiResponseDeckEntryDisplayWidget(this, deckListing, imageNetworkManager);
cardListDisplayWidget->setScaleFactor(cardSizeSlider->getSlider()->value());
connect(cardListDisplayWidget, &ArchidektApiResponseDeckEntryDisplayWidget::requestNavigation, this,
&ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation);
connect(cardSizeSlider->getSlider(), &QSlider::valueChanged, cardListDisplayWidget,
&ArchidektApiResponseDeckEntryDisplayWidget::setScaleFactor);
flowWidget->addWidget(cardListDisplayWidget);
}
}
void ArchidektApiResponseDeckListingsDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);

View File

@@ -69,6 +69,7 @@ public:
explicit ArchidektApiResponseDeckListingsDisplayWidget(QWidget *parent,
ArchidektDeckListingApiResponse response,
CardSizeWidget *cardSizeSlider);
void append(const ArchidektDeckListingApiResponse &data);
/**
* @brief Ensures FlowWidget layout properly recomputes on resize.

View File

@@ -9,6 +9,9 @@
#include <QCompleter>
#include <QDebug>
#include <QFormLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QJsonArray>
#include <QJsonDocument>
@@ -18,129 +21,258 @@
#include <QPushButton>
#include <QRegularExpression>
#include <QResizeEvent>
#include <QScrollArea>
#include <QScrollBar>
#include <QUrlQuery>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
#include <libcockatrice/models/database/card/card_search_model.h>
#include <version_string.h>
TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor)
: Tab(_tabSupervisor), currentPage(1), isLoadingMore(false), isListMode(true)
{
// Initialize network
networkManager = new QNetworkAccessManager(this);
networkManager->setTransferTimeout(); // Use Qt's default timeout
networkManager->setTransferTimeout();
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *)));
connect(networkManager, &QNetworkAccessManager::finished, this, &TabArchidekt::processApiJson);
// Initialize debounce timer
searchDebounceTimer = new QTimer(this);
searchDebounceTimer->setSingleShot(true); // We only want it to fire once after inactivity
searchDebounceTimer->setInterval(300); // 300ms debounce
searchDebounceTimer->setSingleShot(true);
searchDebounceTimer->setInterval(300);
connect(searchDebounceTimer, &QTimer::timeout, this, &TabArchidekt::doSearchImmediate);
connect(searchDebounceTimer, &QTimer::timeout, this, [this]() { doSearchImmediate(); });
initializeUi();
setupFilterWidgets();
connectSignals();
retranslateUi();
getTopDecks();
}
void TabArchidekt::initializeUi()
{
// Main container
container = new QWidget(this);
mainLayout = new QVBoxLayout(container);
mainLayout->setContentsMargins(0, 0, 0, 0);
container->setLayout(mainLayout);
mainLayout->setSpacing(0);
navigationContainer = new QWidget(container);
navigationContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
navigationLayout = new QHBoxLayout(navigationContainer);
navigationLayout->setSpacing(3);
navigationContainer->setLayout(navigationLayout);
// Primary toolbar (most important filters)
primaryToolbar = new QWidget(container);
primaryToolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
primaryToolbarLayout = new QHBoxLayout(primaryToolbar);
primaryToolbarLayout->setContentsMargins(6, 6, 6, 6);
primaryToolbarLayout->setSpacing(6);
// Sort by
orderByCombo = new QComboBox(navigationContainer);
// Sort controls
sortByLabel = new QLabel(primaryToolbar);
orderByCombo = new QComboBox(primaryToolbar);
orderByCombo->addItems({"name", "updatedAt", "createdAt", "viewCount", "size", "edhBracket"});
orderByCombo->setCurrentText("updatedAt"); // Pre-select updatedAt
orderByCombo->setCurrentText("updatedAt");
orderByCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
// Asc/Desc toggle
orderDirButton = new QPushButton(tr("Desc."), navigationContainer);
orderDirButton->setCheckable(true); // checked = DESC, unchecked = ASC
orderDirButton = new QPushButton(tr("Desc."), primaryToolbar);
orderDirButton->setCheckable(true);
orderDirButton->setChecked(true);
orderDirButton->setFixedWidth(60);
connect(orderByCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
connect(orderDirButton, &QPushButton::clicked, this, [this](bool checked) {
orderDirButton->setText(checked ? tr("Desc.") : tr("Asc."));
doSearch();
});
// Colors
QHBoxLayout *colorLayout = new QHBoxLayout();
QString colorIdentity = "WUBRG"; // Optionally include "C" for colorless once we have a symbol for it
// Color filter (inline)
QWidget *colorWidget = new QWidget(primaryToolbar);
QHBoxLayout *colorLayout = new QHBoxLayout(colorWidget);
colorLayout->setContentsMargins(0, 0, 0, 0);
colorLayout->setSpacing(2);
QString colorIdentity = "WUBRG";
for (const QChar &color : colorIdentity) {
auto *manaSymbol = new ManaSymbolWidget(navigationContainer, color, false, true);
manaSymbol->setFixedWidth(25);
auto *manaSymbol = new ManaSymbolWidget(colorWidget, color, false, true);
manaSymbol->setFixedSize(28, 28);
colorSymbols.append(manaSymbol);
colorLayout->addWidget(manaSymbol);
connect(manaSymbol, &ManaSymbolWidget::colorToggled, this, [this](QChar c, bool active) {
if (active) {
if (active)
activeColors.insert(c);
} else {
else
activeColors.remove(c);
}
doSearch();
});
}
logicalAndCheck = new QCheckBox("Require ALL colors", navigationContainer);
logicalAndCheck = new QCheckBox(tr("AND"), primaryToolbar);
logicalAndCheck->setToolTip(tr("Require ALL selected colors"));
// Formats
// Common search fields
nameField = new QLineEdit(primaryToolbar);
nameField->setPlaceholderText(tr("Deck name..."));
nameField->setMinimumWidth(150);
formatLabel = new QLabel(this);
ownerField = new QLineEdit(primaryToolbar);
ownerField->setPlaceholderText(tr("Owner..."));
ownerField->setMinimumWidth(120);
formatSettingsWidget = new SettingsButtonWidget(this);
// Filter by label
filterByLabel = new QLabel(primaryToolbar);
// Package toggle
packagesCheck = new QCheckBox(tr("Packages"), primaryToolbar);
// Search button
searchButton = new QPushButton(tr("Search"), primaryToolbar);
searchButton->setDefault(true);
// Advanced filters toggle button
advancedFiltersButton = new QPushButton(tr("Advanced Filters"), primaryToolbar);
advancedFiltersButton->setCheckable(true);
advancedFiltersButton->setChecked(false);
// Settings
settingsButton = new SettingsButtonWidget(primaryToolbar);
cardSizeSlider = new CardSizeWidget(primaryToolbar, nullptr, SettingsCache::instance().getArchidektPreviewSize());
settingsButton->addSettingsWidget(cardSizeSlider);
// Assemble primary toolbar
primaryToolbarLayout->addWidget(sortByLabel);
primaryToolbarLayout->addWidget(orderByCombo);
primaryToolbarLayout->addWidget(orderDirButton);
// Add separator/spacing
primaryToolbarLayout->addSpacing(12);
primaryToolbarLayout->addWidget(filterByLabel);
primaryToolbarLayout->addWidget(colorWidget);
primaryToolbarLayout->addWidget(logicalAndCheck);
primaryToolbarLayout->addWidget(nameField, 1);
primaryToolbarLayout->addWidget(ownerField, 1);
primaryToolbarLayout->addWidget(packagesCheck);
primaryToolbarLayout->addWidget(searchButton, 1);
primaryToolbarLayout->addWidget(advancedFiltersButton);
primaryToolbarLayout->addWidget(settingsButton);
// Secondary toolbar (advanced filters - initially hidden)
secondaryToolbar = new QWidget(container);
secondaryToolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
secondaryToolbar->setVisible(false); // Start hidden
secondaryToolbarLayout = new QHBoxLayout(secondaryToolbar);
secondaryToolbarLayout->setContentsMargins(6, 3, 6, 6);
secondaryToolbarLayout->setSpacing(6);
// Scrollable results area
scrollArea = new QScrollArea(container);
scrollArea->setWidgetResizable(true);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
resultsContainer = new QWidget();
resultsLayout = new QVBoxLayout(resultsContainer);
resultsLayout->setContentsMargins(0, 0, 0, 0);
resultsLayout->setSpacing(0);
scrollArea->setWidget(resultsContainer);
scrollArea->viewport()->installEventFilter(this);
mainLayout->addWidget(primaryToolbar);
mainLayout->addWidget(secondaryToolbar);
mainLayout->addWidget(scrollArea);
setCentralWidget(container);
}
bool TabArchidekt::eventFilter(QObject *obj, QEvent *event)
{
if (obj == scrollArea->viewport() && event->type() == QEvent::Wheel) {
auto *wheelEvent = static_cast<QWheelEvent *>(event);
if (wheelEvent->angleDelta().y() < 0 && !isLoadingMore && isListMode) {
loadNextPage();
wheelEvent->accept();
return false; // allow scrolling
}
}
// Always pass the event to the parent to handle normal scrolling
return QWidget::eventFilter(obj, event);
}
void TabArchidekt::setupFilterWidgets()
{
// Advanced filters (in secondary toolbar)
// EDH Bracket
auto *bracketLabel = new QLabel(tr("Bracket:"), secondaryToolbar);
edhBracketCombo = new QComboBox(secondaryToolbar);
edhBracketCombo->addItem(tr("Any"));
edhBracketCombo->addItems({"1", "2", "3", "4", "5"});
edhBracketCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
// Format filter (collapsible)
formatButton = new SettingsButtonWidget(secondaryToolbar);
formatButton->setButtonText(tr("Formats"));
formatButton->setButtonIcon(QPixmap("theme:icons/scale_balanced"));
QWidget *formatContainer = new QWidget(secondaryToolbar);
QGridLayout *formatLayout = new QGridLayout(formatContainer);
formatLayout->setContentsMargins(4, 4, 4, 4);
QStringList formatNames = {"Standard", "Modern", "Commander", "Legacy", "Vintage",
"Pauper", "Custom", "Frontier", "Future Std", "Penny Dreadful",
"1v1 Commander", "Dual Commander", "Brawl"};
for (int i = 0; i < formatNames.size(); ++i) {
QCheckBox *formatCheckBox = new QCheckBox(formatNames[i], navigationContainer);
connect(formatCheckBox, &QCheckBox::clicked, this, &TabArchidekt::doSearch);
int row = 0, col = 0;
for (const QString &formatName : formatNames) {
auto *formatCheckBox = new QCheckBox(formatName, formatContainer);
formatChecks << formatCheckBox;
formatSettingsWidget->addSettingsWidget(formatCheckBox);
formatLayout->addWidget(formatCheckBox, row, col);
connect(formatCheckBox, &QCheckBox::clicked, this, &TabArchidekt::doSearch);
col++;
if (col >= 3) {
col = 0;
row++;
}
}
// EDH Bracket
edhBracketCombo = new QComboBox(navigationContainer);
edhBracketCombo->addItem(tr("Any Bracket"));
edhBracketCombo->addItems({"1", "2", "3", "4", "5"});
formatButton->addSettingsWidget(formatContainer);
connect(edhBracketCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
cardsField = new QLineEdit(secondaryToolbar);
cardsField->setPlaceholderText(tr("Contains card..."));
cardsField->setMinimumWidth(140);
// Search for Card Packages instead of Decks
packagesCheck = new QCheckBox("Packages", navigationContainer);
commandersField = new QLineEdit(secondaryToolbar);
commandersField->setPlaceholderText(tr("Commander..."));
commandersField->setMinimumWidth(140);
connect(packagesCheck, &QCheckBox::clicked, this, [this]() {
bool disable = packagesCheck->isChecked();
for (auto *cb : formatChecks)
cb->setEnabled(!disable);
commandersField->setEnabled(!disable);
deckTagNameField->setEnabled(!disable);
edhBracketCombo->setCurrentIndex(0);
edhBracketCombo->setEnabled(!disable);
doSearch();
});
deckTagNameField = new QLineEdit(secondaryToolbar);
deckTagNameField->setPlaceholderText(tr("Tag..."));
deckTagNameField->setMinimumWidth(100);
// Deck Name
nameField = new QLineEdit(navigationContainer);
nameField->setPlaceholderText(tr("Deck name contains..."));
// Deck size filter (collapsible)
deckSizeButton = new SettingsButtonWidget(secondaryToolbar);
deckSizeButton->setButtonText(tr("Deck Size"));
// Owner Name
ownerField = new QLineEdit(navigationContainer);
ownerField->setPlaceholderText(tr("Owner name contains..."));
QWidget *sizeContainer = new QWidget(secondaryToolbar);
QHBoxLayout *sizeLayout = new QHBoxLayout(sizeContainer);
sizeLayout->setContentsMargins(4, 4, 4, 4);
// Contained cards
cardsField = new QLineEdit(navigationContainer);
cardsField->setPlaceholderText("Deck contains card...");
minDeckSizeSpin = new QSpinBox(sizeContainer);
minDeckSizeSpin->setSpecialValueText(tr("Any"));
minDeckSizeSpin->setRange(0, 200);
minDeckSizeSpin->setValue(0);
// Commanders
commandersField = new QLineEdit(navigationContainer);
commandersField->setPlaceholderText("Deck has commander...");
minDeckSizeLogicCombo = new QComboBox(sizeContainer);
minDeckSizeLogicCombo->addItems({"Exact", "", ""});
minDeckSizeLogicCombo->setCurrentIndex(1);
// DB supplemented card search
sizeLayout->addWidget(new QLabel(tr("Cards:"), sizeContainer));
sizeLayout->addWidget(minDeckSizeSpin);
sizeLayout->addWidget(minDeckSizeLogicCombo);
deckSizeButton->addSettingsWidget(sizeContainer);
// Setup card name autocomplete
auto cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
auto displayModel = new CardDatabaseDisplayModel(this);
displayModel->setSourceModel(cardDatabaseModel);
@@ -161,144 +293,119 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
cardsField->setCompleter(completer);
commandersField->setCompleter(completer);
connect(cardsField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults);
// Keep autocomplete working for both fields
connect(cardsField, &QLineEdit::textChanged, this, [=](const QString &text) {
searchModel->updateSearchResults(text);
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
if (!text.isEmpty())
completer->complete();
});
connect(commandersField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults);
connect(commandersField, &QLineEdit::textChanged, this, [=](const QString &text) {
searchModel->updateSearchResults(text);
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
if (!text.isEmpty())
completer->complete();
});
// Tag Name
deckTagNameField = new QLineEdit(navigationContainer);
deckTagNameField->setPlaceholderText("Deck tag");
// Assemble secondary toolbar
secondaryToolbarLayout->addWidget(bracketLabel);
secondaryToolbarLayout->addWidget(edhBracketCombo);
secondaryToolbarLayout->addWidget(formatButton);
secondaryToolbarLayout->addWidget(cardsField);
secondaryToolbarLayout->addWidget(commandersField);
secondaryToolbarLayout->addWidget(deckTagNameField);
secondaryToolbarLayout->addWidget(deckSizeButton);
secondaryToolbarLayout->addStretch();
}
connect(deckTagNameField, &QLineEdit::textChanged, this, &TabArchidekt::doSearch);
void TabArchidekt::connectSignals()
{
// Advanced filters toggle
connect(advancedFiltersButton, &QPushButton::clicked, this,
[this](bool checked) { secondaryToolbar->setVisible(checked); });
// Search button
searchPushButton = new QPushButton(navigationContainer);
searchPushButton->setText("Search");
// These trigger immediate search (no debounce needed)
connect(orderByCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
connect(orderDirButton, &QPushButton::clicked, [this](bool checked) {
orderDirButton->setText(checked ? tr("Desc.") : tr("Asc."));
doSearch();
});
connect(searchPushButton, &QPushButton::clicked, this, &TabArchidekt::doSearch);
// Card Size settings
settingsButton = new SettingsButtonWidget(this);
cardSizeSlider = new CardSizeWidget(this, nullptr, SettingsCache::instance().getArchidektPreviewSize());
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(),
&SettingsCache::setArchidektPreviewCardSize);
settingsButton->addSettingsWidget(cardSizeSlider);
// Min deck size
minDeckSizeLabel = new QLabel(navigationContainer);
// Search button triggers immediate search
connect(searchButton, &QPushButton::clicked, this, &TabArchidekt::doSearchImmediate);
minDeckSizeSpin = new QSpinBox(navigationContainer);
minDeckSizeSpin->setSpecialValueText(tr("Disabled"));
minDeckSizeSpin->setRange(0, 200);
minDeckSizeSpin->setValue(0);
// Size logic
minDeckSizeLogicCombo = new QComboBox(navigationContainer);
minDeckSizeLogicCombo->addItems({"Exact", "", ""}); // Exact = unset, ≥ = GTE, ≤ = LTE
minDeckSizeLogicCombo->setCurrentIndex(1); // default GTE
// These trigger search (but not text fields)
connect(logicalAndCheck, &QCheckBox::clicked, this, &TabArchidekt::doSearch);
connect(packagesCheck, &QCheckBox::clicked, [this]() {
updatePackageModeState(packagesCheck->isChecked());
doSearch();
});
// Format filters trigger search
connect(edhBracketCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
connect(minDeckSizeSpin, qOverload<int>(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
// Page number
pageLabel = new QLabel(navigationContainer);
// Allow Enter key in text fields to trigger search
connect(nameField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate);
connect(ownerField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate);
connect(cardsField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate);
connect(commandersField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate);
connect(deckTagNameField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate);
pageSpin = new QSpinBox(navigationContainer);
pageSpin->setRange(1, 9999);
pageSpin->setValue(1);
// Format checkboxes trigger search
for (auto *formatCheck : formatChecks) {
connect(formatCheck, &QCheckBox::clicked, this, &TabArchidekt::doSearch);
}
}
connect(pageSpin, qOverload<int>(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
void TabArchidekt::updatePackageModeState(bool isPackageMode)
{
// Disable format-specific and commander-specific filters in package mode
for (auto *cb : formatChecks) {
cb->setEnabled(!isPackageMode);
}
// Page display
currentPageDisplay = new QWidget(container);
currentPageLayout = new QVBoxLayout(currentPageDisplay);
currentPageLayout->setContentsMargins(0, 0, 0, 0);
currentPageDisplay->setLayout(currentPageLayout);
edhBracketCombo->setEnabled(!isPackageMode);
if (isPackageMode) {
edhBracketCombo->setCurrentIndex(0);
}
// Layout composition
// Sort section
navigationLayout->addWidget(orderByCombo);
navigationLayout->addWidget(orderDirButton);
// Colors section
navigationLayout->addLayout(colorLayout);
navigationLayout->addWidget(logicalAndCheck);
// Formats section
navigationLayout->addWidget(formatSettingsWidget);
navigationLayout->addWidget(formatLabel);
// EDH Bracket
navigationLayout->addWidget(edhBracketCombo);
// Packages toggle
navigationLayout->addWidget(packagesCheck);
// Deck name
navigationLayout->addWidget(nameField);
// Owner name
navigationLayout->addWidget(ownerField);
// Contained cards
navigationLayout->addWidget(cardsField);
// Commanders
navigationLayout->addWidget(commandersField);
// Deck tag
navigationLayout->addWidget(deckTagNameField);
// Search button
navigationLayout->addWidget(searchPushButton);
// Card size settings
navigationLayout->addWidget(settingsButton);
// Min. # of cards in deck
navigationLayout->addWidget(minDeckSizeLabel);
navigationLayout->addWidget(minDeckSizeSpin);
navigationLayout->addWidget(minDeckSizeLogicCombo);
// Page number
navigationLayout->addWidget(pageLabel);
navigationLayout->addWidget(pageSpin);
mainLayout->addWidget(navigationContainer);
mainLayout->addWidget(currentPageDisplay);
// Ensure navigation stays at the top and currentPageDisplay takes remaining space
mainLayout->setStretch(0, 0); // navigationContainer gets minimum space
mainLayout->setStretch(1, 1); // currentPageDisplay expands as much as possible
setCentralWidget(container);
TabArchidekt::retranslateUi();
getTopDecks();
commandersField->setEnabled(!isPackageMode);
deckTagNameField->setEnabled(!isPackageMode);
}
void TabArchidekt::retranslateUi()
{
searchPushButton->setText(tr("Search"));
formatLabel->setText(tr("Formats"));
minDeckSizeLabel->setText(tr("Min. # of Cards:"));
pageLabel->setText(tr("Page:"));
sortByLabel->setText(tr("Sort by:"));
orderDirButton->setText(orderDirButton->isChecked() ? tr("Desc.") : tr("Asc."));
filterByLabel->setText(tr("Filter by:"));
logicalAndCheck->setText(tr("AND"));
logicalAndCheck->setToolTip(tr("Require ALL selected colors"));
nameField->setPlaceholderText(tr("Deck name..."));
ownerField->setPlaceholderText(tr("Owner..."));
packagesCheck->setText(tr("Packages"));
advancedFiltersButton->setText(tr("Advanced Filters"));
cardsField->setPlaceholderText(tr("Contains card..."));
commandersField->setPlaceholderText(tr("Commander..."));
deckTagNameField->setPlaceholderText(tr("Tag..."));
formatButton->setButtonText(tr("Formats"));
deckSizeButton->setButtonText(tr("Deck Size"));
searchButton->setText(tr("Search"));
settingsButton->setToolTip(tr("Display Settings"));
}
QString TabArchidekt::buildSearchUrl()
@@ -306,13 +413,11 @@ QString TabArchidekt::buildSearchUrl()
QUrlQuery query;
// orderBy (field + direction)
{
QString field = orderByCombo->currentText();
if (!field.isEmpty()) {
bool desc = orderDirButton->isChecked();
QString final = desc ? "-" + field : field;
query.addQueryItem("orderBy", final);
}
QString field = orderByCombo->currentText();
if (!field.isEmpty()) {
bool desc = orderDirButton->isChecked();
QString final = desc ? "-" + field : field;
query.addQueryItem("orderBy", final);
}
// Colors
@@ -329,29 +434,26 @@ QString TabArchidekt::buildSearchUrl()
query.addQueryItem("logicalAnd", "true");
}
// Formats
// Formats (disabled in package mode)
if (!packagesCheck->isChecked()) {
QStringList formatIds;
for (int i = 0; i < formatChecks.size(); ++i)
for (int i = 0; i < formatChecks.size(); ++i) {
if (formatChecks[i]->isChecked()) {
formatIds << QString::number(i + 1);
}
}
if (!formatIds.isEmpty()) {
query.addQueryItem("deckFormat", formatIds.join(","));
}
}
// edhBracket
if (!packagesCheck->isChecked()) {
if (!edhBracketCombo->currentText().isEmpty()) {
if (edhBracketCombo->currentText() != tr("Any Bracket")) {
query.addQueryItem("edhBracket", edhBracketCombo->currentText());
}
// edhBracket
if (edhBracketCombo->currentIndex() > 0) {
query.addQueryItem("edhBracket", edhBracketCombo->currentText());
}
}
// Search for card packages instead of decks
// Package mode
if (packagesCheck->isChecked()) {
query.addQueryItem("packages", "true");
}
@@ -361,54 +463,47 @@ QString TabArchidekt::buildSearchUrl()
query.addQueryItem("name", nameField->text());
}
// owner
// Owner
if (!ownerField->text().isEmpty()) {
query.addQueryItem("ownerUsername", ownerField->text());
}
// cards
// Cards
if (!cardsField->text().isEmpty()) {
query.addQueryItem("cardName", cardsField->text());
query.addQueryItem("cards", cardsField->text());
}
// Commander Name
if (!packagesCheck->isChecked()) {
if (!commandersField->text().isEmpty()) {
query.addQueryItem("commanderName", commandersField->text());
}
// Commander (disabled in package mode)
if (!packagesCheck->isChecked() && !commandersField->text().isEmpty()) {
query.addQueryItem("commanderName", commandersField->text());
}
// deckTagName
if (!packagesCheck->isChecked()) {
if (!deckTagNameField->text().isEmpty()) {
query.addQueryItem("deckTagName", deckTagNameField->text());
}
// Deck tag (disabled in package mode)
if (!packagesCheck->isChecked() && !deckTagNameField->text().isEmpty()) {
query.addQueryItem("deckTagName", deckTagNameField->text());
}
// page number
if (pageSpin->value() <= 1) {
query.addQueryItem("page", QString::number(pageSpin->value()));
}
// Page number (for infinite scroll)
query.addQueryItem("page", QString::number(currentPage));
// Min deck size
if (minDeckSizeSpin->value() != 0) {
query.addQueryItem("size", QString::number(minDeckSizeSpin->value()));
QString logic = "GTE"; // default
QString logic = "GTE";
QString selected = minDeckSizeLogicCombo->currentText();
if (selected == "")
logic = "GTE";
else if (selected == "")
logic = "LTE";
else
logic = ""; // Exact = unset
logic = "";
if (!logic.isEmpty()) {
query.addQueryItem("sizeLogic", logic);
}
}
// build final URL
QUrl url("https://archidekt.com/api/decks/v3/");
url.setQuery(query);
@@ -417,7 +512,12 @@ QString TabArchidekt::buildSearchUrl()
void TabArchidekt::doSearch()
{
searchDebounceTimer->start();
// 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()
@@ -428,6 +528,21 @@ void TabArchidekt::doSearchImmediate()
networkManager->get(req);
}
void TabArchidekt::loadNextPage()
{
if (isLoadingMore) {
return;
}
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)
{
QNetworkRequest request{QUrl(url)};
@@ -437,6 +552,7 @@ void TabArchidekt::actNavigatePage(QString url)
void TabArchidekt::getTopDecks()
{
currentPage = 1;
QNetworkRequest request{QUrl(buildSearchUrl())};
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
networkManager->get(request);
@@ -445,7 +561,7 @@ void TabArchidekt::getTopDecks()
void TabArchidekt::processApiJson(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "Network error occurred:" << reply->errorString();
isLoadingMore = false;
reply->deleteLater();
return;
}
@@ -454,17 +570,14 @@ void TabArchidekt::processApiJson(QNetworkReply *reply)
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
if (!jsonDoc.isObject()) {
qDebug() << "Invalid JSON response received.";
isLoadingMore = false;
reply->deleteLater();
return;
}
QJsonObject jsonObj = jsonDoc.object();
// Get the actual URL from the reply
QString responseUrl = reply->url().toString();
// Check if the response URL matches a commander request
if (responseUrl.startsWith("https://archidekt.com/api/decks/v3/")) {
processTopDecksResponse(jsonObj);
} else if (responseUrl.startsWith("https://archidekt.com/api/decks/")) {
@@ -473,6 +586,7 @@ void TabArchidekt::processApiJson(QNetworkReply *reply)
prettyPrintJson(jsonObj, 4);
}
isLoadingMore = false;
reply->deleteLater();
}
@@ -481,28 +595,27 @@ void TabArchidekt::processTopDecksResponse(QJsonObject reply)
ArchidektDeckListingApiResponse deckData;
deckData.fromJson(reply);
// **Remove previous page display to prevent stacking**
if (currentPageDisplay) {
mainLayout->removeWidget(currentPageDisplay);
delete currentPageDisplay;
currentPageDisplay = nullptr;
// New search → clear everything
if (currentPage == 1) {
QLayoutItem *item;
while ((item = resultsLayout->takeAt(0)) != nullptr) {
delete item->widget();
delete item;
}
listingsWidget = new ArchidektApiResponseDeckListingsDisplayWidget(resultsContainer, deckData, cardSizeSlider);
connect(listingsWidget, &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation, this,
&TabArchidekt::actNavigatePage);
resultsLayout->addWidget(listingsWidget);
return;
}
// **Create new currentPageDisplay**
currentPageDisplay = new QWidget(container);
currentPageLayout = new QVBoxLayout(currentPageDisplay);
currentPageDisplay->setLayout(currentPageLayout);
auto display = new ArchidektApiResponseDeckListingsDisplayWidget(currentPageDisplay, deckData, cardSizeSlider);
connect(display, &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation, this,
&TabArchidekt::actNavigatePage);
currentPageLayout->addWidget(display);
mainLayout->addWidget(currentPageDisplay);
// **Ensure layout stays correct**
mainLayout->setStretch(0, 0); // Keep navigationContainer at the top
mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space
// Infinite scroll → append
if (listingsWidget) {
listingsWidget->append(deckData);
}
}
void TabArchidekt::processDeckResponse(QJsonObject reply)
@@ -510,54 +623,47 @@ void TabArchidekt::processDeckResponse(QJsonObject reply)
ArchidektApiResponseDeck deckData;
deckData.fromJson(reply);
// **Remove previous page display to prevent stacking**
if (currentPageDisplay) {
mainLayout->removeWidget(currentPageDisplay);
delete currentPageDisplay;
currentPageDisplay = nullptr;
// We're in single deck mode - disable infinite scroll
isListMode = false;
// Clear existing results for single deck view
QLayoutItem *item;
while ((item = resultsLayout->takeAt(0)) != nullptr) {
delete item->widget();
delete item;
}
// **Create new currentPageDisplay**
currentPageDisplay = new QWidget(container);
currentPageLayout = new QVBoxLayout(currentPageDisplay);
currentPageDisplay->setLayout(currentPageLayout);
auto display = new ArchidektApiResponseDeckDisplayWidget(currentPageDisplay, deckData, cardSizeSlider);
auto display = new ArchidektApiResponseDeckDisplayWidget(resultsContainer, deckData, cardSizeSlider);
connect(display, &ArchidektApiResponseDeckDisplayWidget::requestNavigation, this, &TabArchidekt::actNavigatePage);
connect(display, &ArchidektApiResponseDeckDisplayWidget::requestSearch, this, &TabArchidekt::doSearchImmediate);
connect(display, &ArchidektApiResponseDeckDisplayWidget::openInDeckEditor, tabSupervisor,
&TabSupervisor::openDeckInNewTab);
currentPageLayout->addWidget(display);
mainLayout->addWidget(currentPageDisplay);
// **Ensure layout stays correct**
mainLayout->setStretch(0, 0); // Keep navigationContainer at the top
mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space
resultsLayout->addWidget(display);
}
void TabArchidekt::prettyPrintJson(const QJsonValue &value, int indentLevel)
{
const QString indent(indentLevel * 2, ' '); // Adjust spacing as needed for pretty printing
const QString indent(indentLevel * 2, ' ');
if (value.isObject()) {
QJsonObject obj = value.toObject();
for (auto it = obj.begin(); it != obj.end(); ++it) {
qDebug().noquote() << indent + it.key() + ":";
qInfo().noquote() << indent + it.key() + ":";
prettyPrintJson(it.value(), indentLevel + 1);
}
} else if (value.isArray()) {
QJsonArray array = value.toArray();
for (int i = 0; i < array.size(); ++i) {
qDebug().noquote() << indent + QString("[%1]:").arg(i);
qInfo().noquote() << indent + QString("[%1]:").arg(i);
prettyPrintJson(array[i], indentLevel + 1);
}
} else if (value.isString()) {
qDebug().noquote() << indent + "\"" + value.toString() + "\"";
qInfo().noquote() << indent + "\"" + value.toString() + "\"";
} else if (value.isDouble()) {
qDebug().noquote() << indent + QString::number(value.toDouble());
qInfo().noquote() << indent + QString::number(value.toDouble());
} else if (value.isBool()) {
qDebug().noquote() << indent + (value.toBool() ? "true" : "false");
qInfo().noquote() << indent + (value.toBool() ? "true" : "false");
} else if (value.isNull()) {
qDebug().noquote() << indent + "null";
qInfo().noquote() << indent + "null";
}
}
}

View File

@@ -4,26 +4,35 @@
#include "../../interface/widgets/cards/card_size_widget.h"
#include "../../interface/widgets/quick_settings/settings_button_widget.h"
#include "../../tab.h"
#include "display/archidekt_api_response_deck_listings_display_widget.h"
#include <QCheckBox>
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QNetworkAccessManager>
#include <QPushButton>
#include <QScrollArea>
#include <QSet>
#include <QSpinBox>
#include <QString>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
#include <libcockatrice/card/database/card_database.h>
/** Base API link for Archidekt deck search */
inline QString archidektApiLink = "https://archidekt.com/api/decks/v3/?name=";
class ManaSymbolWidget;
/**
* @brief Tab for browsing, searching, and filtering Archidekt decks.
*
* This class provides a comprehensive interface for querying decks from the Archidekt API.
* Users can filter decks by name, owner, included cards, commanders, deck tags, colors, EDH bracket,
* and formats. It also provides sorting and pagination, as well as a card size adjustment widget.
* and formats. It supports infinite scroll pagination for seamless browsing.
*/
class TabArchidekt : public Tab
{
@@ -63,7 +72,7 @@ public:
* - Packages toggle
* - Sorting field and direction
* - Minimum amount of cards in the deck
* - Pagination (page)
* - Current page (for infinite scroll)
*/
QString buildSearchUrl();
@@ -90,32 +99,45 @@ public:
return cardSizeSlider;
}
/** @brief Network manager for handling API requests */
QNetworkAccessManager *networkManager;
public slots:
/**
* @brief Trigger a search using the current filters
* @brief Trigger a debounced search using the current filters
*
* Sends a network request to the Archidekt API using the URL generated by buildSearchUrl().
* Updates the current page display with results asynchronously.
* Resets to page 1 and starts the debounce timer. The actual search will execute
* after 300ms of inactivity.
*/
void doSearch();
/**
* @brief Immediately trigger a search using the current filters
*
* Sends a network request to the Archidekt API using the URL generated by buildSearchUrl().
* Updates the results display asynchronously.
*/
void doSearchImmediate();
/**
* @brief Load the next page of results for infinite scroll
*
* Increments the current page and fetches additional results, which are appended
* to the existing results display.
*/
void loadNextPage();
/**
* @brief Process a network reply containing JSON data
* @param reply QNetworkReply object with the API response
*
* Determines whether the response corresponds to a top decks query or a single deck,
* Determines whether the response corresponds to a deck listing or a single deck,
* and dispatches it to the appropriate handler.
*/
void processApiJson(QNetworkReply *reply);
/**
* @brief Handle a JSON response containing multiple decks
* @param reply QJsonObject containing top deck listings
* @param reply QJsonObject containing deck listings
*
* Clears the previous page display and creates a new display widget for the results.
* If this is page 1, clears previous results. Appends new results to the display.
*/
void processTopDecksResponse(QJsonObject reply);
@@ -123,7 +145,7 @@ public slots:
* @brief Handle a JSON response for a single deck
* @param reply QJsonObject containing deck data
*
* Clears the previous page display and creates a new display widget for the deck details.
* Clears the results area and displays the single deck details.
*/
void processDeckResponse(QJsonObject reply);
@@ -138,107 +160,129 @@ public slots:
* @brief Navigate to a specified page URL
* @param url The URL to request
*
* Typically called when a navigation button is clicked in a deck listing.
* Typically called when a deck card is clicked in the listing.
*/
void actNavigatePage(QString url);
/**
* @brief Fetch top decks from the Archidekt API
*
* Called on initialization to populate the initial page display.
* Called on initialization to populate the initial results display.
*/
void getTopDecks();
protected:
/**
* @brief Event filter to catch wheel events for infinite scroll
* @param obj The object that received the event
* @param event The event to filter
* @return bool Whether the event was handled
*/
bool eventFilter(QObject *obj, QEvent *event) override;
private:
QTimer *searchDebounceTimer; ///< Timer to debounce search requests by spin-boxes etc.
/**
* @brief Initialize the main UI layout and toolbars
*
* Creates the container, main layout, primary toolbar (sort, colors, name, owner, packages),
* secondary toolbar (advanced filters), and scrollable results area.
*/
void initializeUi();
/**
* @brief Set up all filter widgets
*
* Creates filter widgets for:
* - Card search with autocomplete
* - Commander search with autocomplete
* - Deck tags
* - Format selection (collapsible)
* - Deck size filter (collapsible)
*/
void setupFilterWidgets();
/**
* @brief Connect all signals and slots for UI interactions
*
* Links all widget signals to their appropriate handlers, including
* search triggers, filter changes, package mode toggling, and infinite scroll.
*/
void connectSignals();
/**
* @brief Update UI state when package mode is toggled
* @param isPackageMode Whether package mode is currently enabled
*
* Disables format-specific and commander-specific filters when searching
* for card packages instead of full decks.
*/
void updatePackageModeState(bool isPackageMode);
// ---------------------------------------------------------------------
// Network & Timing
// ---------------------------------------------------------------------
QNetworkAccessManager *networkManager; ///< Network manager for handling API requests
QTimer *searchDebounceTimer; ///< Timer to debounce search requests
int currentPage; ///< Current page number for infinite scroll
bool isLoadingMore; ///< Flag to prevent multiple simultaneous page loads
bool isListMode;
ArchidektApiResponseDeckListingsDisplayWidget *listingsWidget = nullptr;
// ---------------------------------------------------------------------
// Layout Containers
// ---------------------------------------------------------------------
QWidget *container; ///< Root container for the entire tab
QVBoxLayout *mainLayout; ///< Outer vertical layout containing navigation and page display
QWidget *navigationContainer; ///< Container for all navigation/filter controls
QHBoxLayout *navigationLayout; ///< Layout for horizontal arrangement of filter widgets
QWidget *currentPageDisplay; ///< Widget containing the currently displayed deck(s)
QVBoxLayout *currentPageLayout; ///< Layout for deck display widgets
QWidget *container; ///< Root container for the entire tab
QVBoxLayout *mainLayout; ///< Outer vertical layout containing toolbars and results
QWidget *primaryToolbar; ///< Primary toolbar with most important filters
QHBoxLayout *primaryToolbarLayout; ///< Layout for primary toolbar
QWidget *secondaryToolbar; ///< Secondary toolbar with advanced filters
QHBoxLayout *secondaryToolbarLayout; ///< Layout for secondary toolbar
QScrollArea *scrollArea; ///< Scrollable area for results (enables infinite scroll)
QWidget *resultsContainer; ///< Container widget inside scroll area
QVBoxLayout *resultsLayout; ///< Layout for results (decks appended here)
// ---------------------------------------------------------------------
// Sorting Controls
// Primary Toolbar Controls (Most Important)
// ---------------------------------------------------------------------
QLabel *sortByLabel; ///< Label for sort controls
QComboBox *orderByCombo; ///< Dropdown for selecting the sort field
QPushButton *orderDirButton; ///< Toggle button for ascending/descending sort
// ---------------------------------------------------------------------
// Color Filters
// ---------------------------------------------------------------------
QLabel *filterByLabel; ///< Label for filter controls
QList<ManaSymbolWidget *> colorSymbols; ///< Mana symbol toggle buttons
QSet<QChar> activeColors; ///< Set of currently active mana colors
QCheckBox *logicalAndCheck; ///< Require ALL selected colors instead of ANY
QSet<QChar> activeColors; ///< Set of currently active mana colors
QCheckBox *logicalAndCheck; ///< Require ALL selected colors instead of ANY
QLineEdit *nameField; ///< Input for deck name filter
QLineEdit *ownerField; ///< Input for owner name filter
QCheckBox *packagesCheck; ///< Toggle for searching card packages instead of full decks
QPushButton *searchButton; ///< Button to trigger search
QPushButton *advancedFiltersButton; ///< Button to show/hide advanced filters
// ---------------------------------------------------------------------
// Format Filters
// ---------------------------------------------------------------------
QLabel *formatLabel; ///< Label displaying "Formats"
SettingsButtonWidget *formatSettingsWidget; ///< Collapsible widget containing format checkboxes
QVector<QCheckBox *> formatChecks; ///< Individual checkboxes for each format
// ---------------------------------------------------------------------
// EDH Bracket / Package Toggle
// ---------------------------------------------------------------------
QComboBox *edhBracketCombo; ///< Dropdown for EDH bracket selection
QCheckBox *packagesCheck; ///< Toggle for searching card packages instead of full decks
// ---------------------------------------------------------------------
// Basic Search Fields
// ---------------------------------------------------------------------
QLineEdit *nameField; ///< Input for deck name filter
QLineEdit *ownerField; ///< Input for owner name filter
// ---------------------------------------------------------------------
// Card Filters
// ---------------------------------------------------------------------
QLineEdit *cardsField; ///< Input for cards included in the deck (comma-separated)
QLineEdit *commandersField; ///< Input for commander cards (comma-separated)
// ---------------------------------------------------------------------
// Deck Tag
// ---------------------------------------------------------------------
QLineEdit *deckTagNameField; ///< Input for deck tag filtering
// ---------------------------------------------------------------------
// Search Trigger
// ---------------------------------------------------------------------
QPushButton *searchPushButton; ///< Button to trigger the search manually
// ---------------------------------------------------------------------
// UI Settings (Card Size)
// ---------------------------------------------------------------------
SettingsButtonWidget *settingsButton; ///< Container for additional UI settings
SettingsButtonWidget *settingsButton; ///< Container for card size settings
CardSizeWidget *cardSizeSlider; ///< Slider to adjust card size in results
// ---------------------------------------------------------------------
// Minimum Cards in Deck
// Secondary Toolbar Controls (Advanced Filters)
// ---------------------------------------------------------------------
QLabel *minDeckSizeLabel; ///< Label for minimum number of cards per deck
QSpinBox *minDeckSizeSpin; ///< Spinner to select minimum deck size
QComboBox *minDeckSizeLogicCombo; ///< Combo box for the size logic to apply
QLineEdit *cardsField; ///< Input for cards included in the deck
QLineEdit *commandersField; ///< Input for commander cards
QLineEdit *deckTagNameField; ///< Input for deck tag filtering
// ---------------------------------------------------------------------
// Pagination
// ---------------------------------------------------------------------
SettingsButtonWidget *formatButton; ///< Collapsible button for format filters
QVector<QCheckBox *> formatChecks; ///< Individual checkboxes for each format
QComboBox *edhBracketCombo; ///< Dropdown for EDH bracket selection
QLabel *pageLabel; ///< Label for current page selection
QSpinBox *pageSpin; ///< Spinner to select the page number for results
SettingsButtonWidget *deckSizeButton; ///< Collapsible button for deck size filter
QSpinBox *minDeckSizeSpin; ///< Spinner to select minimum deck size
QComboBox *minDeckSizeLogicCombo; ///< Combo box for size comparison logic
// ---------------------------------------------------------------------
// Optional Context
@@ -247,4 +291,4 @@ private:
CardInfoPtr cardToQuery; ///< Optional pre-selected card for initial filtering
};
#endif // COCKATRICE_TAB_ARCHIDEKT_H
#endif // COCKATRICE_TAB_ARCHIDEKT_H

View File

@@ -54,49 +54,14 @@ void TabDeckEditor::createMenus()
viewMenu = new QMenu(this);
cardInfoDockMenu = viewMenu->addMenu(QString());
deckDockMenu = viewMenu->addMenu(QString());
filterDockMenu = viewMenu->addMenu(QString());
printingSelectorDockMenu = viewMenu->addMenu(QString());
// Card Info dock
aCardInfoDockVisible = cardInfoDockMenu->addAction(QString());
aCardInfoDockVisible->setCheckable(true);
connect(aCardInfoDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered);
aCardInfoDockFloating = cardInfoDockMenu->addAction(QString());
aCardInfoDockFloating->setCheckable(true);
connect(aCardInfoDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered);
// Deck dock
aDeckDockVisible = deckDockMenu->addAction(QString());
aDeckDockVisible->setCheckable(true);
connect(aDeckDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered);
aDeckDockFloating = deckDockMenu->addAction(QString());
aDeckDockFloating->setCheckable(true);
connect(aDeckDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered);
// Filter dock
aFilterDockVisible = filterDockMenu->addAction(QString());
aFilterDockVisible->setCheckable(true);
connect(aFilterDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered);
aFilterDockFloating = filterDockMenu->addAction(QString());
aFilterDockFloating->setCheckable(true);
connect(aFilterDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered);
// Printing selector dock
aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockVisible->setCheckable(true);
connect(aPrintingSelectorDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered);
aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockFloating->setCheckable(true);
connect(aPrintingSelectorDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered);
if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) {
printingSelectorDockMenu->setEnabled(false);
}
registerDockWidget(viewMenu, cardDatabaseDockWidget);
registerDockWidget(viewMenu, cardInfoDockWidget);
registerDockWidget(viewMenu, deckDockWidget);
registerDockWidget(viewMenu, filterDockWidget);
registerDockWidget(viewMenu, printingSelectorDockWidget);
connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this,
[this](bool enabled) { printingSelectorDockMenu->setEnabled(!enabled); });
[this](bool enabled) { dockToActions[printingSelectorDockWidget].menu->setEnabled(!enabled); });
viewMenu->addSeparator();
@@ -125,25 +90,25 @@ QString TabDeckEditor::getTabText() const
void TabDeckEditor::retranslateUi()
{
deckMenu->retranslateUi();
cardDatabaseDockWidget->retranslateUi();
cardInfoDockWidget->retranslateUi();
deckDockWidget->retranslateUi();
filterDockWidget->retranslateUi();
printingSelectorDockWidget->retranslateUi();
viewMenu->setTitle(tr("&View"));
cardInfoDockMenu->setTitle(tr("Card Info"));
deckDockMenu->setTitle(tr("Deck"));
filterDockMenu->setTitle(tr("Filters"));
printingSelectorDockMenu->setTitle(tr("Printing"));
aCardInfoDockVisible->setText(tr("Visible"));
aCardInfoDockFloating->setText(tr("Floating"));
aDeckDockVisible->setText(tr("Visible"));
aDeckDockFloating->setText(tr("Floating"));
aFilterDockVisible->setText(tr("Visible"));
aFilterDockFloating->setText(tr("Floating"));
aPrintingSelectorDockVisible->setText(tr("Visible"));
aPrintingSelectorDockFloating->setText(tr("Floating"));
dockToActions[cardDatabaseDockWidget].menu->setTitle(tr("Card Database"));
dockToActions[cardInfoDockWidget].menu->setTitle(tr("Card Info"));
dockToActions[deckDockWidget].menu->setTitle(tr("Deck"));
dockToActions[filterDockWidget].menu->setTitle(tr("Filters"));
dockToActions[printingSelectorDockWidget].menu->setTitle(tr("Printing"));
for (auto &actions : dockToActions.values()) {
actions.aVisible->setText(tr("Visible"));
actions.aFloating->setText(tr("Floating"));
}
aResetLayout->setText(tr("Reset layout"));
}
@@ -161,7 +126,6 @@ void TabDeckEditor::showPrintingSelector()
{
printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr());
printingSelectorDockWidget->printingSelector->updateDisplay();
aPrintingSelectorDockVisible->setChecked(true);
printingSelectorDockWidget->setVisible(true);
}
@@ -171,7 +135,6 @@ void TabDeckEditor::showPrintingSelector()
void TabDeckEditor::loadLayout()
{
LayoutsSettings &layouts = SettingsCache::instance().layouts();
setCentralWidget(databaseDisplayDockWidget);
auto &layoutState = layouts.getDeckEditorLayoutState();
if (layoutState.isNull())
@@ -181,27 +144,8 @@ void TabDeckEditor::loadLayout()
restoreGeometry(layouts.getDeckEditorGeometry());
}
if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) {
if (!printingSelectorDockWidget->isHidden()) {
printingSelectorDockWidget->setHidden(true);
aPrintingSelectorDockVisible->setChecked(false);
}
}
aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden());
aFilterDockVisible->setChecked(!filterDockWidget->isHidden());
aDeckDockVisible->setChecked(!deckDockWidget->isHidden());
aPrintingSelectorDockVisible->setChecked(!printingSelectorDockWidget->isHidden());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
aCardInfoDockFloating->setChecked(cardInfoDockWidget->isFloating());
aFilterDockFloating->setChecked(filterDockWidget->isFloating());
aDeckDockFloating->setChecked(deckDockWidget->isFloating());
aPrintingSelectorDockFloating->setChecked(printingSelectorDockWidget->isFloating());
cardDatabaseDockWidget->setMinimumSize(layouts.getDeckEditorCardDatabaseSize());
cardDatabaseDockWidget->setMaximumSize(layouts.getDeckEditorCardDatabaseSize());
cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize());
cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize());
@@ -223,35 +167,18 @@ void TabDeckEditor::loadLayout()
*/
void TabDeckEditor::restartLayout()
{
// Show/hide and reset floating
for (auto dockWidget : dockToActions.keys()) {
dockWidget->setVisible(true);
dockWidget->setFloating(false);
}
// Update menu checkboxes
aCardInfoDockVisible->setChecked(true);
aDeckDockVisible->setChecked(true);
aFilterDockVisible->setChecked(true);
aPrintingSelectorDockVisible->setChecked(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference());
aCardInfoDockFloating->setChecked(false);
aDeckDockFloating->setChecked(false);
aFilterDockFloating->setChecked(false);
aPrintingSelectorDockFloating->setChecked(false);
setCentralWidget(databaseDisplayDockWidget);
addDockWidget(Qt::LeftDockWidgetArea, cardDatabaseDockWidget);
addDockWidget(Qt::RightDockWidgetArea, deckDockWidget);
addDockWidget(Qt::RightDockWidgetArea, cardInfoDockWidget);
addDockWidget(Qt::RightDockWidgetArea, filterDockWidget);
addDockWidget(Qt::RightDockWidgetArea, printingSelectorDockWidget);
// Show/hide and reset floating
deckDockWidget->setFloating(false);
cardInfoDockWidget->setFloating(false);
filterDockWidget->setFloating(false);
printingSelectorDockWidget->setFloating(false);
deckDockWidget->setVisible(true);
cardInfoDockWidget->setVisible(true);
filterDockWidget->setVisible(true);
printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference());
splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal);
splitDockWidget(printingSelectorDockWidget, deckDockWidget, Qt::Horizontal);
splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal);
@@ -266,66 +193,12 @@ void TabDeckEditor::freeDocksSize()
const QSize minSize(100, 100);
const QSize maxSize(5000, 5000);
deckDockWidget->setMinimumSize(minSize);
deckDockWidget->setMaximumSize(maxSize);
cardInfoDockWidget->setMinimumSize(minSize);
cardInfoDockWidget->setMaximumSize(maxSize);
filterDockWidget->setMinimumSize(minSize);
filterDockWidget->setMaximumSize(maxSize);
printingSelectorDockWidget->setMinimumSize(minSize);
printingSelectorDockWidget->setMaximumSize(maxSize);
}
/** @brief Handles dock visibility toggling from menu actions. */
void TabDeckEditor::dockVisibleTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockVisible) {
cardInfoDockWidget->setHidden(!aCardInfoDockVisible->isChecked());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
} else if (o == aDeckDockVisible) {
deckDockWidget->setHidden(!aDeckDockVisible->isChecked());
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
} else if (o == aFilterDockVisible) {
filterDockWidget->setHidden(!aFilterDockVisible->isChecked());
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
} else if (o == aPrintingSelectorDockVisible) {
printingSelectorDockWidget->setHidden(!aPrintingSelectorDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
for (auto dockWidget : dockToActions.keys()) {
dockWidget->setMinimumSize(minSize);
dockWidget->setMaximumSize(maxSize);
}
}
/** @brief Handles dock floating toggling from menu actions. */
void TabDeckEditor::dockFloatingTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockFloating)
cardInfoDockWidget->setFloating(aCardInfoDockFloating->isChecked());
else if (o == aDeckDockFloating)
deckDockWidget->setFloating(aDeckDockFloating->isChecked());
else if (o == aFilterDockFloating)
filterDockWidget->setFloating(aFilterDockFloating->isChecked());
else if (o == aPrintingSelectorDockFloating)
printingSelectorDockWidget->setFloating(aPrintingSelectorDockFloating->isChecked());
}
/** @brief Syncs menu state with dock floating changes. */
void TabDeckEditor::dockTopLevelChanged(bool topLevel)
{
QObject *o = sender();
if (o == cardInfoDockWidget)
aCardInfoDockFloating->setChecked(topLevel);
else if (o == deckDockWidget)
aDeckDockFloating->setChecked(topLevel);
else if (o == filterDockWidget)
aFilterDockFloating->setChecked(topLevel);
else if (o == printingSelectorDockWidget)
aPrintingSelectorDockFloating->setChecked(topLevel);
}
/**
* @brief Handles close/hide events to update menu state and save layout.
* @param o Object sending the event.
@@ -334,26 +207,11 @@ void TabDeckEditor::dockTopLevelChanged(bool topLevel)
*/
bool TabDeckEditor::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Close) {
if (o == cardInfoDockWidget) {
aCardInfoDockVisible->setChecked(false);
aCardInfoDockFloating->setEnabled(false);
} else if (o == deckDockWidget) {
aDeckDockVisible->setChecked(false);
aDeckDockFloating->setEnabled(false);
} else if (o == filterDockWidget) {
aFilterDockVisible->setChecked(false);
aFilterDockFloating->setEnabled(false);
} else if (o == printingSelectorDockWidget) {
aPrintingSelectorDockVisible->setChecked(false);
aPrintingSelectorDockFloating->setEnabled(false);
}
}
if (o == this && e->type() == QEvent::Hide) {
LayoutsSettings &layouts = SettingsCache::instance().layouts();
layouts.setDeckEditorLayoutState(saveState());
layouts.setDeckEditorGeometry(saveGeometry());
layouts.setDeckEditorCardDatabaseSize(cardDatabaseDockWidget->size());
layouts.setDeckEditorCardSize(cardInfoDockWidget->size());
layouts.setDeckEditorFilterSize(filterDockWidget->size());
layouts.setDeckEditorDeckSize(deckDockWidget->size());

View File

@@ -70,9 +70,6 @@ protected slots:
/** @brief Handles dock visibility, floating, and top-level changes. */
bool eventFilter(QObject *o, QEvent *e) override;
void dockVisibleTriggered() override;
void dockFloatingTriggered() override;
void dockTopLevelChanged(bool topLevel) override;
public:
/**

View File

@@ -241,11 +241,11 @@ void TabDeckStorage::actOpenLocalDeck()
continue;
QString filePath = localDirModel->filePath(curLeft);
auto deckLoader = new DeckLoader(this);
if (!deckLoader->loadFromFile(filePath, DeckFileFormat::Cockatrice, true))
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromFile(filePath, DeckFileFormat::Cockatrice, true);
if (!deckOpt)
continue;
emit openDeckEditor(deckLoader->getDeck());
emit openDeckEditor(deckOpt.value());
}
}
@@ -307,13 +307,13 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa
QFile deckFile(filePath);
QFileInfo deckFileInfo(deckFile);
DeckLoader deckLoader(this);
if (!deckLoader.loadFromFile(filePath, DeckFileFormat::Cockatrice)) {
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromFile(filePath, DeckFileFormat::Cockatrice, true);
if (!deckOpt) {
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
return;
}
DeckList deck = deckLoader.getDeck().deckList;
DeckList deck = deckOpt.value().deckList;
if (deck.getName().isEmpty()) {
bool ok;
@@ -434,11 +434,11 @@ void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandCont
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
const Command_DeckDownload &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDownload::ext);
DeckLoader loader(this);
if (!loader.loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id()))
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id());
if (!deckOpt)
return;
emit openDeckEditor(loader.getDeck());
emit openDeckEditor(deckOpt.value());
}
void TabDeckStorage::actDownload()
@@ -496,10 +496,7 @@ void TabDeckStorage::downloadFinished(const Response &r,
DeckList deckList = DeckList(QString::fromStdString(resp.deck()));
DeckLoader deckLoader(this);
deckLoader.setDeck({deckList, {}});
deckLoader.saveToFile(filePath, DeckFileFormat::Cockatrice);
DeckLoader::saveToFile(deckList, filePath, DeckFileFormat::Cockatrice);
}
void TabDeckStorage::actNewFolder()

View File

@@ -20,6 +20,7 @@
#include "../interface/widgets/utility/line_edit_completer.h"
#include "../interface/window_main.h"
#include "../main.h"
#include "../utility/visibility_change_listener.h"
#include "tab_supervisor.h"
#include <QAction>
@@ -97,12 +98,11 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
createMessageDock();
createPlayAreaWidget();
createDeckViewContainerWidget();
createReplayDock(nullptr);
replayDock = nullptr;
addDockWidget(Qt::RightDockWidgetArea, cardInfoDock);
addDockWidget(Qt::RightDockWidgetArea, playerListDock);
addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock);
replayDock->setHidden(true);
mainWidget = new QStackedWidget(this);
mainWidget->addWidget(deckViewContainerWidget);
@@ -256,13 +256,15 @@ void TabGame::emitUserEvent()
TabGame::~TabGame()
{
delete replayManager->replay;
if (replayManager) {
delete replayManager->replay;
}
}
void TabGame::updatePlayerListDockTitle()
{
QString tabText = " | " + (replayManager->replay ? tr("Replay") : tr("Game")) + " #" +
QString::number(game->getGameMetaInfo()->gameId());
QString type = replayDock ? tr("Replay") : tr("Game");
QString tabText = " | " + type + " #" + QString::number(game->getGameMetaInfo()->gameId());
QString userCountInfo =
QString(" %1/%2").arg(game->getPlayerManager()->getPlayerCount()).arg(game->getGameMetaInfo()->maxPlayers());
playerListDock->setWindowTitle(tr("Player List") + userCountInfo +
@@ -271,8 +273,8 @@ void TabGame::updatePlayerListDockTitle()
void TabGame::retranslateUi()
{
QString tabText = " | " + (replayManager->replay ? tr("Replay") : tr("Game")) + " #" +
QString::number(game->getGameMetaInfo()->gameId());
QString type = replayDock ? tr("Replay") : tr("Game");
QString tabText = " | " + type + " #" + QString::number(game->getGameMetaInfo()->gameId());
updatePlayerListDockTitle();
cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString()));
@@ -318,7 +320,7 @@ void TabGame::retranslateUi()
}
}
if (aLeaveGame) {
if (replayManager->replay) {
if (replayDock) {
aLeaveGame->setText(tr("C&lose replay"));
} else {
aLeaveGame->setText(tr("&Leave game"));
@@ -336,23 +338,18 @@ void TabGame::retranslateUi()
}
viewMenu->setTitle(tr("&View"));
cardInfoDockMenu->setTitle(tr("Card Info"));
messageLayoutDockMenu->setTitle(tr("Messages"));
playerListDockMenu->setTitle(tr("Player List"));
aCardInfoDockVisible->setText(tr("Visible"));
aCardInfoDockFloating->setText(tr("Floating"));
aMessageLayoutDockVisible->setText(tr("Visible"));
aMessageLayoutDockFloating->setText(tr("Floating"));
aPlayerListDockVisible->setText(tr("Visible"));
aPlayerListDockFloating->setText(tr("Floating"));
dockToActions[cardInfoDock].menu->setTitle(tr("Card Info"));
dockToActions[messageLayoutDock].menu->setTitle(tr("Messages"));
dockToActions[playerListDock].menu->setTitle(tr("Player List"));
if (replayDock) {
replayDockMenu->setTitle(tr("Replay Timeline"));
aReplayDockVisible->setText(tr("Visible"));
aReplayDockFloating->setText(tr("Floating"));
dockToActions[replayDock].menu->setTitle(tr("Replay Timeline"));
}
for (auto &actions : dockToActions.values()) {
actions.aVisible->setText(tr("Visible"));
actions.aFloating->setText(tr("Floating"));
}
aResetLayout->setText(tr("Reset layout"));
@@ -517,7 +514,7 @@ bool TabGame::leaveGame()
return false;
}
if (!replayManager->replay)
if (!replayDock)
emit gameLeft();
}
return true;
@@ -904,7 +901,7 @@ QString TabGame::getTabText() const
QString gameId(QString::number(game->getGameMetaInfo()->gameId()));
QString tabText;
if (replayManager->replay)
if (replayDock)
tabText.append(tr("Replay") + " ");
if (!gameTypeInfo.isEmpty())
tabText.append(gameTypeInfo + " ");
@@ -1037,40 +1034,12 @@ void TabGame::createViewMenuItems()
{
viewMenu = new QMenu(this);
cardInfoDockMenu = viewMenu->addMenu(QString());
messageLayoutDockMenu = viewMenu->addMenu(QString());
playerListDockMenu = viewMenu->addMenu(QString());
aCardInfoDockVisible = cardInfoDockMenu->addAction(QString());
aCardInfoDockVisible->setCheckable(true);
connect(aCardInfoDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered);
aCardInfoDockFloating = cardInfoDockMenu->addAction(QString());
aCardInfoDockFloating->setCheckable(true);
connect(aCardInfoDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered);
aMessageLayoutDockVisible = messageLayoutDockMenu->addAction(QString());
aMessageLayoutDockVisible->setCheckable(true);
connect(aMessageLayoutDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered);
aMessageLayoutDockFloating = messageLayoutDockMenu->addAction(QString());
aMessageLayoutDockFloating->setCheckable(true);
connect(aMessageLayoutDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered);
aPlayerListDockVisible = playerListDockMenu->addAction(QString());
aPlayerListDockVisible->setCheckable(true);
connect(aPlayerListDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered);
aPlayerListDockFloating = playerListDockMenu->addAction(QString());
aPlayerListDockFloating->setCheckable(true);
connect(aPlayerListDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered);
registerDockWidget(viewMenu, cardInfoDock);
registerDockWidget(viewMenu, messageLayoutDock);
registerDockWidget(viewMenu, playerListDock);
if (replayDock) {
replayDockMenu = viewMenu->addMenu(QString());
aReplayDockVisible = replayDockMenu->addAction(QString());
aReplayDockVisible->setCheckable(true);
connect(aReplayDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered);
aReplayDockFloating = replayDockMenu->addAction(QString());
aReplayDockFloating->setCheckable(true);
connect(aReplayDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered);
registerDockWidget(viewMenu, replayDock);
}
viewMenu->addSeparator();
@@ -1082,10 +1051,40 @@ void TabGame::createViewMenuItems()
addTabMenu(viewMenu);
}
void TabGame::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget)
{
QMenu *menu = _viewMenu->addMenu(QString());
QAction *aVisible = menu->addAction(QString());
aVisible->setCheckable(true);
QAction *aFloating = menu->addAction(QString());
aFloating->setCheckable(true);
aFloating->setEnabled(false);
// user interaction
connect(aVisible, &QAction::triggered, widget, [widget](bool checked) { widget->setVisible(checked); });
connect(aFloating, &QAction::triggered, this, [widget](bool checked) { widget->setFloating(checked); });
// sync aFloating's enabled state with aVisible's checked state
connect(aVisible, &QAction::toggled, aFloating, [aFloating](bool checked) { aFloating->setEnabled(checked); });
// sync aFloating with dockWidget's floating state
connect(widget, &QDockWidget::topLevelChanged, aFloating,
[aFloating](bool topLevel) { aFloating->setChecked(topLevel); });
// sync aVisible with dockWidget's visible state
auto filter = new VisibilityChangeListener(widget);
connect(filter, &VisibilityChangeListener::visibilityChanged, aVisible,
[aVisible](bool visible) { aVisible->setChecked(visible); });
dockToActions.insert(widget, {menu, aVisible, aFloating});
}
void TabGame::loadLayout()
{
LayoutsSettings &layouts = SettingsCache::instance().layouts();
if (replayManager->replay) {
if (replayDock) {
restoreGeometry(layouts.getReplayPlayAreaGeometry());
restoreState(layouts.getReplayPlayAreaLayoutState());
@@ -1109,24 +1108,6 @@ void TabGame::loadLayout()
playerListDock->setMaximumSize(layouts.getGamePlayerListSize());
}
aCardInfoDockVisible->setChecked(cardInfoDock->isVisible());
aMessageLayoutDockVisible->setChecked(messageLayoutDock->isVisible());
aPlayerListDockVisible->setChecked(playerListDock->isVisible());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
aMessageLayoutDockFloating->setEnabled(aMessageLayoutDockVisible->isChecked());
aPlayerListDockFloating->setEnabled(aPlayerListDockVisible->isChecked());
aCardInfoDockFloating->setChecked(cardInfoDock->isFloating());
aMessageLayoutDockFloating->setChecked(messageLayoutDock->isFloating());
aPlayerListDockFloating->setChecked(playerListDock->isFloating());
if (replayManager->replay) {
aReplayDockVisible->setChecked(replayDock->isVisible());
aReplayDockFloating->setEnabled(aReplayDockVisible->isChecked());
aReplayDockFloating->setChecked(replayDock->isFloating());
}
QTimer::singleShot(100, this, &TabGame::freeDocksSize);
}
@@ -1157,14 +1138,6 @@ void TabGame::actResetLayout()
playerListDock->setFloating(false);
messageLayoutDock->setFloating(false);
aCardInfoDockVisible->setChecked(true);
aPlayerListDockVisible->setChecked(true);
aMessageLayoutDockVisible->setChecked(true);
aCardInfoDockFloating->setChecked(false);
aPlayerListDockFloating->setChecked(false);
aMessageLayoutDockFloating->setChecked(false);
addDockWidget(Qt::RightDockWidgetArea, cardInfoDock);
addDockWidget(Qt::RightDockWidgetArea, playerListDock);
addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock);
@@ -1173,8 +1146,6 @@ void TabGame::actResetLayout()
replayDock->setVisible(true);
replayDock->setFloating(false);
addDockWidget(Qt::BottomDockWidgetArea, replayDock);
aReplayDockVisible->setChecked(true);
aReplayDockFloating->setChecked(false);
cardInfoDock->setMinimumSize(250, 360);
cardInfoDock->setMaximumSize(250, 360);
@@ -1226,9 +1197,6 @@ void TabGame::createReplayDock(GameReplay *replay)
QDockWidget::DockWidgetMovable);
replayDock->setWidget(replayManager);
replayDock->setFloating(false);
replayDock->installEventFilter(this);
connect(replayDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged);
}
void TabGame::createDeckViewContainerWidget(bool bReplay)
@@ -1267,9 +1235,6 @@ void TabGame::createCardInfoDock(bool bReplay)
QDockWidget::DockWidgetMovable);
cardInfoDock->setWidget(cardBoxLayoutWidget);
cardInfoDock->setFloating(false);
cardInfoDock->installEventFilter(this);
connect(cardInfoDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged);
}
void TabGame::createPlayerListDock(bool bReplay)
@@ -1289,9 +1254,6 @@ void TabGame::createPlayerListDock(bool bReplay)
QDockWidget::DockWidgetMovable);
playerListDock->setWidget(playerListWidget);
playerListDock->setFloating(false);
playerListDock->installEventFilter(this);
connect(playerListDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged);
}
void TabGame::createMessageDock(bool bReplay)
@@ -1372,15 +1334,12 @@ void TabGame::createMessageDock(bool bReplay)
QDockWidget::DockWidgetMovable);
messageLayoutDock->setWidget(messageLogLayoutWidget);
messageLayoutDock->setFloating(false);
messageLayoutDock->installEventFilter(this);
connect(messageLayoutDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged);
}
void TabGame::hideEvent(QHideEvent *event)
{
LayoutsSettings &layouts = SettingsCache::instance().layouts();
if (replayManager->replay) {
if (replayDock) {
layouts.setReplayPlayAreaState(saveState());
layouts.setReplayPlayAreaGeometry(saveGeometry());
layouts.setReplayCardInfoSize(cardInfoDock->size());
@@ -1397,103 +1356,3 @@ void TabGame::hideEvent(QHideEvent *event)
Tab::hideEvent(event);
}
// Method uses to sync docks state with menu items state
bool TabGame::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Close) {
if (o == cardInfoDock) {
aCardInfoDockVisible->setChecked(false);
aCardInfoDockFloating->setEnabled(false);
} else if (o == messageLayoutDock) {
aMessageLayoutDockVisible->setChecked(false);
aMessageLayoutDockFloating->setEnabled(false);
} else if (o == playerListDock) {
aPlayerListDockVisible->setChecked(false);
aPlayerListDockFloating->setEnabled(false);
} else if (o == replayDock) {
aReplayDockVisible->setChecked(false);
aReplayDockFloating->setEnabled(false);
}
}
return false;
}
void TabGame::dockVisibleTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockVisible) {
cardInfoDock->setVisible(aCardInfoDockVisible->isChecked());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
return;
}
if (o == aMessageLayoutDockVisible) {
messageLayoutDock->setVisible(aMessageLayoutDockVisible->isChecked());
aMessageLayoutDockFloating->setEnabled(aMessageLayoutDockVisible->isChecked());
return;
}
if (o == aPlayerListDockVisible) {
playerListDock->setVisible(aPlayerListDockVisible->isChecked());
aPlayerListDockFloating->setEnabled(aPlayerListDockVisible->isChecked());
return;
}
if (o == aReplayDockVisible) {
replayDock->setVisible(aReplayDockVisible->isChecked());
aReplayDockFloating->setEnabled(aReplayDockVisible->isChecked());
return;
}
}
void TabGame::dockFloatingTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockFloating) {
cardInfoDock->setFloating(aCardInfoDockFloating->isChecked());
return;
}
if (o == aMessageLayoutDockFloating) {
messageLayoutDock->setFloating(aMessageLayoutDockFloating->isChecked());
return;
}
if (o == aPlayerListDockFloating) {
playerListDock->setFloating(aPlayerListDockFloating->isChecked());
return;
}
if (o == aReplayDockFloating) {
replayDock->setFloating(aReplayDockFloating->isChecked());
return;
}
}
void TabGame::dockTopLevelChanged(bool topLevel)
{
retranslateUi();
QObject *o = sender();
if (o == cardInfoDock) {
aCardInfoDockFloating->setChecked(topLevel);
return;
}
if (o == messageLayoutDock) {
aMessageLayoutDockFloating->setChecked(topLevel);
return;
}
if (o == playerListDock) {
aPlayerListDockFloating->setChecked(topLevel);
return;
}
if (o == replayDock) {
aReplayDockFloating->setChecked(topLevel);
return;
}
}

View File

@@ -58,7 +58,7 @@ class TabGame : public Tab
private:
AbstractGame *game;
const UserListProxy *userListProxy;
ReplayManager *replayManager;
ReplayManager *replayManager = nullptr;
QStringList gameTypes;
QCompleter *completer;
QStringList autocompleteUserList;
@@ -78,16 +78,26 @@ private:
QWidget *gamePlayAreaWidget, *deckViewContainerWidget;
QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock;
QAction *playersSeparator;
QMenu *gameMenu, *viewMenu, *cardInfoDockMenu, *messageLayoutDockMenu, *playerListDockMenu, *replayDockMenu;
QMenu *gameMenu, *viewMenu;
TearOffMenu *phasesMenu;
QAction *aGameInfo, *aConcede, *aLeaveGame, *aNextPhase, *aNextPhaseAction, *aNextTurn, *aReverseTurn,
*aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout;
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aMessageLayoutDockVisible, *aMessageLayoutDockFloating,
*aPlayerListDockVisible, *aPlayerListDockFloating, *aReplayDockVisible, *aReplayDockFloating;
QAction *aFocusChat;
QList<QAction *> phaseActions;
QAction *aCardMenu;
/**
* @brief The actions associated with managing a QDockWidget
*/
struct DockActions
{
QMenu *menu;
QAction *aVisible;
QAction *aFloating;
};
QMap<QDockWidget *, DockActions> dockToActions;
Player *addPlayer(Player *newPlayer);
void addLocalPlayer(Player *newPlayer, int playerId);
void processRemotePlayerDeckSelect(QString deckList, int playerId, QString playerName);
@@ -107,6 +117,7 @@ private:
void createMenuItems();
void createReplayMenuItems();
void createViewMenuItems();
void registerDockWidget(QMenu *_viewMenu, QDockWidget *widget);
void createCardInfoDock(bool bReplay = false);
void createPlayerListDock(bool bReplay = false);
void createMessageDock(bool bReplay = false);
@@ -156,10 +167,6 @@ private slots:
void freeDocksSize();
void hideEvent(QHideEvent *event) override;
bool eventFilter(QObject *o, QEvent *e) override;
void dockVisibleTriggered();
void dockFloatingTriggered();
void dockTopLevelChanged(bool topLevel);
protected slots:
void closeEvent(QCloseEvent *event) override;

View File

@@ -6,9 +6,9 @@ TabVisualDatabaseDisplay::TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor
{
deckEditor = new TabDeckEditor(_tabSupervisor);
deckEditor->setHidden(true);
visualDatabaseDisplayWidget =
new VisualDatabaseDisplayWidget(this, deckEditor, deckEditor->databaseDisplayDockWidget->databaseModel,
deckEditor->databaseDisplayDockWidget->databaseDisplayModel);
visualDatabaseDisplayWidget = new VisualDatabaseDisplayWidget(
this, deckEditor, deckEditor->cardDatabaseDockWidget->databaseDisplayWidget->databaseModel,
deckEditor->cardDatabaseDockWidget->databaseDisplayWidget->databaseDisplayModel);
setCentralWidget(visualDatabaseDisplayWidget);

View File

@@ -50,7 +50,7 @@ TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : Abstra
refreshShortcuts();
loadLayout();
databaseDisplayDockWidget->setHidden(true);
cardDatabaseDockWidget->setHidden(true);
}
/** @brief Creates the central frame containing the tab container. */
@@ -62,9 +62,9 @@ void TabDeckEditorVisual::createCentralFrame()
centralFrame = new QVBoxLayout;
centralWidget->setLayout(centralFrame);
tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckStateManager->getModel(),
databaseDisplayDockWidget->databaseModel,
databaseDisplayDockWidget->databaseDisplayModel);
tabContainer = new TabDeckEditorVisualTabWidget(
centralWidget, this, deckStateManager->getModel(), cardDatabaseDockWidget->databaseDisplayWidget->databaseModel,
cardDatabaseDockWidget->databaseDisplayWidget->databaseDisplayModel);
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardChanged, this,
&TabDeckEditorVisual::changeModelIndexAndCardInfo);
@@ -97,45 +97,10 @@ void TabDeckEditorVisual::createMenus()
viewMenu = new QMenu(this);
cardInfoDockMenu = viewMenu->addMenu(QString());
deckDockMenu = viewMenu->addMenu(QString());
filterDockMenu = viewMenu->addMenu(QString());
printingSelectorDockMenu = viewMenu->addMenu(QString());
aCardInfoDockVisible = cardInfoDockMenu->addAction(QString());
aCardInfoDockVisible->setCheckable(true);
connect(aCardInfoDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aCardInfoDockFloating = cardInfoDockMenu->addAction(QString());
aCardInfoDockFloating->setCheckable(true);
connect(aCardInfoDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
aDeckDockVisible = deckDockMenu->addAction(QString());
aDeckDockVisible->setCheckable(true);
connect(aDeckDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aDeckDockFloating = deckDockMenu->addAction(QString());
aDeckDockFloating->setCheckable(true);
connect(aDeckDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
aFilterDockVisible = filterDockMenu->addAction(QString());
aFilterDockVisible->setCheckable(true);
connect(aFilterDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aFilterDockFloating = filterDockMenu->addAction(QString());
aFilterDockFloating->setCheckable(true);
connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockVisible->setCheckable(true);
connect(aPrintingSelectorDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockFloating->setCheckable(true);
connect(aPrintingSelectorDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) {
printingSelectorDockMenu->setEnabled(false);
}
connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this,
[this](bool enabled) { printingSelectorDockMenu->setEnabled(!enabled); });
registerDockWidget(viewMenu, cardInfoDockWidget);
registerDockWidget(viewMenu, deckDockWidget);
registerDockWidget(viewMenu, filterDockWidget);
registerDockWidget(viewMenu, printingSelectorDockWidget);
viewMenu->addSeparator();
@@ -191,23 +156,32 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event,
QItemSelectionModel *sel = deckDockWidget->getSelectionModel();
// Double click = swap
if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton) {
if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton &&
!event->modifiers().testFlag(Qt::AltModifier)) {
deckStateManager->swapCardAtIndex(idx);
idx = deckStateManager->getModel()->findCard(card.getName(), zoneName);
sel->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
return;
}
// Right-click = decrement
if (event->button() == Qt::RightButton) {
actDecrementCard(card);
// Alt + Right-click = decrement
if (event->button() == Qt::RightButton && event->modifiers().testFlag(Qt::AltModifier)) {
if (zoneName == DECK_ZONE_MAIN) {
actDecrementCard(card);
} else {
actDecrementCardFromSideboard(card);
}
// Keep selection intact.
return;
}
// Alt + Left click = increment
if (event->button() == Qt::LeftButton && event->modifiers().testFlag(Qt::AltModifier)) {
// actIncrementCard(card);
if (zoneName == DECK_ZONE_MAIN) {
actAddCard(card);
} else {
actAddCardToSideboard(card);
}
// Keep selection intact.
return;
}
@@ -269,7 +243,6 @@ void TabDeckEditorVisual::showPrintingSelector()
{
printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr());
printingSelectorDockWidget->printingSelector->updateDisplay();
aPrintingSelectorDockVisible->setChecked(true);
printingSelectorDockWidget->setVisible(true);
}
@@ -308,28 +281,6 @@ void TabDeckEditorVisual::loadLayout()
restoreGeometry(layouts.getDeckEditorGeometry());
}
if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) {
if (!printingSelectorDockWidget->isHidden()) {
printingSelectorDockWidget->setHidden(true);
aPrintingSelectorDockVisible->setChecked(false);
}
}
aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden());
aFilterDockVisible->setChecked(!filterDockWidget->isHidden());
aDeckDockVisible->setChecked(!deckDockWidget->isHidden());
aPrintingSelectorDockVisible->setChecked(!printingSelectorDockWidget->isHidden());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
aCardInfoDockFloating->setChecked(cardInfoDockWidget->isFloating());
aFilterDockFloating->setChecked(filterDockWidget->isFloating());
aDeckDockFloating->setChecked(deckDockWidget->isFloating());
aPrintingSelectorDockFloating->setChecked(printingSelectorDockWidget->isFloating());
cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize());
cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize());
@@ -348,15 +299,14 @@ void TabDeckEditorVisual::loadLayout()
/** @brief Resets the layout to default positions and dock states. */
void TabDeckEditorVisual::restartLayout()
{
aCardInfoDockVisible->setChecked(true);
aDeckDockVisible->setChecked(true);
aFilterDockVisible->setChecked(false);
aPrintingSelectorDockVisible->setChecked(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference());
for (auto dockWidget : dockToActions.keys()) {
dockWidget->setFloating(false);
}
aCardInfoDockFloating->setChecked(false);
aDeckDockFloating->setChecked(false);
aFilterDockFloating->setChecked(false);
aPrintingSelectorDockFloating->setChecked(false);
deckDockWidget->setVisible(true);
cardInfoDockWidget->setVisible(true);
filterDockWidget->setVisible(false);
printingSelectorDockWidget->setVisible(true);
setCentralWidget(centralWidget);
addDockWidget(Qt::RightDockWidgetArea, deckDockWidget);
@@ -364,16 +314,6 @@ void TabDeckEditorVisual::restartLayout()
addDockWidget(Qt::RightDockWidgetArea, filterDockWidget);
addDockWidget(Qt::RightDockWidgetArea, printingSelectorDockWidget);
deckDockWidget->setVisible(true);
cardInfoDockWidget->setVisible(true);
filterDockWidget->setVisible(false);
printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference());
deckDockWidget->setFloating(false);
cardInfoDockWidget->setFloating(false);
filterDockWidget->setFloating(false);
printingSelectorDockWidget->setFloating(false);
splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Vertical);
splitDockWidget(cardInfoDockWidget, deckDockWidget, Qt::Horizontal);
splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Horizontal);
@@ -391,22 +331,16 @@ void TabDeckEditorVisual::retranslateUi()
filterDockWidget->setWindowTitle(tr("Filters"));
viewMenu->setTitle(tr("&View"));
cardInfoDockMenu->setTitle(tr("Card Info"));
deckDockMenu->setTitle(tr("Deck"));
filterDockMenu->setTitle(tr("Filters"));
printingSelectorDockMenu->setTitle(tr("Printing"));
aCardInfoDockVisible->setText(tr("Visible"));
aCardInfoDockFloating->setText(tr("Floating"));
dockToActions[cardInfoDockWidget].menu->setTitle(tr("Card Info"));
dockToActions[deckDockWidget].menu->setTitle(tr("Deck"));
dockToActions[filterDockWidget].menu->setTitle(tr("Filters"));
dockToActions[printingSelectorDockWidget].menu->setTitle(tr("Printing"));
aDeckDockVisible->setText(tr("Visible"));
aDeckDockFloating->setText(tr("Floating"));
aFilterDockVisible->setText(tr("Visible"));
aFilterDockFloating->setText(tr("Floating"));
aPrintingSelectorDockVisible->setText(tr("Visible"));
aPrintingSelectorDockFloating->setText(tr("Floating"));
for (auto &actions : dockToActions.values()) {
actions.aVisible->setText(tr("Visible"));
actions.aFloating->setText(tr("Floating"));
}
aResetLayout->setText(tr("Reset layout"));
}
@@ -418,22 +352,6 @@ void TabDeckEditorVisual::retranslateUi()
*/
bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Close) {
if (o == cardInfoDockWidget) {
aCardInfoDockVisible->setChecked(false);
aCardInfoDockFloating->setEnabled(false);
} else if (o == deckDockWidget) {
aDeckDockVisible->setChecked(false);
aDeckDockFloating->setEnabled(false);
} else if (o == filterDockWidget) {
aFilterDockVisible->setChecked(false);
aFilterDockFloating->setEnabled(false);
} else if (o == printingSelectorDockWidget) {
aPrintingSelectorDockVisible->setChecked(false);
aPrintingSelectorDockFloating->setEnabled(false);
}
}
if (o == this && e->type() == QEvent::Hide) {
LayoutsSettings &layouts = SettingsCache::instance().layouts();
layouts.setDeckEditorLayoutState(saveState());
@@ -445,82 +363,3 @@ bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e)
}
return false;
}
/** @brief Toggles dock visibility based on the corresponding menu action. */
void TabDeckEditorVisual::dockVisibleTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockVisible) {
cardInfoDockWidget->setHidden(!aCardInfoDockVisible->isChecked());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
return;
}
if (o == aDeckDockVisible) {
deckDockWidget->setHidden(!aDeckDockVisible->isChecked());
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
return;
}
if (o == aFilterDockVisible) {
filterDockWidget->setHidden(!aFilterDockVisible->isChecked());
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
return;
}
if (o == aPrintingSelectorDockVisible) {
printingSelectorDockWidget->setHidden(!aPrintingSelectorDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
return;
}
}
/** @brief Toggles dock floating state based on the corresponding menu action. */
void TabDeckEditorVisual::dockFloatingTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockFloating) {
cardInfoDockWidget->setFloating(aCardInfoDockFloating->isChecked());
return;
}
if (o == aDeckDockFloating) {
deckDockWidget->setFloating(aDeckDockFloating->isChecked());
return;
}
if (o == aFilterDockFloating) {
filterDockWidget->setFloating(aFilterDockFloating->isChecked());
return;
}
if (o == aPrintingSelectorDockFloating) {
printingSelectorDockWidget->setFloating(aPrintingSelectorDockFloating->isChecked());
return;
}
}
/** @brief Updates menu checkboxes when a dock's top-level/floating state changes. */
void TabDeckEditorVisual::dockTopLevelChanged(bool topLevel)
{
QObject *o = sender();
if (o == cardInfoDockWidget) {
aCardInfoDockFloating->setChecked(topLevel);
return;
}
if (o == deckDockWidget) {
aDeckDockFloating->setChecked(topLevel);
return;
}
if (o == filterDockWidget) {
aFilterDockFloating->setChecked(topLevel);
return;
}
if (o == printingSelectorDockWidget) {
aPrintingSelectorDockFloating->setChecked(topLevel);
return;
}
}

View File

@@ -84,22 +84,6 @@ protected slots:
*/
bool eventFilter(QObject *o, QEvent *e) override;
/**
* @brief Triggered when a dock visibility menu item is clicked.
*/
void dockVisibleTriggered() override;
/**
* @brief Triggered when a dock floating menu item is clicked.
*/
void dockFloatingTriggered() override;
/**
* @brief Triggered when a dock top-level state changes.
* @param topLevel True if the dock became floating.
*/
void dockTopLevelChanged(bool topLevel) override;
protected:
TabDeckEditorVisualTabWidget *tabContainer; ///< Tab container holding different visual widgets.

View File

@@ -24,11 +24,12 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor)
void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath)
{
auto deckLoader = DeckLoader(this);
if (!deckLoader.loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true)) {
std::optional<LoadedDeck> deckOpt =
DeckLoader::loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true);
if (!deckOpt) {
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath));
return;
}
emit openDeckEditor(deckLoader.getDeck());
emit openDeckEditor(deckOpt.value());
}

View File

@@ -0,0 +1,26 @@
#include "visibility_change_listener.h"
#include <QEvent>
#include <QWidget>
VisibilityChangeListener::VisibilityChangeListener(QWidget *targetWidget)
: QObject(targetWidget), targetWidget(targetWidget)
{
if (targetWidget) {
targetWidget->installEventFilter(this);
}
}
bool VisibilityChangeListener::eventFilter(QObject *o, QEvent *e)
{
if (o == targetWidget && !e->spontaneous()) {
if (e->type() == QEvent::Show) {
emit visibilityChanged(true);
}
if (e->type() == QEvent::Hide) {
emit visibilityChanged(false);
}
}
return false;
}

View File

@@ -0,0 +1,35 @@
#ifndef COCKATRICE_VISIBILITY_LISTENER_H
#define COCKATRICE_VISIBILITY_LISTENER_H
#include <QObject>
/**
* @brief This filter listens to the visibility changes of a target widget, emitting signals whenever the visibility of
* that widget changes.
*/
class VisibilityChangeListener : public QObject
{
Q_OBJECT
QWidget *targetWidget;
public:
/**
* Creates a new instance of this class, watching the targetWidget.
* This class automatically installs itself as an eventFilter to the targetWidget.
*
* @param targetWidget The widget to watch. Sets that widget as this object's parent.
*/
explicit VisibilityChangeListener(QWidget *targetWidget);
bool eventFilter(QObject *o, QEvent *e) override;
signals:
/**
* Emitted whenever the target widget's visibility changes
* @param visible The widget's new visibility
*/
void visibilityChanged(bool visible);
};
#endif // COCKATRICE_VISIBILITY_LISTENER_H

View File

@@ -31,12 +31,12 @@ VisualDatabaseDisplayColorFilterWidget::VisualDatabaseDisplayColorFilterWidget(Q
&VisualDatabaseDisplayColorFilterWidget::handleColorToggled);
}
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
modeComboBox = new QComboBox(this);
layout->addWidget(modeComboBox);
connect(modeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&VisualDatabaseDisplayColorFilterWidget::updateFilterMode);
// Connect the button's toggled signal
connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplayColorFilterWidget::updateFilterMode);
connect(filterModel, &FilterTreeModel::layoutChanged, this,
[this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel); });
@@ -46,19 +46,22 @@ VisualDatabaseDisplayColorFilterWidget::VisualDatabaseDisplayColorFilterWidget(Q
void VisualDatabaseDisplayColorFilterWidget::retranslateUi()
{
switch (currentMode) {
case FilterMode::ExactMatch:
toggleButton->setText(tr("Mode: Exact Match"));
break;
case FilterMode::Includes:
toggleButton->setText(tr("Mode: Includes"));
break;
case FilterMode::IncludeExclude:
toggleButton->setText(tr("Mode: Include/Exclude"));
break;
modeComboBox->blockSignals(true);
modeComboBox->clear();
modeComboBox->addItem(tr("Exact match"), QVariant::fromValue(FilterMode::ExactMatch));
modeComboBox->addItem(tr("Includes"), QVariant::fromValue(FilterMode::Includes));
modeComboBox->addItem(tr("Include / Exclude"), QVariant::fromValue(FilterMode::IncludeExclude));
modeComboBox->setToolTip(tr("How selected and unselected colors are combined in the filter"));
// Restore current mode
const int index = modeComboBox->findData(QVariant::fromValue(currentMode));
if (index >= 0) {
modeComboBox->setCurrentIndex(index);
}
toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)"));
modeComboBox->blockSignals(false);
}
void VisualDatabaseDisplayColorFilterWidget::handleColorToggled(QChar color, bool active)
@@ -145,24 +148,19 @@ void VisualDatabaseDisplayColorFilterWidget::removeFilter(QChar color)
void VisualDatabaseDisplayColorFilterWidget::updateFilterMode()
{
switch (currentMode) {
case FilterMode::ExactMatch:
currentMode = FilterMode::Includes; // Switch to Includes
break;
case FilterMode::Includes:
currentMode = FilterMode::IncludeExclude; // Switch to Include/Exclude
break;
case FilterMode::IncludeExclude:
currentMode = FilterMode::ExactMatch; // Switch to Exact Match
break;
const QVariant data = modeComboBox->currentData();
if (!data.isValid()) {
return;
}
currentMode = data.value<FilterMode>();
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
filterModel->clearFiltersOfType(CardFilter::Attr::AttrColor);
QList<ManaSymbolWidget *> manaSymbolWidgets = findChildren<ManaSymbolWidget *>();
const QList<ManaSymbolWidget *> manaSymbolWidgets = findChildren<ManaSymbolWidget *>();
for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) {
handleColorToggled(manaSymbolWidget->getSymbolChar(), manaSymbolWidget->isColorActive());
@@ -173,9 +171,7 @@ void VisualDatabaseDisplayColorFilterWidget::updateFilterMode()
emit filterModel->filterTree()->changed();
emit filterModel->layoutChanged();
retranslateUi(); // Update button text based on the mode
emit filterModeChanged(currentMode); // Signal mode change
emit filterModeChanged(currentMode);
}
void VisualDatabaseDisplayColorFilterWidget::setManaSymbolActive(QChar color, bool active)

View File

@@ -9,8 +9,8 @@
#include "../../../filters/filter_tree_model.h"
#include <QComboBox>
#include <QHBoxLayout>
#include <QPushButton>
#include <QWidget>
class VisualDatabaseDisplayColorFilterCircleWidget : public QWidget
@@ -39,6 +39,8 @@ enum class FilterMode
IncludeExclude // Include selected colors (OR) and exclude unselected colors (AND NOT).
};
Q_DECLARE_METATYPE(FilterMode)
class VisualDatabaseDisplayColorFilterWidget : public QWidget
{
Q_OBJECT
@@ -62,7 +64,7 @@ private slots:
private:
FilterTreeModel *filterModel;
QHBoxLayout *layout;
QPushButton *toggleButton;
QComboBox *modeComboBox;
FilterMode currentMode = FilterMode::Includes; // Default mode
};

View File

@@ -0,0 +1,135 @@
#include "visual_database_display_filter_toolbar_widget.h"
#include "visual_database_display_widget.h"
VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *_parent)
: QWidget(_parent), visualDatabaseDisplay(_parent)
{
filterContainerLayout = new QHBoxLayout(this);
filterContainerLayout->setContentsMargins(11, 0, 11, 0);
setLayout(filterContainerLayout);
filterContainerLayout->setAlignment(Qt::AlignLeft);
setMaximumHeight(80);
connect(this, &VisualDatabaseDisplayFilterToolbarWidget::searchModelChanged, visualDatabaseDisplay,
&VisualDatabaseDisplayWidget::onSearchModelChanged);
filterByLabel = new QLabel(this);
sortByLabel = new QLabel(this);
sortColumnCombo = new QComboBox(this);
sortColumnCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents);
sortOrderCombo = new QComboBox(this);
sortOrderCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents);
sortOrderCombo->addItem("Ascending", Qt::AscendingOrder);
sortOrderCombo->addItem("Descending", Qt::DescendingOrder);
sortOrderCombo->view()->setMinimumWidth(sortOrderCombo->view()->sizeHintForColumn(0));
sortOrderCombo->adjustSize();
// Populate columns dynamically from the model
for (int i = 0; i < visualDatabaseDisplay->getDatabaseDisplayModel()->columnCount(); ++i) {
QString header = visualDatabaseDisplay->getDatabaseDisplayModel()->headerData(i, Qt::Horizontal).toString();
sortColumnCombo->addItem(header, i);
}
sortColumnCombo->view()->setMinimumWidth(sortColumnCombo->view()->sizeHintForColumn(0));
sortColumnCombo->adjustSize();
connect(sortColumnCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this]() {
int column = sortColumnCombo->currentData().toInt();
Qt::SortOrder order = static_cast<Qt::SortOrder>(sortOrderCombo->currentData().toInt());
visualDatabaseDisplay->getDatabaseView()->sortByColumn(column, order);
emit searchModelChanged();
});
connect(sortOrderCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this]() {
int column = sortColumnCombo->currentData().toInt();
Qt::SortOrder order = static_cast<Qt::SortOrder>(sortOrderCombo->currentData().toInt());
visualDatabaseDisplay->getDatabaseView()->sortByColumn(column, order);
emit searchModelChanged();
});
quickFilterSaveLoadWidget = new SettingsButtonWidget(this);
quickFilterSaveLoadWidget->setButtonIcon(QPixmap("theme:icons/floppy_disk"));
quickFilterNameWidget = new SettingsButtonWidget(this);
quickFilterNameWidget->setButtonIcon(QPixmap("theme:icons/pen_to_square"));
quickFilterMainTypeWidget = new SettingsButtonWidget(this);
quickFilterMainTypeWidget->setButtonIcon(QPixmap("theme:icons/circle_half_stroke"));
quickFilterSubTypeWidget = new SettingsButtonWidget(this);
quickFilterSubTypeWidget->setButtonIcon(QPixmap("theme:icons/dragon"));
quickFilterSetWidget = new SettingsButtonWidget(this);
quickFilterSetWidget->setButtonIcon(QPixmap("theme:icons/scroll"));
quickFilterFormatLegalityWidget = new SettingsButtonWidget(this);
quickFilterFormatLegalityWidget->setButtonIcon(QPixmap("theme:icons/scale_balanced"));
retranslateUi();
}
void VisualDatabaseDisplayFilterToolbarWidget::initialize()
{
sortByLabel->setVisible(true);
filterByLabel->setVisible(true);
quickFilterSaveLoadWidget->setVisible(true);
quickFilterNameWidget->setVisible(true);
quickFilterSubTypeWidget->setVisible(true);
quickFilterSetWidget->setVisible(true);
auto filterModel = visualDatabaseDisplay->filterModel;
saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel);
nameFilterWidget =
new VisualDatabaseDisplayNameFilterWidget(this, visualDatabaseDisplay->getDeckEditor(), filterModel);
mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel);
formatLegalityWidget = new VisualDatabaseDisplayFormatLegalityFilterWidget(this, filterModel);
subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel);
setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel);
quickFilterSaveLoadWidget->addSettingsWidget(saveLoadWidget);
quickFilterNameWidget->addSettingsWidget(nameFilterWidget);
quickFilterMainTypeWidget->addSettingsWidget(mainTypeFilterWidget);
quickFilterSubTypeWidget->addSettingsWidget(subTypeFilterWidget);
quickFilterSetWidget->addSettingsWidget(setFilterWidget);
quickFilterFormatLegalityWidget->addSettingsWidget(formatLegalityWidget);
filterContainerLayout->addWidget(sortByLabel);
filterContainerLayout->addWidget(sortColumnCombo);
filterContainerLayout->addWidget(sortOrderCombo);
filterContainerLayout->addWidget(filterByLabel);
filterContainerLayout->addWidget(quickFilterNameWidget);
filterContainerLayout->addWidget(quickFilterMainTypeWidget);
filterContainerLayout->addWidget(quickFilterSubTypeWidget);
filterContainerLayout->addWidget(quickFilterSetWidget);
filterContainerLayout->addWidget(quickFilterFormatLegalityWidget);
filterContainerLayout->addStretch();
filterContainerLayout->addWidget(quickFilterSaveLoadWidget);
}
void VisualDatabaseDisplayFilterToolbarWidget::retranslateUi()
{
sortByLabel->setText(tr("Sort by:"));
filterByLabel->setText(tr("Filter by:"));
quickFilterSaveLoadWidget->setToolTip(tr("Save and load filters"));
quickFilterNameWidget->setToolTip(tr("Filter by exact card name"));
quickFilterMainTypeWidget->setToolTip(tr("Filter by card main-type"));
quickFilterSubTypeWidget->setToolTip(tr("Filter by card sub-type"));
quickFilterSetWidget->setToolTip(tr("Filter by set"));
quickFilterFormatLegalityWidget->setToolTip(tr("Filter by format legality"));
quickFilterSaveLoadWidget->setButtonText(tr("Save/Load"));
quickFilterNameWidget->setButtonText(tr("Name"));
quickFilterMainTypeWidget->setButtonText(tr("Main Type"));
quickFilterSubTypeWidget->setButtonText(tr("Sub Type"));
quickFilterSetWidget->setButtonText(tr("Sets"));
quickFilterFormatLegalityWidget->setButtonText(tr("Formats"));
}

View File

@@ -0,0 +1,48 @@
#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_TOOLBAR_WIDGET_H
#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_TOOLBAR_WIDGET_H
#include "visual_database_display_filter_save_load_widget.h"
#include "visual_database_display_format_legality_filter_widget.h"
#include "visual_database_display_main_type_filter_widget.h"
#include "visual_database_display_name_filter_widget.h"
#include "visual_database_display_set_filter_widget.h"
#include "visual_database_display_sub_type_filter_widget.h"
class VisualDatabaseDisplayWidget;
class VisualDatabaseDisplayFilterToolbarWidget : public QWidget
{
Q_OBJECT
signals:
void searchModelChanged();
public:
explicit VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *parent);
void initialize();
void retranslateUi();
private:
VisualDatabaseDisplayWidget *visualDatabaseDisplay;
QLabel *sortByLabel;
QComboBox *sortColumnCombo, *sortOrderCombo;
QLabel *filterByLabel;
QHBoxLayout *filterContainerLayout;
SettingsButtonWidget *quickFilterSaveLoadWidget;
VisualDatabaseDisplayFilterSaveLoadWidget *saveLoadWidget;
SettingsButtonWidget *quickFilterNameWidget;
VisualDatabaseDisplayNameFilterWidget *nameFilterWidget;
SettingsButtonWidget *quickFilterMainTypeWidget;
VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget;
SettingsButtonWidget *quickFilterSubTypeWidget;
VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget;
SettingsButtonWidget *quickFilterSetWidget;
VisualDatabaseDisplaySetFilterWidget *setFilterWidget;
SettingsButtonWidget *quickFilterFormatLegalityWidget;
VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget;
};
#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_TOOLBAR_WIDGET_H

View File

@@ -2,6 +2,7 @@
#include "../../../filters/filter_tree_model.h"
#include <QLabel>
#include <QPushButton>
#include <QSpinBox>
#include <QTimer>
@@ -14,11 +15,14 @@ VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLega
: QWidget(parent), filterModel(_filterModel)
{
allFormatsWithCount = CardDatabaseManager::query()->getAllFormatsWithCount();
int maxValue = std::numeric_limits<int>::min();
for (int value : allFormatsWithCount) {
maxValue = std::max(maxValue, value);
}
setMinimumWidth(300);
setMaximumHeight(300);
setMaximumHeight(75);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
layout = new QHBoxLayout(this);
layout = new QVBoxLayout(this);
setLayout(layout);
layout->setContentsMargins(0, 1, 0, 1);
layout->setSpacing(1);
@@ -27,33 +31,45 @@ VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLega
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
// Create a container for the threshold control
auto *thresholdLayout = new QHBoxLayout();
thresholdLayout->setContentsMargins(0, 0, 0, 0);
thresholdLabel = new QLabel(this);
thresholdLayout->addWidget(thresholdLabel);
// Create the spinbox
spinBox = new QSpinBox(this);
spinBox->setMinimum(1);
spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically
spinBox->setMaximum(maxValue); // Set the max value dynamically
spinBox->setValue(150);
layout->addWidget(spinBox);
thresholdLayout->addWidget(spinBox);
thresholdLayout->addStretch();
layout->addLayout(thresholdLayout);
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility);
// Create the toggle button for Exact Match/Includes mode
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
connect(toggleButton, &QPushButton::toggled, this,
connect(toggleButton, &QPushButton::clicked, this,
&VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode);
connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() {
QTimer::singleShot(100, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel);
});
createFormatButtons(); // Populate buttons initially
updateFilterMode(false); // Initialize toggle button text
createFormatButtons(); // Populate buttons initially
updateFilterMode(); // Initialize toggle button text
retranslateUi();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::retranslateUi()
{
thresholdLabel->setText(tr("Show formats with at least:"));
spinBox->setSuffix(tr(" cards"));
spinBox->setToolTip(tr("Do not display formats with less than this amount of cards in the database"));
toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)"));
}
@@ -160,9 +176,9 @@ void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatFilter()
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode(bool checked)
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode()
{
exactMatchMode = checked;
exactMatchMode = !exactMatchMode;
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
updateFormatFilter();
}

View File

@@ -4,6 +4,7 @@
#include "../../../filters/filter_tree_model.h"
#include "../general/layout_containers/flow_widget.h"
#include <QLabel>
#include <QMap>
#include <QPushButton>
#include <QSpinBox>
@@ -23,21 +24,23 @@ public:
void handleFormatToggled(const QString &format, bool active);
void updateFormatFilter();
void updateFilterMode(bool checked);
void updateFilterMode();
void syncWithFilterModel();
private:
FilterTreeModel *filterModel;
QMap<QString, int> allFormatsWithCount;
QSpinBox *spinBox;
QHBoxLayout *layout;
QVBoxLayout *layout;
FlowWidget *flowWidget;
QLabel *thresholdLabel;
QSpinBox *spinBox;
QPushButton *toggleButton; // Mode switch button
QMap<QString, bool> activeFormats; // Track active filters
QMap<QString, QPushButton *> formatButtons; // Store toggle buttons
bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes"
bool exactMatchMode = true; // Toggle between "Exact Match" and "Includes"
};
#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H

View File

@@ -2,6 +2,7 @@
#include "../../../filters/filter_tree_model.h"
#include <QLabel>
#include <QPushButton>
#include <QSpinBox>
#include <QTimer>
@@ -12,13 +13,12 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi
FilterTreeModel *_filterModel)
: QWidget(parent), filterModel(_filterModel)
{
allMainCardTypesWithCount = CardDatabaseManager::query()->getAllMainCardTypesWithCount();
// Get all main card types with their count
allMainCardTypesWithCount = CardDatabaseManager::query()->getAllMainCardTypesWithCount();
setMinimumWidth(300);
setMaximumHeight(200);
setMaximumHeight(75);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
layout = new QHBoxLayout(this);
layout = new QVBoxLayout(this);
setLayout(layout);
layout->setContentsMargins(0, 1, 0, 1);
layout->setSpacing(1);
@@ -27,32 +27,44 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
// Create a container for the threshold control
auto *thresholdLayout = new QHBoxLayout();
thresholdLayout->setContentsMargins(0, 0, 0, 0);
thresholdLabel = new QLabel(this);
thresholdLayout->addWidget(thresholdLabel);
// Create the spinbox
spinBox = new QSpinBox(this);
spinBox->setMinimum(1);
spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically
spinBox->setMaximum(getMaxMainTypeCount());
spinBox->setValue(150);
layout->addWidget(spinBox);
thresholdLayout->addWidget(spinBox);
thresholdLayout->addStretch();
layout->addLayout(thresholdLayout);
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility);
// Create the toggle button for Exact Match/Includes mode
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode);
connect(toggleButton, &QPushButton::clicked, this, &VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode);
connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() {
QTimer::singleShot(100, this, &VisualDatabaseDisplayMainTypeFilterWidget::syncWithFilterModel);
});
createMainTypeButtons(); // Populate buttons initially
updateFilterMode(false); // Initialize toggle button text
updateFilterMode(); // Initialize toggle button text
retranslateUi();
}
void VisualDatabaseDisplayMainTypeFilterWidget::retranslateUi()
{
thresholdLabel->setText(tr("Show main types with at least:"));
spinBox->setSuffix(tr(" cards"));
spinBox->setToolTip(tr("Do not display card main-types with less than this amount of cards in the database"));
toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)"));
}
@@ -159,9 +171,9 @@ void VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeFilter()
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode(bool checked)
void VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode()
{
exactMatchMode = checked;
exactMatchMode = !exactMatchMode;
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
updateMainTypeFilter();
}

Some files were not shown because too many files have changed in this diff Show More