Compare commits
56 Commits
tooomm-tra
...
2026-01-24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffc55aff10 | ||
|
|
2b372c14e4 | ||
|
|
12b5525a2d | ||
|
|
3c48d92663 | ||
|
|
948ec9e042 | ||
|
|
5a274fdbed | ||
|
|
bfeb3a7ca9 | ||
|
|
d363ec5154 | ||
|
|
999733fc0f | ||
|
|
8d274c1924 | ||
|
|
2e1a0bec93 | ||
|
|
39ddaa0c35 | ||
|
|
d9b9c79112 | ||
|
|
485d5a8b48 | ||
|
|
f7e71a0868 | ||
|
|
af2995ba96 | ||
|
|
f7ffcc58fe | ||
|
|
792f077071 | ||
|
|
9c07c7a963 | ||
|
|
d579c82cb9 | ||
|
|
c7c7bf550a | ||
|
|
84483c56d7 | ||
|
|
1b71519ec6 | ||
|
|
154b9ace92 | ||
|
|
93f0715d02 | ||
|
|
57e6c91689 | ||
|
|
6213ccff48 | ||
|
|
c075deeb2d | ||
|
|
29f60c4a67 | ||
|
|
c553e15036 | ||
|
|
a4eef648bc | ||
|
|
47720ff286 | ||
|
|
289b139be9 | ||
|
|
21d60ec3f1 | ||
|
|
ed1115f4c0 | ||
|
|
cc5e2ab10a | ||
|
|
b19312be70 | ||
|
|
a0d1359860 | ||
|
|
52547bbfe8 | ||
|
|
9ab398f08d | ||
|
|
0deaa9d9b4 | ||
|
|
7c7755b61d | ||
|
|
6340c4a6b7 | ||
|
|
0a2fdb05ad | ||
|
|
b86853b65c | ||
|
|
192dac0396 | ||
|
|
85c9d8a9ff | ||
|
|
ee2699413c | ||
|
|
d50297bbe6 | ||
|
|
489ce416c3 | ||
|
|
731c487ccb | ||
|
|
2d5e8deb75 | ||
|
|
746f2af044 | ||
|
|
f16c552d97 | ||
|
|
72a85b58cf | ||
|
|
b88a98b09a |
@@ -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
|
||||
|
||||
40
.ci/resolve_latest_aqt_qt_version.sh
Executable 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
@@ -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::"
|
||||
54
.github/workflows/desktop-build.yml
vendored
@@ -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
@@ -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
|
||||
|
||||
12
Doxyfile
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
31
cockatrice/resources/backgrounds/card_triplet.svg
Normal 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 |
@@ -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 |
@@ -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>
|
||||
@@ -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>
|
||||
1
cockatrice/resources/icons/circle_half_stroke.svg
Normal 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 |
1
cockatrice/resources/icons/dragon.svg
Normal 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 |
1
cockatrice/resources/icons/floppy_disk.svg
Normal 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 |
1
cockatrice/resources/icons/gear.svg
Normal 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 |
1
cockatrice/resources/icons/pen_to_square.svg
Normal 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 |
1
cockatrice/resources/icons/scale_balanced.svg
Normal 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 |
1
cockatrice/resources/icons/scroll.svg
Normal 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 |
1
cockatrice/resources/icons/sort_arrow_down.svg
Normal 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 |
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 &) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -40,7 +40,6 @@ public:
|
||||
QPersistentModelIndex trackedIndex;
|
||||
QString zoneName;
|
||||
void addCardsToOverlapWidget();
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
public slots:
|
||||
void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -31,7 +31,6 @@ void DeckEditorCardInfoDockWidget::createCardInfoDock()
|
||||
setWidget(cardInfoDockContents);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||
}
|
||||
|
||||
void DeckEditorCardInfoDockWidget::updateCard(const ExactCard &_card)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -80,7 +80,6 @@ void DeckEditorFilterDockWidget::createFiltersDock()
|
||||
setWidget(filterDockContents);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::filterViewCustomContextMenu(const QPoint &point)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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());
|
||||
|
||||
@@ -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:
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||