Merge branch 'master' into 6269/ci/strip-fat-bins

This commit is contained in:
Bruno Alexandre Rosa 2025-12-31 14:53:45 -03:00
commit 04509150ae
183 changed files with 3199 additions and 2758 deletions

View file

@ -122,7 +122,7 @@ if [[ $MAKE_SERVER ]]; then
flags+=("-DWITH_SERVER=1") flags+=("-DWITH_SERVER=1")
fi fi
if [[ $MAKE_NO_CLIENT ]]; then if [[ $MAKE_NO_CLIENT ]]; then
flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0" "-DWITH_DBCONVERTER=0") flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0")
fi fi
if [[ $MAKE_TEST ]]; then if [[ $MAKE_TEST ]]; then
flags+=("-DTEST=1") flags+=("-DTEST=1")
@ -258,7 +258,7 @@ fi
if [[ $RUNNER_OS == macOS ]]; then if [[ $RUNNER_OS == macOS ]]; then
echo "::group::Inspect Mach-O binaries" echo "::group::Inspect Mach-O binaries"
for app in cockatrice oracle servatrice dbconverter; do for app in cockatrice oracle servatrice; do
binary="$GITHUB_WORKSPACE/build/$app/$app.app/Contents/MacOS/$app" binary="$GITHUB_WORKSPACE/build/$app/$app.app/Contents/MacOS/$app"
echo "Inspecting $app..." echo "Inspecting $app..."
vtool -show-build "$binary" vtool -show-build "$binary"

View file

@ -142,7 +142,6 @@ jobs:
- distro: Ubuntu - distro: Ubuntu
version: 22.04 version: 22.04
package: DEB package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu - distro: Ubuntu
version: 24.04 version: 24.04
@ -166,7 +165,7 @@ jobs:
- name: Restore compiler cache (ccache) - name: Restore compiler cache (ccache)
id: ccache_restore id: ccache_restore
uses: actions/cache/restore@v4 uses: actions/cache/restore@v5
env: env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with: with:
@ -205,7 +204,7 @@ jobs:
- name: Save compiler cache (ccache) - name: Save compiler cache (ccache)
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v4 uses: actions/cache/save@v5
with: with:
path: ${{env.CACHE}} path: ${{env.CACHE}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }} key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
@ -213,7 +212,7 @@ jobs:
- name: Upload artifact - name: Upload artifact
id: upload_artifact id: upload_artifact
if: matrix.package != 'skip' if: matrix.package != 'skip'
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: ${{matrix.distro}}${{matrix.version}}-package name: ${{matrix.distro}}${{matrix.version}}-package
path: ${{steps.build.outputs.path}} path: ${{steps.build.outputs.path}}
@ -336,6 +335,11 @@ jobs:
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure needs: configure
runs-on: ${{matrix.runner}} runs-on: ${{matrix.runner}}
env:
CCACHE_DIR: ${{github.workspace}}/.cache/
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 500M
steps: steps:
- name: Checkout - name: Checkout
@ -350,16 +354,20 @@ jobs:
with: with:
msbuild-architecture: x64 msbuild-architecture: x64
# Using jianmingyong/ccache-action to setup ccache without using brew
# It tries to download a binary of ccache from GitHub Release and falls back to building from source if it fails
- name: Setup ccache - name: Setup ccache
if: matrix.use_ccache == 1 && matrix.os == 'macOS'
run: brew install ccache
- name: Restore compiler cache (ccache)
if: matrix.use_ccache == 1 if: matrix.use_ccache == 1
uses: jianmingyong/ccache-action@v1 id: ccache_restore
uses: actions/cache/restore@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with: with:
install-type: "binary" path: ${{env.CCACHE_DIR}}
ccache-key-prefix: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}} key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
max-size: 500M restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-
gh-token: ${{ secrets.GITHUB_TOKEN }}
- name: Restore thin Qt ${{matrix.qt_version}} libraries - name: Restore thin Qt ${{matrix.qt_version}} libraries
if: matrix.os == 'macOS' if: matrix.os == 'macOS'
@ -429,6 +437,15 @@ jobs:
TARGET_MACOS_VERSION: ${{ matrix.override_target }} TARGET_MACOS_VERSION: ${{ matrix.override_target }}
run: .ci/compile.sh --server --test --vcpkg run: .ci/compile.sh --server --test --vcpkg
- name: Save compiler cache (ccache)
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1
uses: actions/cache/save@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
path: ${{env.CCACHE_DIR}}
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
- name: Sign app bundle - name: Sign app bundle
if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null) if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
env: env:
@ -476,7 +493,7 @@ jobs:
- name: Upload artifact - name: Upload artifact
id: upload_artifact id: upload_artifact
if: matrix.make_package if: matrix.make_package
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: ${{matrix.artifact_name}} name: ${{matrix.artifact_name}}
path: ${{steps.build.outputs.path}} path: ${{steps.build.outputs.path}}
@ -484,7 +501,7 @@ jobs:
- name: Upload pdb database - name: Upload pdb database
if: matrix.os == 'Windows' if: matrix.os == 'Windows'
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: Windows${{matrix.target}}-debug-pdbs name: Windows${{matrix.target}}-debug-pdbs
path: | path: |

View file

@ -33,7 +33,7 @@ jobs:
- name: Create pull request - name: Create pull request
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
id: create_pr id: create_pr
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v8
with: with:
add-paths: | add-paths: |
cockatrice/translations/*.ts cockatrice/translations/*.ts

View file

@ -57,7 +57,7 @@ jobs:
- name: Create pull request - name: Create pull request
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
id: create_pr id: create_pr
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v8
with: with:
add-paths: | add-paths: |
cockatrice/cockatrice_en@source.ts cockatrice/cockatrice_en@source.ts

View file

@ -20,8 +20,6 @@ option(WITH_SERVER "build servatrice" OFF)
option(WITH_CLIENT "build cockatrice" ON) option(WITH_CLIENT "build cockatrice" ON)
# Compile oracle # Compile oracle
option(WITH_ORACLE "build oracle" ON) option(WITH_ORACLE "build oracle" ON)
# Compile dbconverter
option(WITH_DBCONVERTER "build dbconverter" ON)
# Compile tests # Compile tests
option(TEST "build tests" OFF) option(TEST "build tests" OFF)
# Use vcpkg regardless of OS # Use vcpkg regardless of OS
@ -356,11 +354,6 @@ if(WITH_ORACLE)
set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif() endif()
if(WITH_DBCONVERTER)
add_subdirectory(dbconverter)
set(CPACK_INSTALL_CMAKE_PROJECTS "Dbconverter;Dbconverter;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
if(TEST) if(TEST)
include(CTest) include(CTest)
add_subdirectory(tests) add_subdirectory(tests)

View file

@ -20,7 +20,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN mkdir build && cd build && \ RUN mkdir build && cd build && \
cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=0 && \ cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 && \
make -j$(nproc) && \ make -j$(nproc) && \
make install make install

View file

@ -1068,7 +1068,6 @@ RECURSIVE = YES
EXCLUDE = build/ \ EXCLUDE = build/ \
cmake/ \ cmake/ \
dbconverter/ \
vcpkg/ \ vcpkg/ \
webclient/ webclient/

View file

@ -44,10 +44,10 @@ Latest <kbd>beta</kbd> version:
# Related Repositories # Related Repositories
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice - [Magic-Token](https://github.com/Cockatrice/Magic-Token): File with MtG token data for use in Cockatrice
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to use in Cockatrice - [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage - [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
- [Cockatrice @Flathub](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration for our Linux `flatpak` package - [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice)
# Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) # Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
@ -55,15 +55,31 @@ Latest <kbd>beta</kbd> version:
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out. Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
- [Official Website](https://cockatrice.github.io) - [Official Website](https://cockatrice.github.io)
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) - [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
- [Official Code Documentation](https://cockatrice.github.io/docs)
- [Official Discord](https://discord.gg/3Z9yzmA) - [Official Discord](https://discord.gg/3Z9yzmA)
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice) - [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
>[!IMPORTANT] > [!IMPORTANT]
>For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software. > For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software.
# Contribute # Contribute
<p>
<a href="#code">Code</a> <b>|</b>
<a href="#documentation-">Documentation</a> <b>|</b>
<a href="#translation-">Translation</a>
</p>
#### Repository Activity
![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats")
<details>
<summary><b>Kudos to all our amazing contributors ❤️</b></summary>
<br>
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
</a><br>
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
</details>
### Code ### Code
@ -77,18 +93,19 @@ This tag is used for issues that we are looking for somebody to pick up. Often t
For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code reviews for submitted changes.<br> For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code reviews for submitted changes.<br>
We'll happily advice on how best to implement a feature, or we can show you where the codebase is doing something similar before you get too far along - put a note on an issue you want to discuss more on! We'll happily advice on how best to implement a feature, or we can show you where the codebase is doing something similar before you get too far along - put a note on an issue you want to discuss more on!
You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code).
### Documentation [![CI Docs](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml/badge.svg?event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Apush)
There are various places where useful information for different needs are maintained:
- [Official Code Documentation](https://cockatrice.github.io/docs/)
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) `Community supported`
- [Official Webpage](https://cockatrice.github.io/)
- [Official README](https://github.com/Cockatrice/Cockatrice/blob/master/README.md) `This file`
Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide. Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
<details> ### Translation [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/)
<summary><b>Kudos to our amazing contributors ❤️</b></summary>
<br>
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
</a><br>
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
</details>
### Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/)
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br> Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
@ -126,8 +143,8 @@ You can then
make package make package
``` ```
>[!NOTE] > [!NOTE]
>Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice) > Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
<br> <br>

View file

@ -42,7 +42,6 @@ tell disk image_name
set position of item "Cockatrice.app" to { 139, 214 } set position of item "Cockatrice.app" to { 139, 214 }
set position of item "Oracle.app" to { 139, 414 } set position of item "Oracle.app" to { 139, 414 }
set position of item "Servatrice.app" to { 139, 614 } set position of item "Servatrice.app" to { 139, 614 }
set position of item "dbconverter.app" to { 1400, 1400 }
set position of item "Applications" to { 861, 414 } set position of item "Applications" to { 861, 414 }
end tell end tell
update without registering applications update without registering applications

View file

@ -1,12 +1,11 @@
# Find a compatible Qt version # Find a compatible Qt version
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, WITH_DBCONVERTER, FORCE_USE_QT5 # Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, FORCE_USE_QT5
# Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system # Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system
# Optional Input: QT5_DIR -- Hint as to where Qt5 lives on the system # Optional Input: QT5_DIR -- Hint as to where Qt5 lives on the system
# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6 # Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6
# Output: SERVATRICE_QT_MODULES # Output: SERVATRICE_QT_MODULES
# Output: COCKATRICE_QT_MODULES # Output: COCKATRICE_QT_MODULES
# Output: ORACLE_QT_MODULES # Output: ORACLE_QT_MODULES
# Output: DBCONVERTER_QT_MODULES
# Output: TEST_QT_MODULES # Output: TEST_QT_MODULES
set(REQUIRED_QT_COMPONENTS Core) set(REQUIRED_QT_COMPONENTS Core)
@ -29,15 +28,12 @@ endif()
if(WITH_ORACLE) if(WITH_ORACLE)
set(_ORACLE_NEEDED Concurrent Network Svg Widgets) set(_ORACLE_NEEDED Concurrent Network Svg Widgets)
endif() endif()
if(WITH_DBCONVERTER)
set(_DBCONVERTER_NEEDED Network Widgets)
endif()
if(TEST) if(TEST)
set(_TEST_NEEDED Widgets) set(_TEST_NEEDED Widgets)
endif() endif()
set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED} set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED}
${_DBCONVERTER_NEEDED} ${_TEST_NEEDED} ${_TEST_NEEDED}
) )
list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS) list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
@ -63,7 +59,7 @@ if(Qt6_FOUND)
endif() endif()
else() else()
find_package( find_package(
Qt5 5.8.0 Qt5 5.15.2
COMPONENTS ${REQUIRED_QT_COMPONENTS} COMPONENTS ${REQUIRED_QT_COMPONENTS}
QUIET HINTS ${Qt5_DIR} QUIET HINTS ${Qt5_DIR}
) )
@ -112,7 +108,6 @@ message(DEBUG "QT_LIBRARY_DIR = ${QT_LIBRARY_DIR}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" SERVATRICE_QT_MODULES "${_SERVATRICE_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" SERVATRICE_QT_MODULES "${_SERVATRICE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" COCKATRICE_QT_MODULES "${_COCKATRICE_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" COCKATRICE_QT_MODULES "${_COCKATRICE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MODULES "${_ORACLE_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MODULES "${_ORACLE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" DB_CONVERTER_QT_MODULES "${_DBCONVERTER_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}")
# Core-only export (useful for headless libs) # Core-only export (useful for headless libs)

View file

@ -213,7 +213,6 @@ ${AndIf} ${FileExists} "$INSTDIR\portable.dat"
Delete "$INSTDIR\uninstall.exe" Delete "$INSTDIR\uninstall.exe"
Delete "$INSTDIR\cockatrice.exe" Delete "$INSTDIR\cockatrice.exe"
Delete "$INSTDIR\oracle.exe" Delete "$INSTDIR\oracle.exe"
Delete "$INSTDIR\dbconverter.exe"
Delete "$INSTDIR\servatrice.exe" Delete "$INSTDIR\servatrice.exe"
Delete "$INSTDIR\Qt*.dll" Delete "$INSTDIR\Qt*.dll"
Delete "$INSTDIR\libmysql.dll" Delete "$INSTDIR\libmysql.dll"

View file

@ -3,7 +3,7 @@
string(LENGTH "$ENV{MACOS_CERTIFICATE_NAME}" MACOS_CERTIFICATE_NAME_LEN) string(LENGTH "$ENV{MACOS_CERTIFICATE_NAME}" MACOS_CERTIFICATE_NAME_LEN)
if(APPLE AND MACOS_CERTIFICATE_NAME_LEN GREATER 0) if(APPLE AND MACOS_CERTIFICATE_NAME_LEN GREATER 0)
set(APPLICATIONS "cockatrice" "servatrice" "oracle" "dbconverter") set(APPLICATIONS "cockatrice" "servatrice" "oracle")
foreach(app_name IN LISTS APPLICATIONS) foreach(app_name IN LISTS APPLICATIONS)
set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app") set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app")

View file

@ -156,6 +156,7 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp 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_list_style_proxy.cpp
src/interface/widgets/deck_editor/deck_state_manager.cpp
src/interface/widgets/general/background_sources.cpp src/interface/widgets/general/background_sources.cpp
src/interface/widgets/general/display/background_plate_widget.cpp src/interface/widgets/general/display/background_plate_widget.cpp
src/interface/widgets/general/display/banner_widget.cpp src/interface/widgets/general/display/banner_widget.cpp
@ -230,6 +231,7 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp
@ -286,6 +288,10 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/interface/key_signals.cpp src/interface/key_signals.cpp
src/interface/logger.cpp src/interface/logger.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
) )
add_subdirectory(sounds) add_subdirectory(sounds)

View file

@ -15,15 +15,18 @@ searches are case insensitive.
<dd>[n:red n:deck n:wins](#n:red n:deck n:wins) <small>(Any deck with a name containing the words red, deck, and wins)</small></dd> <dd>[n:red n:deck n:wins](#n:red n:deck n:wins) <small>(Any deck with a name containing the words red, deck, and wins)</small></dd>
<dd>[n:"red deck wins"](#n:%22red deck wins%22) <small>(Any deck with a name containing the exact phrase "red deck wins")</small></dd> <dd>[n:"red deck wins"](#n:%22red deck wins%22) <small>(Any deck with a name containing the exact phrase "red deck wins")</small></dd>
<dt><u>F</u>ile Name:</dt> <dt><u>F</u>ile <u>N</u>ame:</dt>
<dd>[f:aggro](#f:aggro) <small>(Any deck with a filename containing the word aggro)</small></dd> <dd>[fn:aggro](#fn:aggro) <small>(Any deck with a filename containing the word aggro)</small></dd>
<dd>[f:red f:deck f:wins](#f:red f:deck f:wins) <small>(Any deck with a filename containing the words red, deck, and wins)</small></dd> <dd>[fn:red fn:deck fn:wins](#fn:red fn:deck fn:wins) <small>(Any deck with a filename containing the words red, deck, and wins)</small></dd>
<dd>[f:"red deck wins"](#f:%22red deck wins%22) <small>(Any deck with a filename containing the exact phrase "red deck wins")</small></dd> <dd>[fn:"red deck wins"](#fn:%22red deck wins%22) <small>(Any deck with a filename containing the exact phrase "red deck wins")</small></dd>
<dt>Relative <u>P</u>ath (starting from the deck folder):</dt> <dt>Relative <u>P</u>ath (starting from the deck folder):</dt>
<dd>[p:aggro](#p:aggro) <small>(Any deck that has "aggro" somewhere in its relative path)</small></dd> <dd>[p:aggro](#p:aggro) <small>(Any deck that has "aggro" somewhere in its relative path)</small></dd>
<dd>[p:edh/](#p:edh/) <small>(Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)</small></dd> <dd>[p:edh/](#p:edh/) <small>(Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)</small></dd>
<dt><u>F</u>ormat:</dt>
<dd>[f:standard](#f:standard) <small>(Any deck with format set to standard)</small></dd>
<dt>Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):</dt> <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="#[[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> <dd><a href="#[[t:legendary]]">[[t:legendary]]</a> <small>(Any deck that contains at least one legendary)</small></dd>

View file

@ -43,23 +43,23 @@ void DeckStatsInterface::queryFinished(QNetworkReply *reply)
deleteLater(); deleteLater();
} }
void DeckStatsInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data) void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
{ {
DeckList deckWithoutTokens; DeckList deckWithoutTokens;
copyDeckWithoutTokens(*deck, deckWithoutTokens); copyDeckWithoutTokens(deck, deckWithoutTokens);
QUrl params; QUrl params;
QUrlQuery urlQuery; QUrlQuery urlQuery;
urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain()); urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain());
urlQuery.addQueryItem("decktitle", deck->getName()); urlQuery.addQueryItem("decktitle", deck.getName());
params.setQuery(urlQuery); params.setQuery(urlQuery);
data->append(params.query(QUrl::EncodeReserved).toUtf8()); data.append(params.query(QUrl::EncodeReserved).toUtf8());
} }
void DeckStatsInterface::analyzeDeck(DeckList *deck) void DeckStatsInterface::analyzeDeck(const DeckList &deck)
{ {
QByteArray data; QByteArray data;
getAnalyzeRequestData(deck, &data); getAnalyzeRequestData(deck, data);
QNetworkRequest request(QUrl("https://deckstats.net/index.php")); QNetworkRequest request(QUrl("https://deckstats.net/index.php"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
@ -68,7 +68,7 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck)
manager->post(request, data); manager->post(request, data);
} }
void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination) void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination)
{ {
auto copyIfNotAToken = [this, &destination](const auto node, const auto card) { auto copyIfNotAToken = [this, &destination](const auto node, const auto card) {
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());

View file

@ -28,15 +28,15 @@ private:
* closest non-token card instead. So we construct a new deck which has no * closest non-token card instead. So we construct a new deck which has no
* tokens. * tokens.
*/ */
void copyDeckWithoutTokens(DeckList &source, DeckList &destination); void copyDeckWithoutTokens(const DeckList &source, DeckList &destination);
private slots: private slots:
void queryFinished(QNetworkReply *reply); void queryFinished(QNetworkReply *reply);
void getAnalyzeRequestData(DeckList *deck, QByteArray *data); void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public: public:
explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
void analyzeDeck(DeckList *deck); void analyzeDeck(const DeckList &deck);
}; };
#endif #endif

View file

@ -67,24 +67,24 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply)
deleteLater(); deleteLater();
} }
void TappedOutInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data) void TappedOutInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
{ {
DeckList mainboard, sideboard; DeckList mainboard, sideboard;
copyDeckSplitMainAndSide(*deck, mainboard, sideboard); copyDeckSplitMainAndSide(deck, mainboard, sideboard);
QUrl params; QUrl params;
QUrlQuery urlQuery; QUrlQuery urlQuery;
urlQuery.addQueryItem("name", deck->getName()); urlQuery.addQueryItem("name", deck.getName());
urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true)); urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true));
urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true)); urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true));
params.setQuery(urlQuery); params.setQuery(urlQuery);
data->append(params.query(QUrl::EncodeReserved).toUtf8()); data.append(params.query(QUrl::EncodeReserved).toUtf8());
} }
void TappedOutInterface::analyzeDeck(DeckList *deck) void TappedOutInterface::analyzeDeck(const DeckList &deck)
{ {
QByteArray data; QByteArray data;
getAnalyzeRequestData(deck, &data); getAnalyzeRequestData(deck, data);
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/")); QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
@ -93,7 +93,7 @@ void TappedOutInterface::analyzeDeck(DeckList *deck)
manager->post(request, data); manager->post(request, data);
} }
void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard) void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard)
{ {
auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) { auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());

View file

@ -30,14 +30,14 @@ private:
QNetworkAccessManager *manager; QNetworkAccessManager *manager;
CardDatabase &cardDatabase; CardDatabase &cardDatabase;
void copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard); void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard);
private slots: private slots:
void queryFinished(QNetworkReply *reply); void queryFinished(QNetworkReply *reply);
void getAnalyzeRequestData(DeckList *deck, QByteArray *data); void getAnalyzeRequestData(const DeckList &deck, QByteArray &data);
public: public:
explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
void analyzeDeck(DeckList *deck); void analyzeDeck(const DeckList &deck);
}; };
#endif #endif

View file

@ -18,21 +18,21 @@ class IJsonDeckParser
public: public:
virtual ~IJsonDeckParser() = default; virtual ~IJsonDeckParser() = default;
virtual DeckLoader *parse(const QJsonObject &obj) = 0; virtual DeckList parse(const QJsonObject &obj) = 0;
}; };
class ArchidektJsonParser : public IJsonDeckParser class ArchidektJsonParser : public IJsonDeckParser
{ {
public: public:
DeckLoader *parse(const QJsonObject &obj) override DeckList parse(const QJsonObject &obj) override
{ {
DeckLoader *loader = new DeckLoader(nullptr); DeckList deckList;
QString deckName = obj.value("name").toString(); QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString(); QString deckDescription = obj.value("description").toString();
loader->getDeckList()->setName(deckName); deckList.setName(deckName);
loader->getDeckList()->setComments(deckDescription); deckList.setComments(deckDescription);
QString outputText; QString outputText;
QTextStream outStream(&outputText); QTextStream outStream(&outputText);
@ -49,25 +49,25 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
} }
loader->getDeckList()->loadFromStream_Plain(outStream, false); deckList.loadFromStream_Plain(outStream, false);
loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); deckList.forEachCard(CardNodeFunction::ResolveProviderId());
return loader; return deckList;
} }
}; };
class MoxfieldJsonParser : public IJsonDeckParser class MoxfieldJsonParser : public IJsonDeckParser
{ {
public: public:
DeckLoader *parse(const QJsonObject &obj) override DeckList parse(const QJsonObject &obj) override
{ {
DeckLoader *loader = new DeckLoader(nullptr); DeckList deckList;
QString deckName = obj.value("name").toString(); QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString(); QString deckDescription = obj.value("description").toString();
loader->getDeckList()->setName(deckName); deckList.setName(deckName);
loader->getDeckList()->setComments(deckDescription); deckList.setComments(deckDescription);
QString outputText; QString outputText;
QTextStream outStream(&outputText); QTextStream outStream(&outputText);
@ -96,8 +96,8 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
} }
loader->getDeckList()->loadFromStream_Plain(outStream, false); deckList.loadFromStream_Plain(outStream, false);
loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); deckList.forEachCard(CardNodeFunction::ResolveProviderId());
QJsonObject commandersObj = obj.value("commanders").toObject(); QJsonObject commandersObj = obj.value("commanders").toObject();
if (!commandersObj.isEmpty()) { if (!commandersObj.isEmpty()) {
@ -108,12 +108,12 @@ public:
QString collectorNumber = cardData.value("cn").toString(); QString collectorNumber = cardData.value("cn").toString();
QString providerId = cardData.value("scryfall_id").toString(); QString providerId = cardData.value("scryfall_id").toString();
loader->getDeckList()->setBannerCard({commanderName, providerId}); deckList.setBannerCard({commanderName, providerId});
loader->getDeckList()->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId); deckList.addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
} }
} }
return loader; return deckList;
} }
}; };

View file

@ -2,6 +2,7 @@
#include "../network/update/client/release_channel.h" #include "../network/update/client/release_channel.h"
#include "card_counter_settings.h" #include "card_counter_settings.h"
#include "version_string.h"
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QApplication> #include <QApplication>
@ -198,7 +199,13 @@ SettingsCache::SettingsCache()
mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool(); mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool();
if (settings->contains("personal/startupUpdateCheck")) {
checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool(); checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool();
} else if (QString(VERSION_STRING).contains("custom", Qt::CaseInsensitive)) {
checkUpdatesOnStartup = false; // do not run auto updater on custom version
} else {
checkUpdatesOnStartup = true; // default to run auto updater
}
startupCardUpdateCheckPromptForUpdate = startupCardUpdateCheckPromptForUpdate =
settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool(); settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool();
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool(); startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
@ -206,7 +213,15 @@ SettingsCache::SettingsCache()
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate(); lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool(); notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool(); notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt();
if (settings->contains("personal/updatereleasechannel")) {
updateReleaseChannel = settings->value("personal/updatereleasechannel").toInt();
} else if (QString(VERSION_STRING).contains("beta", Qt::CaseInsensitive)) {
// default to beta if this is a beta release
updateReleaseChannel = 1;
} else {
updateReleaseChannel = 0; // stable
}
lang = settings->value("personal/lang").toString(); lang = settings->value("personal/lang").toString();
keepalive = settings->value("personal/keepalive", 3).toInt(); keepalive = settings->value("personal/keepalive", 3).toInt();

View file

@ -150,12 +150,7 @@ void ShortcutTreeView::currentChanged(const QModelIndex &current, const QModelIn
*/ */
void ShortcutTreeView::updateSearchString(const QString &searchString) void ShortcutTreeView::updateSearchString(const QString &searchString)
{ {
#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0) QStringList searchWords = searchString.split(" ", Qt::SkipEmptyParts);
const auto skipEmptyParts = Qt::SkipEmptyParts;
#else
const auto skipEmptyParts = QString::SkipEmptyParts;
#endif
QStringList searchWords = searchString.split(" ", skipEmptyParts);
auto escapeRegex = [](const QString &s) { return QRegularExpression::escape(s); }; auto escapeRegex = [](const QString &s) { return QRegularExpression::escape(s); };
std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex); std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex);

View file

@ -13,7 +13,7 @@ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws*
ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / GenericQuery QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / GenericQuery
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
@ -22,8 +22,9 @@ CardSearch <- '[[' CardFilterString ']]'
CardFilterString <- (!']]'.)* CardFilterString <- (!']]'.)*
DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String
FileNameQuery <- [Ff] ('ile' 'name'?)? [:] String FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String
PathQuery <- [Pp] 'ath'? [:] String PathQuery <- [Pp] 'ath'? [:] String
FormatQuery <- [Ff] 'ormat'? [:] String
GenericQuery <- String GenericQuery <- String
@ -118,7 +119,7 @@ static void setupParserRules()
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool { return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool {
int count = 0; int count = 0;
auto cardNodes = deck->deckLoader->getDeckList()->getCardNodes(); auto cardNodes = deck->deckLoader->getDeck().deckList.getCardNodes();
for (auto node : cardNodes) { for (auto node : cardNodes) {
auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName()); auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName());
if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) { if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) {
@ -138,7 +139,7 @@ static void setupParserRules()
search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast<QString>(sv[0]); auto name = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
return deck->deckLoader->getDeckList()->getName().contains(name, Qt::CaseInsensitive); return deck->deckLoader->getDeck().deckList.getName().contains(name, Qt::CaseInsensitive);
}; };
}; };
@ -157,6 +158,14 @@ static void setupParserRules()
}; };
}; };
search["FormatQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto format = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
auto gameFormat = deck->deckLoader->getDeck().deckList.getGameFormat();
return QString::compare(format, gameFormat, Qt::CaseInsensitive) == 0;
};
};
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast<QString>(sv[0]); auto name = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {

View file

@ -269,12 +269,12 @@ void DeckViewContainer::loadDeckFromFile(const QString &filePath)
return; return;
} }
loadDeckFromDeckLoader(&deck); loadDeckFromDeckList(deck.getDeck().deckList);
} }
void DeckViewContainer::loadDeckFromDeckLoader(DeckLoader *deck) void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck)
{ {
QString deckString = deck->getDeckList()->writeToString_Native(); QString deckString = deck.writeToString_Native();
if (deckString.length() > MAX_FILE_LENGTH) { if (deckString.length() > MAX_FILE_LENGTH) {
QMessageBox::critical(this, tr("Error"), tr("Deck is greater than maximum file size.")); QMessageBox::critical(this, tr("Error"), tr("Deck is greater than maximum file size."));
@ -308,8 +308,8 @@ void DeckViewContainer::loadFromClipboard()
return; return;
} }
DeckLoader *deck = dlg.getDeckList(); DeckList deck = dlg.getDeckList();
loadDeckFromDeckLoader(deck); loadDeckFromDeckList(deck);
} }
void DeckViewContainer::loadFromWebsite() void DeckViewContainer::loadFromWebsite()
@ -320,16 +320,15 @@ void DeckViewContainer::loadFromWebsite()
return; return;
} }
DeckLoader *deck = dlg.getDeck(); DeckList deck = dlg.getDeck();
loadDeckFromDeckLoader(deck); loadDeckFromDeckList(deck);
} }
void DeckViewContainer::deckSelectFinished(const Response &r) void DeckViewContainer::deckSelectFinished(const Response &r)
{ {
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
DeckLoader newDeck(this, new DeckList(QString::fromStdString(resp.deck()))); DeckList newDeck = DeckList(QString::fromStdString(resp.deck()));
CardPictureLoader::cacheCardPixmaps( CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(newDeck.getCardRefList()));
CardDatabaseManager::query()->getCards(newDeck.getDeckList()->getCardRefList()));
setDeck(newDeck); setDeck(newDeck);
switchToDeckLoadedView(); switchToDeckLoadedView();
} }
@ -410,8 +409,8 @@ void DeckViewContainer::setSideboardLocked(bool locked)
deckView->resetSideboardPlan(); deckView->resetSideboardPlan();
} }
void DeckViewContainer::setDeck(DeckLoader &deck) void DeckViewContainer::setDeck(const DeckList &deck)
{ {
deckView->setDeck(*deck.getDeckList()); deckView->setDeck(deck);
switchToDeckLoadedView(); switchToDeckLoadedView();
} }

View file

@ -85,12 +85,12 @@ public:
void setReadyStart(bool ready); void setReadyStart(bool ready);
void readyAndUpdate(); void readyAndUpdate();
void setSideboardLocked(bool locked); void setSideboardLocked(bool locked);
void setDeck(DeckLoader &deck); void setDeck(const DeckList &deck);
void setVisualDeckStorageExists(bool exists); void setVisualDeckStorageExists(bool exists);
public slots: public slots:
void loadDeckFromFile(const QString &filePath); void loadDeckFromFile(const QString &filePath);
void loadDeckFromDeckLoader(DeckLoader *deck); void loadDeckFromDeckList(const DeckList &deck);
}; };
#endif // DECK_VIEW_CONTAINER_H #endif // DECK_VIEW_CONTAINER_H

View file

@ -117,11 +117,7 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa
chooseTokenFromDeckRadioButton->setDisabled(true); // No tokens in deck = no need for option chooseTokenFromDeckRadioButton->setDisabled(true); // No tokens in deck = no need for option
} else { } else {
chooseTokenFromDeckRadioButton->setChecked(true); chooseTokenFromDeckRadioButton->setChecked(true);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end())); cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
#else
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>::fromList(predefinedTokens));
#endif
} }
auto *tokenChooseLayout = new QVBoxLayout; auto *tokenChooseLayout = new QVBoxLayout;
@ -223,11 +219,7 @@ void DlgCreateToken::actChooseTokenFromAll(bool checked)
void DlgCreateToken::actChooseTokenFromDeck(bool checked) void DlgCreateToken::actChooseTokenFromDeck(bool checked)
{ {
if (checked) { if (checked) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end())); cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
#else
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>::fromList(predefinedTokens));
#endif
} }
} }

View file

@ -60,13 +60,13 @@ void UtilityMenu::populatePredefinedTokensMenu()
clear(); clear();
setEnabled(false); setEnabled(false);
predefinedTokens.clear(); predefinedTokens.clear();
DeckLoader *_deck = player->getDeck(); const DeckList &deckList = player->getDeck();
if (!_deck) { if (deckList.isEmpty()) {
return; return;
} }
auto tokenCardNodes = _deck->getDeckList()->getCardNodes({DECK_ZONE_TOKENS}); auto tokenCardNodes = deckList.getCardNodes({DECK_ZONE_TOKENS});
if (!tokenCardNodes.isEmpty()) { if (!tokenCardNodes.isEmpty()) {
setEnabled(true); setEnabled(true);

View file

@ -32,7 +32,7 @@
Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent) Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent)
: QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)), : QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)),
playerEventHandler(new PlayerEventHandler(this)), playerActions(new PlayerActions(this)), active(false), playerEventHandler(new PlayerEventHandler(this)), playerActions(new PlayerActions(this)), active(false),
conceded(false), deck(nullptr), zoneId(0), dialogSemaphore(false) conceded(false), zoneId(0), dialogSemaphore(false)
{ {
initializeZones(); initializeZones();
@ -263,10 +263,9 @@ void Player::deleteCard(CardItem *card)
} }
} }
// TODO: Does a player need a DeckLoader? void Player::setDeck(const DeckList &_deck)
void Player::setDeck(DeckLoader &_deck)
{ {
deck = new DeckLoader(this, _deck.getDeckList()); deck = _deck;
emit deckChanged(); emit deckChanged();
} }

View file

@ -9,6 +9,7 @@
#include "../../game_graphics/board/abstract_graphics_item.h" #include "../../game_graphics/board/abstract_graphics_item.h"
#include "../../interface/widgets/menus/tearoff_menu.h" #include "../../interface/widgets/menus/tearoff_menu.h"
#include "../interface/deck_loader/loaded_deck.h"
#include "../zones/logic/hand_zone_logic.h" #include "../zones/logic/hand_zone_logic.h"
#include "../zones/logic/pile_zone_logic.h" #include "../zones/logic/pile_zone_logic.h"
#include "../zones/logic/stack_zone_logic.h" #include "../zones/logic/stack_zone_logic.h"
@ -44,7 +45,6 @@ class ArrowTarget;
class CardDatabase; class CardDatabase;
class CardZone; class CardZone;
class CommandContainer; class CommandContainer;
class DeckLoader;
class GameCommand; class GameCommand;
class GameEvent; class GameEvent;
class PlayerInfo; class PlayerInfo;
@ -66,7 +66,7 @@ class Player : public QObject
Q_OBJECT Q_OBJECT
signals: signals:
void openDeckEditor(DeckLoader *deck); void openDeckEditor(const LoadedDeck &deck);
void deckChanged(); void deckChanged();
void newCardAdded(AbstractCardItem *card); void newCardAdded(AbstractCardItem *card);
void rearrangeCounters(); void rearrangeCounters();
@ -130,9 +130,9 @@ public:
return playerMenu; return playerMenu;
} }
void setDeck(DeckLoader &_deck); void setDeck(const DeckList &_deck);
[[nodiscard]] DeckLoader *getDeck() const [[nodiscard]] const DeckList &getDeck() const
{ {
return deck; return deck;
} }
@ -241,7 +241,7 @@ private:
bool active; bool active;
bool conceded; bool conceded;
DeckLoader *deck; DeckList deck;
int zoneId; int zoneId;
QMap<QString, CardZoneLogic *> zones; QMap<QString, CardZoneLogic *> zones;

View file

@ -218,7 +218,7 @@ void PlayerActions::actAlwaysLookAtTopCard()
void PlayerActions::actOpenDeckInDeckEditor() void PlayerActions::actOpenDeckInDeckEditor()
{ {
emit player->openDeckEditor(player->getDeck()); emit player->openDeckEditor({.deckList = player->getDeck()});
} }
void PlayerActions::actViewGraveyard() void PlayerActions::actViewGraveyard()

View file

@ -78,14 +78,7 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event)
void PlayerEventHandler::eventRollDie(const Event_RollDie &event) void PlayerEventHandler::eventRollDie(const Event_RollDie &event)
{ {
if (!event.values().empty()) { if (!event.values().empty()) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QList<uint> rolls(event.values().begin(), event.values().end()); QList<uint> rolls(event.values().begin(), event.values().end());
#else
QList<uint> rolls;
for (const auto &value : event.values()) {
rolls.append(value);
}
#endif
std::sort(rolls.begin(), rolls.end()); std::sort(rolls.begin(), rolls.end());
emit logRollDie(player, static_cast<int>(event.sides()), rolls); emit logRollDie(player, static_cast<int>(event.sides()), rolls);
} else if (event.value()) { } else if (event.value()) {

View file

@ -13,12 +13,20 @@
#include <QGraphicsLinearLayout> #include <QGraphicsLinearLayout>
#include <QGraphicsProxyWidget> #include <QGraphicsProxyWidget>
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QLabel> #include <QLabel>
#include <QPainter> #include <QPainter>
#include <QScrollBar> #include <QScrollBar>
#include <QStyle>
#include <QStyleOption> #include <QStyleOption>
#include <libcockatrice/protocol/pb/command_shuffle.pb.h> #include <libcockatrice/protocol/pb/command_shuffle.pb.h>
namespace
{
constexpr qreal kTitleBarHeight = 24.0;
constexpr qreal kMinVisibleWidth = 100.0;
} // namespace
/** /**
* @param _player the player the cards were revealed to. * @param _player the player the cards were revealed to.
* @param _origZone the zone the cards were revealed from. * @param _origZone the zone the cards were revealed from.
@ -241,33 +249,182 @@ void ZoneViewWidget::retranslateUi()
pileViewCheckBox.setText(tr("pile view")); pileViewCheckBox.setText(tr("pile view"));
} }
void ZoneViewWidget::moveEvent(QGraphicsSceneMoveEvent * /* event */) void ZoneViewWidget::stopWindowDrag()
{ {
if (!scene()) if (!draggingWindow)
return; return;
int titleBarHeight = 24; draggingWindow = false;
ungrabMouse();
}
QPointF scenePos = pos(); void ZoneViewWidget::startWindowDrag(QGraphicsSceneMouseEvent *event)
{
draggingWindow = true;
dragStartItemPos = pos();
dragStartScreenPos = event->screenPos();
dragView = findDragView(event->widget());
if (scenePos.x() < 0) { // need to grab mouse to receive events and not miss initial movement
scenePos.setX(0); grabMouse();
} else { }
qreal maxw = scene()->sceneRect().width() - 100;
if (scenePos.x() > maxw) QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const
scenePos.setX(maxw); {
const QRectF frameRectF = windowFrameRect();
const QRect titleBarRect(frameRectF.toRect().x(), frameRectF.toRect().y(), frameRectF.toRect().width(),
static_cast<int>(kTitleBarHeight));
// query the style for the close button position (handles macOS top-left placement)
if (styleWidget) {
QStyleOptionTitleBar opt;
opt.initFrom(styleWidget);
opt.rect = titleBarRect;
opt.text = windowTitle();
opt.icon = styleWidget->windowIcon();
opt.titleBarFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
opt.subControls = QStyle::SC_TitleBarCloseButton;
opt.activeSubControls = QStyle::SC_TitleBarCloseButton;
opt.titleBarState = styleWidget->isActiveWindow() ? Qt::WindowActive : Qt::WindowNoState;
if (styleWidget->isActiveWindow())
opt.state |= QStyle::State_Active;
const QRect r = styleWidget->style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarCloseButton,
styleWidget);
if (r.isValid() && !r.isEmpty()) {
return QRectF(r);
}
} }
if (scenePos.y() < titleBarHeight) { // fallback: square at right end of titlebar (Windows/Linux style)
scenePos.setY(titleBarHeight); return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight);
} else { }
qreal maxh = scene()->sceneRect().height() - titleBarHeight;
if (scenePos.y() > maxh) QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const
scenePos.setY(maxh); {
QWidget *current = eventWidget;
while (current) {
if (auto *view = qobject_cast<QGraphicsView *>(current))
return view;
current = current->parentWidget();
} }
if (scenePos != pos()) if (scene() && !scene()->views().isEmpty())
setPos(scenePos); return scene()->views().constFirst();
return nullptr;
}
QPointF ZoneViewWidget::calcDraggedWindowPos(const QPoint &screenPos,
const QPointF &scenePos,
const QPointF &buttonDownScenePos) const
{
if (dragView && dragView->viewport()) {
const QPoint vpStart = dragView->viewport()->mapFromGlobal(dragStartScreenPos);
const QPoint vpNow = dragView->viewport()->mapFromGlobal(screenPos);
const QPointF sceneStart = dragView->mapToScene(vpStart);
const QPointF sceneNow = dragView->mapToScene(vpNow);
return dragStartItemPos + (sceneNow - sceneStart);
}
return dragStartItemPos + (scenePos - buttonDownScenePos);
}
bool ZoneViewWidget::windowFrameEvent(QEvent *event)
{
if (event->type() == QEvent::UngrabMouse) {
stopWindowDrag();
return QGraphicsWidget::windowFrameEvent(event);
}
auto *me = dynamic_cast<QGraphicsSceneMouseEvent *>(event);
if (!me)
return QGraphicsWidget::windowFrameEvent(event);
switch (event->type()) {
case QEvent::GraphicsSceneMousePress:
if (me->button() == Qt::LeftButton && windowFrameSectionAt(me->pos()) == Qt::TitleBarArea) {
// avoid drag on close button
if (closeButtonRect(me->widget()).contains(me->pos())) {
me->accept();
close();
return true;
}
startWindowDrag(me);
me->accept();
return true;
}
break;
case QEvent::GraphicsSceneMouseMove:
if (draggingWindow) {
if (!(me->buttons() & Qt::LeftButton)) {
stopWindowDrag();
} else {
setPos(
calcDraggedWindowPos(me->screenPos(), me->scenePos(), me->buttonDownScenePos(Qt::LeftButton)));
}
me->accept();
return true;
}
break;
case QEvent::GraphicsSceneMouseRelease:
if (draggingWindow && me->button() == Qt::LeftButton) {
stopWindowDrag();
me->accept();
return true;
}
break;
default:
break;
}
return QGraphicsWidget::windowFrameEvent(event);
}
void ZoneViewWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
// move if the scene routes moves while dragging
if (draggingWindow && (event->buttons() & Qt::LeftButton)) {
setPos(calcDraggedWindowPos(event->screenPos(), event->scenePos(), event->buttonDownScenePos(Qt::LeftButton)));
event->accept();
return;
}
QGraphicsWidget::mouseMoveEvent(event);
}
void ZoneViewWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (draggingWindow && event->button() == Qt::LeftButton) {
stopWindowDrag();
event->accept();
return;
}
QGraphicsWidget::mouseReleaseEvent(event);
}
QVariant ZoneViewWidget::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemPositionChange && scene()) {
// Keep grab area in main view
const QRectF sceneRect = scene()->sceneRect();
const QPointF requestedPos = value.toPointF();
QPointF desiredPos = requestedPos;
const qreal minX = sceneRect.left();
const qreal maxX = qMax(minX, sceneRect.right() - kMinVisibleWidth);
const qreal minY = sceneRect.top() + kTitleBarHeight;
const qreal maxY = qMax(minY, sceneRect.bottom() - kTitleBarHeight);
desiredPos.setX(qBound(minX, desiredPos.x(), maxX));
desiredPos.setY(qBound(minY, desiredPos.y(), maxY));
return desiredPos;
}
return QGraphicsWidget::itemChange(change, value);
} }
void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event) void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
@ -350,6 +507,7 @@ void ZoneViewWidget::handleScrollBarChange(int value)
void ZoneViewWidget::closeEvent(QCloseEvent *event) void ZoneViewWidget::closeEvent(QCloseEvent *event)
{ {
stopWindowDrag();
disconnect(zone, &ZoneViewZone::closed, this, 0); disconnect(zone, &ZoneViewZone::closed, this, 0);
// manually call zone->close in order to remove it from the origZones views // manually call zone->close in order to remove it from the origZones views
zone->close(); zone->close();

View file

@ -3,7 +3,6 @@
* @ingroup GameGraphicsZones * @ingroup GameGraphicsZones
* @brief TODO: Document this. * @brief TODO: Document this.
*/ */
#ifndef ZONEVIEWWIDGET_H #ifndef ZONEVIEWWIDGET_H
#define ZONEVIEWWIDGET_H #define ZONEVIEWWIDGET_H
@ -14,6 +13,7 @@
#include <QGraphicsProxyWidget> #include <QGraphicsProxyWidget>
#include <QGraphicsWidget> #include <QGraphicsWidget>
#include <QLineEdit> #include <QLineEdit>
#include <QPointer>
#include <libcockatrice/utility/macros.h> #include <libcockatrice/utility/macros.h>
class QLabel; class QLabel;
@ -28,6 +28,8 @@ class ServerInfo_Card;
class QGraphicsSceneMouseEvent; class QGraphicsSceneMouseEvent;
class QGraphicsSceneWheelEvent; class QGraphicsSceneWheelEvent;
class QStyleOption; class QStyleOption;
class QGraphicsView;
class QWidget;
class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget
{ {
@ -66,6 +68,33 @@ private:
int extraHeight; int extraHeight;
Player *player; Player *player;
bool draggingWindow = false;
QPoint dragStartScreenPos;
QPointF dragStartItemPos;
QPointer<QGraphicsView> dragView;
void stopWindowDrag();
void startWindowDrag(QGraphicsSceneMouseEvent *event);
QRectF closeButtonRect(QWidget *styleWidget) const;
/**
* @brief Resolves the QGraphicsView to use for drag coordinate mapping
*
* @param eventWidget QWidget that originated the mouse event
* @return The resolved QGraphicsView
*/
QGraphicsView *findDragView(QWidget *eventWidget) const;
/**
* @brief Calculates the desired widget position while dragging
*
* @param screenPos Global screen coordinates of the current mouse position
* @param scenePos Scene coordinates of the current mouse position
* @param buttonDownScenePos Scene coordinates of the initial mouse press position
*
* @return The new widget position in scene coordinates
*/
QPointF
calcDraggedWindowPos(const QPoint &screenPos, const QPointF &scenePos, const QPointF &buttonDownScenePos) const;
void resizeScrollbar(qreal newZoneHeight); void resizeScrollbar(qreal newZoneHeight);
signals: signals:
void closePressed(ZoneViewWidget *zv); void closePressed(ZoneViewWidget *zv);
@ -76,7 +105,6 @@ private slots:
void resizeToZoneContents(bool forceInitialHeight = false); void resizeToZoneContents(bool forceInitialHeight = false);
void handleScrollBarChange(int value); void handleScrollBarChange(int value);
void zoneDeleted(); void zoneDeleted();
void moveEvent(QGraphicsSceneMoveEvent * /* event */) override;
void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override; void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override;
void expandWindow(); void expandWindow();
@ -101,6 +129,10 @@ public:
protected: protected:
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
void initStyleOption(QStyleOption *option) const override; void initStyleOption(QStyleOption *option) const override;
bool windowFrameEvent(QEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
}; };

View file

@ -17,12 +17,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number,
font.setWeight(QFont::Bold); font.setWeight(QFont::Bold);
QFontMetrics fm(font); QFontMetrics fm(font);
double w = 1.3 * double w = 1.3 * fm.horizontalAdvance(numStr);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
fm.horizontalAdvance(numStr);
#else
fm.width(numStr);
#endif
double h = fm.height() * 1.3; double h = fm.height() * 1.3;
if (w < h) if (w < h)
w = h; w = h;

View file

@ -21,9 +21,7 @@ CardPictureLoaderWorker::CardPictureLoaderWorker()
// We need a timeout to ensure requests don't hang indefinitely in case of // We need a timeout to ensure requests don't hang indefinitely in case of
// cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397 // cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397
// Use Qt's default timeout (30s, as of 2023-02-22) // Use Qt's default timeout (30s, as of 2023-02-22)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
networkManager->setTransferTimeout(); networkManager->setTransferTimeout();
#endif
cache = new QNetworkDiskCache(this); cache = new QNetworkDiskCache(this);
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath()); cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
cache->setMaximumCacheSize(1024L * 1024L * cache->setMaximumCacheSize(1024L * 1024L *

View file

@ -25,11 +25,7 @@ const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.d
const QStringList DeckLoader::FILE_NAME_FILTERS = { const QStringList DeckLoader::FILE_NAME_FILTERS = {
tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")}; tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")};
DeckLoader::DeckLoader(QObject *parent) : QObject(parent), deckList(new DeckList()) DeckLoader::DeckLoader(QObject *parent) : QObject(parent)
{
}
DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) : QObject(parent), deckList(_deckList)
{ {
} }
@ -41,17 +37,18 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm
} }
bool result = false; bool result = false;
DeckList deckList = DeckList();
switch (fmt) { switch (fmt) {
case DeckFileFormat::PlainText: case DeckFileFormat::PlainText:
result = deckList->loadFromFile_Plain(&file); result = deckList.loadFromFile_Plain(&file);
break; break;
case DeckFileFormat::Cockatrice: { case DeckFileFormat::Cockatrice: {
result = deckList->loadFromFile_Native(&file); result = deckList.loadFromFile_Native(&file);
qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result; qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result;
if (!result) { if (!result) {
qCInfo(DeckLoaderLog) << "Retrying as plain format"; qCInfo(DeckLoaderLog) << "Retrying as plain format";
file.seek(0); file.seek(0);
result = deckList->loadFromFile_Plain(&file); result = deckList.loadFromFile_Plain(&file);
fmt = DeckFileFormat::PlainText; fmt = DeckFileFormat::PlainText;
} }
break; break;
@ -62,7 +59,8 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm
} }
if (result) { if (result) {
lastLoadInfo = { loadedDeck.deckList = deckList;
loadedDeck.lastLoadInfo = {
.fileName = fileName, .fileName = fileName,
.fileFormat = fmt, .fileFormat = fmt,
}; };
@ -86,7 +84,7 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form
watcher->deleteLater(); watcher->deleteLater();
if (result) { if (result) {
lastLoadInfo = { loadedDeck.lastLoadInfo = {
.fileName = fileName, .fileName = fileName,
.fileFormat = fmt, .fileFormat = fmt,
}; };
@ -107,13 +105,13 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form
switch (fmt) { switch (fmt) {
case DeckFileFormat::PlainText: case DeckFileFormat::PlainText:
return deckList->loadFromFile_Plain(&file); return loadedDeck.deckList.loadFromFile_Plain(&file);
case DeckFileFormat::Cockatrice: { case DeckFileFormat::Cockatrice: {
bool result = false; bool result = false;
result = deckList->loadFromFile_Native(&file); result = loadedDeck.deckList.loadFromFile_Native(&file);
if (!result) { if (!result) {
file.seek(0); file.seek(0);
return deckList->loadFromFile_Plain(&file); return loadedDeck.deckList.loadFromFile_Plain(&file);
} }
return result; return result;
} }
@ -129,9 +127,9 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form
bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
{ {
bool result = deckList->loadFromString_Native(nativeString); bool result = loadedDeck.deckList.loadFromString_Native(nativeString);
if (result) { if (result) {
lastLoadInfo = { loadedDeck.lastLoadInfo = {
.remoteDeckId = remoteDeckId, .remoteDeckId = remoteDeckId,
}; };
@ -150,16 +148,16 @@ bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt)
bool result = false; bool result = false;
switch (fmt) { switch (fmt) {
case DeckFileFormat::PlainText: case DeckFileFormat::PlainText:
result = deckList->saveToFile_Plain(&file); result = loadedDeck.deckList.saveToFile_Plain(&file);
break; break;
case DeckFileFormat::Cockatrice: case DeckFileFormat::Cockatrice:
result = deckList->saveToFile_Native(&file); result = loadedDeck.deckList.saveToFile_Native(&file);
qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result; qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result;
break; break;
} }
if (result) { if (result) {
lastLoadInfo = { loadedDeck.lastLoadInfo = {
.fileName = fileName, .fileName = fileName,
.fileFormat = fmt, .fileFormat = fmt,
}; };
@ -194,18 +192,18 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileForm
// Perform file modifications // Perform file modifications
switch (fmt) { switch (fmt) {
case DeckFileFormat::PlainText: case DeckFileFormat::PlainText:
result = deckList->saveToFile_Plain(&file); result = loadedDeck.deckList.saveToFile_Plain(&file);
break; break;
case DeckFileFormat::Cockatrice: case DeckFileFormat::Cockatrice:
deckList->setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); loadedDeck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
result = deckList->saveToFile_Native(&file); result = loadedDeck.deckList.saveToFile_Native(&file);
break; break;
} }
file.close(); // Close the file to ensure changes are flushed file.close(); // Close the file to ensure changes are flushed
if (result) { if (result) {
lastLoadInfo = { loadedDeck.lastLoadInfo = {
.fileName = fileName, .fileName = fileName,
.fileFormat = fmt, .fileFormat = fmt,
}; };
@ -289,14 +287,14 @@ static QString toDecklistExportString(const QList<const DecklistCardNode *> &car
* @param deckList The decklist to export * @param deckList The decklist to export
* @param website The website we're sending the deck to * @param website The website we're sending the deck to
*/ */
QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website) QString DeckLoader::exportDeckToDecklist(const DeckList &deckList, DecklistWebsite website)
{ {
// Add the base url // Add the base url
QString deckString = "https://" + getDomainForWebsite(website) + "/?"; QString deckString = "https://" + getDomainForWebsite(website) + "/?";
// export all cards in zone // export all cards in zone
QString mainBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_MAIN})); QString mainBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_MAIN}));
QString sideBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_SIDE})); QString sideBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_SIDE}));
// Remove the extra return at the end of the last cards // Remove the extra return at the end of the last cards
mainBoardCards.chop(3); mainBoardCards.chop(3);
@ -312,7 +310,7 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi
return deckString; return deckString;
} }
void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber) void DeckLoader::saveToClipboard(const DeckList &deckList, bool addComments, bool addSetNameAndNumber)
{ {
QString buffer; QString buffer;
QTextStream stream(&buffer); QTextStream stream(&buffer);
@ -322,7 +320,7 @@ void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, boo
} }
bool DeckLoader::saveToStream_Plain(QTextStream &out, bool DeckLoader::saveToStream_Plain(QTextStream &out,
const DeckList *deckList, const DeckList &deckList,
bool addComments, bool addComments,
bool addSetNameAndNumber) bool addSetNameAndNumber)
{ {
@ -331,7 +329,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out,
} }
// loop zones // loop zones
for (auto zoneNode : deckList->getZoneNodes()) { for (auto zoneNode : deckList.getZoneNodes()) {
saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber); saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber);
// end of zone // end of zone
@ -341,14 +339,14 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out,
return true; return true;
} }
void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList) void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList)
{ {
if (!deckList->getName().isEmpty()) { if (!deckList.getName().isEmpty()) {
out << "// " << deckList->getName() << "\n\n"; out << "// " << deckList.getName() << "\n\n";
} }
if (!deckList->getComments().isEmpty()) { if (!deckList.getComments().isEmpty()) {
QStringList commentRows = deckList->getComments().split(QRegularExpression("\n|\r\n|\r")); QStringList commentRows = deckList.getComments().split(QRegularExpression("\n|\r\n|\r"));
for (const QString &row : commentRows) { for (const QString &row : commentRows) {
out << "// " << row << "\n"; out << "// " << row << "\n";
} }
@ -436,7 +434,7 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
} }
} }
bool DeckLoader::convertToCockatriceFormat(QString fileName) bool DeckLoader::convertToCockatriceFormat(const QString &fileName)
{ {
// Change the file extension to .cod // Change the file extension to .cod
QFileInfo fileInfo(fileName); QFileInfo fileInfo(fileName);
@ -455,7 +453,7 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName)
switch (DeckFileFormat::getFormatFromName(fileName)) { switch (DeckFileFormat::getFormatFromName(fileName)) {
case DeckFileFormat::PlainText: case DeckFileFormat::PlainText:
// Save in Cockatrice's native format // Save in Cockatrice's native format
result = deckList->saveToFile_Native(&file); result = loadedDeck.deckList.saveToFile_Native(&file);
break; break;
case DeckFileFormat::Cockatrice: case DeckFileFormat::Cockatrice:
qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed.";
@ -476,7 +474,7 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName)
} else { } else {
qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName;
} }
lastLoadInfo = { loadedDeck.lastLoadInfo = {
.fileName = newFileName, .fileName = newFileName,
.fileFormat = DeckFileFormat::Cockatrice, .fileFormat = DeckFileFormat::Cockatrice,
}; };
@ -485,29 +483,6 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName)
return result; return result;
} }
QString DeckLoader::getCardZoneFromName(const QString &cardName, QString currentZoneName)
{
CardInfoPtr card = CardDatabaseManager::query()->getCardInfo(cardName);
if (card && card->getIsToken()) {
return DECK_ZONE_TOKENS;
}
return currentZoneName;
}
QString DeckLoader::getCompleteCardName(const QString &cardName)
{
if (CardDatabaseManager::getInstance()) {
ExactCard temp = CardDatabaseManager::query()->guessCard({cardName});
if (temp) {
return temp.getName();
}
}
return cardName;
}
void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node) void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node)
{ {
const int totalColumns = 2; const int totalColumns = 2;
@ -568,7 +543,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
cursor->movePosition(QTextCursor::End); cursor->movePosition(QTextCursor::End);
} }
void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList) void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList)
{ {
QTextDocument doc; QTextDocument doc;
@ -584,14 +559,14 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
headerCharFormat.setFontWeight(QFont::Bold); headerCharFormat.setFontWeight(QFont::Bold);
cursor.insertBlock(headerBlockFormat, headerCharFormat); cursor.insertBlock(headerBlockFormat, headerCharFormat);
cursor.insertText(deckList->getName()); cursor.insertText(deckList.getName());
headerCharFormat.setFontPointSize(12); headerCharFormat.setFontPointSize(12);
cursor.insertBlock(headerBlockFormat, headerCharFormat); cursor.insertBlock(headerBlockFormat, headerCharFormat);
cursor.insertText(deckList->getComments()); cursor.insertText(deckList.getComments());
cursor.insertBlock(headerBlockFormat, headerCharFormat); cursor.insertBlock(headerBlockFormat, headerCharFormat);
for (auto zoneNode : deckList->getZoneNodes()) { for (auto zoneNode : deckList.getZoneNodes()) {
cursor.insertHtml("<br><img src=theme:hr.jpg>"); cursor.insertHtml("<br><img src=theme:hr.jpg>");
cursor.insertBlock(headerBlockFormat, headerCharFormat); cursor.insertBlock(headerBlockFormat, headerCharFormat);

View file

@ -41,28 +41,16 @@ public:
}; };
private: private:
DeckList *deckList; LoadedDeck loadedDeck;
LoadedDeck::LoadInfo lastLoadInfo;
public: public:
DeckLoader(QObject *parent); DeckLoader(QObject *parent);
DeckLoader(QObject *parent, DeckList *_deckList);
DeckLoader(const DeckLoader &) = delete; DeckLoader(const DeckLoader &) = delete;
DeckLoader &operator=(const DeckLoader &) = delete; DeckLoader &operator=(const DeckLoader &) = delete;
const LoadedDeck::LoadInfo &getLastLoadInfo() const
{
return lastLoadInfo;
}
void setLastLoadInfo(const LoadedDeck::LoadInfo &info)
{
lastLoadInfo = info;
}
[[nodiscard]] bool hasNotBeenLoaded() const [[nodiscard]] bool hasNotBeenLoaded() const
{ {
return lastLoadInfo.isEmpty(); return loadedDeck.lastLoadInfo.isEmpty();
} }
bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false); bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false);
@ -71,11 +59,11 @@ public:
bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt); bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt);
bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt); bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt);
static QString exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website); static QString exportDeckToDecklist(const DeckList &deckList, DecklistWebsite website);
static void saveToClipboard(const DeckList *deckList, bool addComments = true, bool addSetNameAndNumber = true); static void saveToClipboard(const DeckList &deckList, bool addComments = true, bool addSetNameAndNumber = true);
static bool saveToStream_Plain(QTextStream &out, static bool saveToStream_Plain(QTextStream &out,
const DeckList *deckList, const DeckList &deckList,
bool addComments = true, bool addComments = true,
bool addSetNameAndNumber = true); bool addSetNameAndNumber = true);
@ -84,18 +72,26 @@ public:
* @param printer The printer to render the decklist to. * @param printer The printer to render the decklist to.
* @param deckList * @param deckList
*/ */
static void printDeckList(QPrinter *printer, const DeckList *deckList); static void printDeckList(QPrinter *printer, const DeckList &deckList);
bool convertToCockatriceFormat(QString fileName); bool convertToCockatriceFormat(const QString &fileName);
DeckList *getDeckList() LoadedDeck &getDeck()
{ {
return deckList; return loadedDeck;
}
const LoadedDeck &getDeck() const
{
return loadedDeck;
}
void setDeck(const LoadedDeck &deck)
{
loadedDeck = deck;
} }
private: private:
static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node); static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node);
static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList); static void saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList);
static void saveToStream_DeckZone(QTextStream &out, static void saveToStream_DeckZone(QTextStream &out,
const InnerDecklistNode *zoneNode, const InnerDecklistNode *zoneNode,
@ -106,9 +102,6 @@ private:
QList<DecklistCardNode *> cards, QList<DecklistCardNode *> cards,
bool addComments = true, bool addComments = true,
bool addSetNameAndNumber = true); bool addSetNameAndNumber = true);
[[nodiscard]] static QString getCardZoneFromName(const QString &cardName, QString currentZoneName);
[[nodiscard]] static QString getCompleteCardName(const QString &cardName);
}; };
#endif #endif

View file

@ -31,7 +31,7 @@ struct LoadedDeck
}; };
DeckList deckList; ///< The decklist itself DeckList deckList; ///< The decklist itself
LoadInfo lastLoadInfo; ///< info about where the deck was loaded from LoadInfo lastLoadInfo = {}; ///< info about where the deck was loaded from
bool isEmpty() const; bool isEmpty() const;
}; };

View file

@ -57,17 +57,10 @@ void Logger::openLogfileSession()
return; return;
} }
fileStream.setDevice(&fileHandle); fileStream.setDevice(&fileHandle);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << Qt::endl; fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << Qt::endl;
fileStream << getClientVersion() << Qt::endl; fileStream << getClientVersion() << Qt::endl;
fileStream << getSystemArchitecture() << Qt::endl; fileStream << getSystemArchitecture() << Qt::endl;
fileStream << getClientInstallInfo() << Qt::endl; fileStream << getClientInstallInfo() << Qt::endl;
#else
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << endl;
fileStream << getClientVersion() << endl;
fileStream << getSystemArchitecture() << endl;
fileStream << getClientInstallInfo() << endl;
#endif
logToFileEnabled = true; logToFileEnabled = true;
} }
@ -77,11 +70,7 @@ void Logger::closeLogfileSession()
return; return;
logToFileEnabled = false; logToFileEnabled = false;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl; fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl;
#else
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << endl;
#endif
fileHandle.close(); fileHandle.close();
} }
@ -103,11 +92,7 @@ void Logger::internalLog(const QString &message)
std::cerr << message.toStdString() << std::endl; // Print to stdout std::cerr << message.toStdString() << std::endl; // Print to stdout
if (logToFileEnabled) { if (logToFileEnabled) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
fileStream << message << Qt::endl; // Print to fileStream fileStream << message << Qt::endl; // Print to fileStream
#else
fileStream << message << endl; // Print to fileStream
#endif
} }
} }

View file

@ -91,8 +91,9 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex i
if (indexToWidgetMap.contains(index)) { if (indexToWidgetMap.contains(index)) {
return indexToWidgetMap[index]; return indexToWidgetMap[index];
} }
auto cardName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString(); auto cardName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
auto cardProviderId = deckListModel->data(index.sibling(index.row(), 4), Qt::EditRole).toString(); auto cardProviderId =
index.sibling(index.row(), DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::EditRole).toString();
auto widget = new CardInfoPictureWithTextOverlayWidget(getLayoutParent(), true); auto widget = new CardInfoPictureWithTextOverlayWidget(getLayoutParent(), true);
widget->setScaleFactor(cardSizeWidget->getSlider()->value()); widget->setScaleFactor(cardSizeWidget->getSlider()->value());
@ -114,7 +115,7 @@ void CardGroupDisplayWidget::updateCardDisplays()
// This doesn't really matter since overwrite the whole lessThan function to just compare dynamically anyway. // This doesn't really matter since overwrite the whole lessThan function to just compare dynamically anyway.
proxy.setSortRole(Qt::EditRole); proxy.setSortRole(Qt::EditRole);
proxy.sort(1, Qt::AscendingOrder); proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
// 1. trackedIndex is a source index → map it to proxy space // 1. trackedIndex is a source index → map it to proxy space
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex); QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);

View file

@ -27,7 +27,7 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *pa
layout->addWidget(text, 0, Qt::AlignCenter); layout->addWidget(text, 0, Qt::AlignCenter);
setLayout(layout); setLayout(layout);
setFrameStyle(QFrame::Panel | QFrame::Raised); setFrameStyle(static_cast<int>(QFrame::Panel) | QFrame::Raised);
int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3; int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3;
int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio); int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio);

View file

@ -39,10 +39,6 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT
setMouseTracking(true); setMouseTracking(true);
} }
enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(this->window());
enlargedPixmapWidget->hide();
connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater);
hoverTimer = new QTimer(this); hoverTimer = new QTimer(this);
hoverTimer->setSingleShot(true); hoverTimer->setSingleShot(true);
connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap); connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap);
@ -277,7 +273,7 @@ void CardInfoPictureWidget::leaveEvent(QEvent *event)
if (hoverToZoomEnabled) { if (hoverToZoomEnabled) {
hoverTimer->stop(); hoverTimer->stop();
enlargedPixmapWidget->hide(); destroyEnlargedPixmapWidget();
} }
if (raiseOnEnter) { if (raiseOnEnter) {
@ -294,7 +290,7 @@ void CardInfoPictureWidget::moveEvent(QMoveEvent *event)
QWidget::moveEvent(event); QWidget::moveEvent(event);
hoverTimer->stop(); hoverTimer->stop();
enlargedPixmapWidget->hide(); destroyEnlargedPixmapWidget();
if (animation->state() == QAbstractAnimation::Running) { if (animation->state() == QAbstractAnimation::Running) {
return; return;
@ -310,7 +306,7 @@ void CardInfoPictureWidget::mouseMoveEvent(QMouseEvent *event)
{ {
QWidget::mouseMoveEvent(event); QWidget::mouseMoveEvent(event);
if (hoverToZoomEnabled && enlargedPixmapWidget->isVisible()) { if (hoverToZoomEnabled && enlargedPixmapWidget && enlargedPixmapWidget->isVisible()) {
const QPoint cursorPos = QCursor::pos(); const QPoint cursorPos = QCursor::pos();
const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry(); const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry();
const QSize widgetSize = enlargedPixmapWidget->size(); const QSize widgetSize = enlargedPixmapWidget->size();
@ -344,7 +340,7 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event)
void CardInfoPictureWidget::hideEvent(QHideEvent *event) void CardInfoPictureWidget::hideEvent(QHideEvent *event)
{ {
enlargedPixmapWidget->hide(); destroyEnlargedPixmapWidget();
QWidget::hideEvent(event); QWidget::hideEvent(event);
} }
@ -444,12 +440,19 @@ QMenu *CardInfoPictureWidget::createAddToOpenDeckMenu()
* If card information is available, the enlarged pixmap is loaded, positioned near the cursor, * If card information is available, the enlarged pixmap is loaded, positioned near the cursor,
* and displayed. * and displayed.
*/ */
void CardInfoPictureWidget::showEnlargedPixmap() const void CardInfoPictureWidget::showEnlargedPixmap()
{ {
if (!exactCard) { if (!exactCard) {
return; return;
} }
// Lazy creation of the enlarged widget
if (!enlargedPixmapWidget) {
enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(const_cast<CardInfoPictureWidget *>(this)->window());
enlargedPixmapWidget->hide();
connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater);
}
const QSize enlargedSize(static_cast<int>(size().width() * 2), static_cast<int>(size().width() * aspectRatio * 2)); const QSize enlargedSize(static_cast<int>(size().width() * 2), static_cast<int>(size().width() * aspectRatio * 2));
enlargedPixmapWidget->setCardPixmap(exactCard, enlargedSize); enlargedPixmapWidget->setCardPixmap(exactCard, enlargedSize);
@ -460,7 +463,6 @@ void CardInfoPictureWidget::showEnlargedPixmap() const
int newX = cursorPos.x() + enlargedPixmapOffset; int newX = cursorPos.x() + enlargedPixmapOffset;
int newY = cursorPos.y() + enlargedPixmapOffset; int newY = cursorPos.y() + enlargedPixmapOffset;
// Adjust if out of bounds
if (newX + widgetSize.width() > screenGeometry.right()) { if (newX + widgetSize.width() > screenGeometry.right()) {
newX = cursorPos.x() - widgetSize.width() - enlargedPixmapOffset; newX = cursorPos.x() - widgetSize.width() - enlargedPixmapOffset;
} }
@ -472,3 +474,11 @@ void CardInfoPictureWidget::showEnlargedPixmap() const
enlargedPixmapWidget->show(); enlargedPixmapWidget->show();
} }
void CardInfoPictureWidget::destroyEnlargedPixmapWidget()
{
if (enlargedPixmapWidget) {
enlargedPixmapWidget->deleteLater();
enlargedPixmapWidget = nullptr;
}
}

View file

@ -63,7 +63,8 @@ protected:
{ {
return resizedPixmap; return resizedPixmap;
} }
void showEnlargedPixmap() const; void showEnlargedPixmap();
void destroyEnlargedPixmapWidget();
private: private:
ExactCard exactCard; ExactCard exactCard;

View file

@ -82,10 +82,11 @@ void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *
void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index) void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index)
{ {
auto categoryName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString();
if (indexToWidgetMap.contains(index)) { if (indexToWidgetMap.contains(index)) {
return; return;
} }
auto categoryName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
if (displayType == DisplayType::Overlap) { if (displayType == DisplayType::Overlap) {
auto *displayWidget = new OverlappedCardGroupDisplayWidget( auto *displayWidget = new OverlappedCardGroupDisplayWidget(
cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria, cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria,
@ -120,7 +121,7 @@ void DeckCardZoneDisplayWidget::displayCards()
QSortFilterProxyModel proxy; QSortFilterProxyModel proxy;
proxy.setSourceModel(deckListModel); proxy.setSourceModel(deckListModel);
proxy.setSortRole(Qt::EditRole); proxy.setSortRole(Qt::EditRole);
proxy.sort(1, Qt::AscendingOrder); proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
// 1. trackedIndex is a source index → map it to proxy space // 1. trackedIndex is a source index → map it to proxy space
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex); QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);

View file

@ -17,7 +17,6 @@
* @param outlineColor The color of the outline around the text. * @param outlineColor The color of the outline around the text.
* @param fontSize The font size of the overlay text. * @param fontSize The font size of the overlay text.
* @param alignment The alignment of the text within the overlay. * @param alignment The alignment of the text within the overlay.
* @param _deckLoader The Deck Loader holding the Deck associated with this preview.
* *
* Sets the widget's size policy and default border style. * Sets the widget's size policy and default border style.
*/ */

View file

@ -21,34 +21,28 @@ void DeckListStatisticsAnalyzer::update()
manaCurveMap.clear(); manaCurveMap.clear();
manaDevotionMap.clear(); manaDevotionMap.clear();
auto nodes = model->getDeckList()->getCardNodes(); QList<ExactCard> cards = model->getCards();
for (auto *node : nodes) { for (const ExactCard &card : cards) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
if (!info)
continue;
for (int i = 0; i < node->getNumber(); ++i) {
// ---- Mana curve ---- // ---- Mana curve ----
if (config.computeManaCurve) { if (config.computeManaCurve) {
manaCurveMap[info->getCmc().toInt()]++; manaCurveMap[card.getInfo().getCmc().toInt()]++;
} }
// ---- Mana base ---- // ---- Mana base ----
if (config.computeManaBase) { if (config.computeManaBase) {
auto mana = determineManaProduction(info->getText()); auto mana = determineManaProduction(card.getInfo().getText());
for (auto it = mana.begin(); it != mana.end(); ++it) for (auto it = mana.begin(); it != mana.end(); ++it)
manaBaseMap[it.key()] += it.value(); manaBaseMap[it.key()] += it.value();
} }
// ---- Devotion ---- // ---- Devotion ----
if (config.computeDevotion) { if (config.computeDevotion) {
auto devo = countManaSymbols(info->getManaCost()); auto devo = countManaSymbols(card.getInfo().getManaCost());
for (auto &d : devo) for (auto &d : devo)
manaDevotionMap[d.first] += d.second; manaDevotionMap[d.first] += d.second;
} }
} }
}
emit statsUpdated(); emit statsUpdated();
} }

View file

@ -134,13 +134,13 @@ void DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters()
void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex &current, const QModelIndex & /*previous*/) void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex &current, const QModelIndex & /*previous*/)
{ {
const QString cardName = current.sibling(current.row(), 0).data().toString();
if (!current.isValid()) { if (!current.isValid()) {
return; return;
} }
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) { const QString cardName = current.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString();
if (!current.model()->hasChildren(current.siblingAtColumn(CardDatabaseModel::NameColumn))) {
emit cardChanged(CardDatabaseManager::query()->getPreferredCard(cardName)); emit cardChanged(CardDatabaseManager::query()->getPreferredCard(cardName));
} }
} }
@ -172,7 +172,7 @@ ExactCard DeckEditorDatabaseDisplayWidget::currentCard() const
return {}; return {};
} }
const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString(); const QString cardName = currentIndex.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString();
return CardDatabaseManager::query()->getPreferredCard(cardName); return CardDatabaseManager::query()->getPreferredCard(cardName);
} }

View file

@ -1,8 +1,8 @@
#include "deck_editor_deck_dock_widget.h" #include "deck_editor_deck_dock_widget.h"
#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/cache_settings.h"
#include "../../deck_loader/deck_loader.h"
#include "deck_list_style_proxy.h" #include "deck_list_style_proxy.h"
#include "deck_state_manager.h"
#include <QComboBox> #include <QComboBox>
#include <QDockWidget> #include <QDockWidget>
@ -38,7 +38,7 @@ static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo)
} }
DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent) DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent)
: QDockWidget(parent), deckEditor(parent) : QDockWidget(parent), deckEditor(parent), deckStateManager(parent->deckStateManager)
{ {
setObjectName("deckDock"); setObjectName("deckDock");
@ -52,19 +52,19 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent
void DeckEditorDeckDockWidget::createDeckDock() void DeckEditorDeckDockWidget::createDeckDock()
{ {
deckModel = new DeckListModel(this); connect(getModel(), &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
deckModel->setObjectName("deckModel");
connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
deckLoader = new DeckLoader(this, deckModel->getDeckList());
proxy = new DeckListStyleProxy(this); proxy = new DeckListStyleProxy(this);
proxy->setSourceModel(deckModel); proxy->setSourceModel(getModel());
historyManagerWidget = new DeckListHistoryManagerWidget(deckModel, proxy, deckEditor->getHistoryManager(), this); historyManagerWidget = new DeckListHistoryManagerWidget(deckStateManager, proxy, this);
connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this, connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this,
&DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel);
connect(deckStateManager, &DeckStateManager::focusIndexChanged, this, &DeckEditorDeckDockWidget::setSelectedIndex);
connect(deckStateManager, &DeckStateManager::deckReplaced, this,
&DeckEditorDeckDockWidget::syncDisplayWidgetsToModel);
deckView = new QTreeView(); deckView = new QTreeView();
deckView->setObjectName("deckView"); deckView->setObjectName("deckView");
deckView->setModel(proxy); deckView->setModel(proxy);
@ -76,14 +76,14 @@ void DeckEditorDeckDockWidget::createDeckDock()
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
&DeckEditorDeckDockWidget::updateCard); &DeckEditorDeckDockWidget::updateCard);
connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapCard); connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapSelection);
deckView->setContextMenuPolicy(Qt::CustomContextMenu); deckView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(deckView, &QTreeView::customContextMenuRequested, this, &DeckEditorDeckDockWidget::decklistCustomMenu); connect(deckView, &QTreeView::customContextMenuRequested, this, &DeckEditorDeckDockWidget::decklistCustomMenu);
connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapCard); connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapSelection);
connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrement); connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrementSelection);
connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrement); connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrementSelection);
connect(&deckViewKeySignals, &KeySignals::onCtrlAltMinus, this, &DeckEditorDeckDockWidget::actDecrementSelection); connect(&deckViewKeySignals, &KeySignals::onCtrlAltMinus, this, &DeckEditorDeckDockWidget::actDecrementSelection);
connect(&deckViewKeySignals, &KeySignals::onShiftRight, this, &DeckEditorDeckDockWidget::actIncrement); connect(&deckViewKeySignals, &KeySignals::onShiftRight, this, &DeckEditorDeckDockWidget::actIncrementSelection);
connect(&deckViewKeySignals, &KeySignals::onShiftLeft, this, &DeckEditorDeckDockWidget::actDecrementSelection); connect(&deckViewKeySignals, &KeySignals::onShiftLeft, this, &DeckEditorDeckDockWidget::actDecrementSelection);
connect(&deckViewKeySignals, &KeySignals::onDelete, this, &DeckEditorDeckDockWidget::actRemoveCard); connect(&deckViewKeySignals, &KeySignals::onDelete, this, &DeckEditorDeckDockWidget::actRemoveCard);
@ -97,7 +97,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
nameDebounceTimer = new QTimer(this); nameDebounceTimer = new QTimer(this);
nameDebounceTimer->setSingleShot(true); nameDebounceTimer->setSingleShot(true);
nameDebounceTimer->setInterval(300); // debounce duration in ms nameDebounceTimer->setInterval(300); // debounce duration in ms
connect(nameDebounceTimer, &QTimer::timeout, this, [this]() { updateName(nameEdit->text()); }); connect(nameDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeName);
connect(nameEdit, &LineEditUnfocusable::textChanged, this, [this]() { connect(nameEdit, &LineEditUnfocusable::textChanged, this, [this]() {
nameDebounceTimer->start(); // restart debounce timer nameDebounceTimer->start(); // restart debounce timer
@ -141,7 +141,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
commentsDebounceTimer = new QTimer(this); commentsDebounceTimer = new QTimer(this);
commentsDebounceTimer->setSingleShot(true); commentsDebounceTimer->setSingleShot(true);
commentsDebounceTimer->setInterval(400); // longer debounce for multi-line commentsDebounceTimer->setInterval(400); // longer debounce for multi-line
connect(commentsDebounceTimer, &QTimer::timeout, this, [this]() { updateComments(); }); connect(commentsDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeComments);
connect(commentsEdit, &QTextEdit::textChanged, this, [this]() { connect(commentsEdit, &QTextEdit::textChanged, this, [this]() {
commentsDebounceTimer->start(); // restart debounce timer commentsDebounceTimer->start(); // restart debounce timer
@ -152,18 +152,21 @@ void DeckEditorDeckDockWidget::createDeckDock()
bannerCardLabel->setText(tr("Banner Card")); bannerCardLabel->setText(tr("Banner Card"));
bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
bannerCardComboBox = new QComboBox(this); bannerCardComboBox = new QComboBox(this);
connect(deckModel, &DeckListModel::dataChanged, this, [this]() { connect(getModel(), &DeckListModel::dataChanged, this, [this]() {
// Delay the update to avoid race conditions // Delay the update to avoid race conditions
QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox); QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox);
}); });
connect(getModel(), &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand);
connect(getModel(), &DeckListModel::modelReset, this, &DeckEditorDeckDockWidget::expandAll);
connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&DeckEditorDeckDockWidget::setBannerCard); &DeckEditorDeckDockWidget::writeBannerCard);
bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags()); deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, {});
deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible());
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, deckStateManager,
&DeckEditorDeckDockWidget::setTags); &DeckStateManager::setTags);
activeGroupCriteriaLabel = new QLabel(this); activeGroupCriteriaLabel = new QLabel(this);
@ -172,16 +175,14 @@ void DeckEditorDeckDockWidget::createDeckDock()
activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST); activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST);
activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR); activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR);
connect(activeGroupCriteriaComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { connect(activeGroupCriteriaComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() {
deckModel->setActiveGroupCriteria(static_cast<DeckListModelGroupCriteria::Type>( getModel()->setActiveGroupCriteria(static_cast<DeckListModelGroupCriteria::Type>(
activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt())); activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt()));
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
deckView->expandAll();
deckView->expandAll();
}); });
aIncrement = new QAction(QString(), this); aIncrement = new QAction(QString(), this);
aIncrement->setIcon(QPixmap("theme:icons/increment")); aIncrement->setIcon(QPixmap("theme:icons/increment"));
connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrement); connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrementSelection);
auto *tbIncrement = new QToolButton(this); auto *tbIncrement = new QToolButton(this);
tbIncrement->setDefaultAction(aIncrement); tbIncrement->setDefaultAction(aIncrement);
@ -199,7 +200,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
aSwapCard = new QAction(QString(), this); aSwapCard = new QAction(QString(), this);
aSwapCard->setIcon(QPixmap("theme:icons/swap")); aSwapCard->setIcon(QPixmap("theme:icons/swap"));
connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapCard); connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapSelection);
auto *tbSwapCard = new QToolButton(this); auto *tbSwapCard = new QToolButton(this);
tbSwapCard->setDefaultAction(aSwapCard); tbSwapCard->setDefaultAction(aSwapCard);
@ -283,21 +284,21 @@ void DeckEditorDeckDockWidget::createDeckDock()
void DeckEditorDeckDockWidget::initializeFormats() void DeckEditorDeckDockWidget::initializeFormats()
{ {
QMap<QString, int> allFormats = CardDatabaseManager::query()->getAllFormatsWithCount(); QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys();
formatComboBox->clear(); // Remove "Loading Database..." formatComboBox->clear(); // Remove "Loading Database..."
formatComboBox->setEnabled(true); formatComboBox->setEnabled(true);
// Populate with formats // Populate with formats
formatComboBox->addItem("", ""); formatComboBox->addItem("", "");
for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); ++it) { for (auto formatName : allFormats) {
QString displayText = QString("%1").arg(it.key()); formatComboBox->addItem(formatName, formatName); // store the raw key in itemData
formatComboBox->addItem(displayText, it.key()); // store the raw key in itemData
} }
if (!deckModel->getDeckList()->getGameFormat().isEmpty()) { QString format = deckStateManager->getMetadata().gameFormat;
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); if (!format.isEmpty()) {
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); getModel()->setActiveFormat(format);
formatComboBox->setCurrentIndex(formatComboBox->findData(format));
} else { } else {
// Ensure no selection is visible initially // Ensure no selection is visible initially
formatComboBox->setCurrentIndex(-1); formatComboBox->setCurrentIndex(-1);
@ -306,11 +307,10 @@ void DeckEditorDeckDockWidget::initializeFormats()
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) { connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
if (index >= 0) { if (index >= 0) {
QString formatKey = formatComboBox->itemData(index).toString(); QString formatKey = formatComboBox->itemData(index).toString();
deckModel->setActiveFormat(formatKey); deckStateManager->setFormat(formatKey);
} else { } else {
deckModel->setActiveFormat(QString()); // clear format if deselected deckStateManager->setFormat(""); // clear format if deselected
} }
emit deckModified();
}); });
} }
@ -319,17 +319,17 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard()
QModelIndex current = deckView->selectionModel()->currentIndex(); QModelIndex current = deckView->selectionModel()->currentIndex();
if (!current.isValid()) if (!current.isValid())
return {}; return {};
const QString cardName = current.sibling(current.row(), 1).data().toString(); const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
const QString cardProviderID = current.sibling(current.row(), 4).data().toString(); const QString cardProviderID = current.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
const QModelIndex gparent = current.parent().parent(); const QModelIndex gparent = current.parent().parent();
if (!gparent.isValid()) { if (!gparent.isValid()) {
return {}; return {};
} }
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString(); const QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) { if (!current.model()->hasChildren(current.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT))) {
if (ExactCard selectedCard = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) { if (ExactCard selectedCard = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) {
return selectedCard; return selectedCard;
} }
@ -340,43 +340,37 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard()
void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*&current*/, const QModelIndex & /*previous*/) void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*&current*/, const QModelIndex & /*previous*/)
{ {
if (ExactCard card = getCurrentCard()) { if (ExactCard card = getCurrentCard()) {
emit cardChanged(card); emit selectedCardChanged(card);
} }
} }
void DeckEditorDeckDockWidget::updateName(const QString &name) /**
* @brief Writes the contents of the name textBox to the DeckStateManager
*/
void DeckEditorDeckDockWidget::writeName()
{ {
emit requestDeckHistorySave( QString name = nameEdit->text();
QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName())); deckStateManager->setName(name);
deckModel->getDeckList()->setName(name);
deckEditor->setModified(name.isEmpty());
emit nameChanged();
emit deckModified();
} }
void DeckEditorDeckDockWidget::updateComments() /**
* @brief Writes the contents of the comments textBox to the DeckStateManager
*/
void DeckEditorDeckDockWidget::writeComments()
{ {
emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)") QString comments = commentsEdit->toPlainText();
.arg(deckLoader->getDeckList()->getComments().size()) deckStateManager->setComments(comments);
.arg(commentsEdit->toPlainText().size()));
deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
deckEditor->setModified(commentsEdit->toPlainText().isEmpty());
emit commentsChanged();
emit deckModified();
} }
void DeckEditorDeckDockWidget::updateHash() void DeckEditorDeckDockWidget::updateHash()
{ {
hashLabel->setText(deckModel->getDeckList()->getDeckHash()); hashLabel->setText(deckStateManager->getDeckHash());
emit hashChanged();
emit deckModified();
} }
void DeckEditorDeckDockWidget::updateBannerCardComboBox() void DeckEditorDeckDockWidget::updateBannerCardComboBox()
{ {
// Store current banner card identity // Store current banner card identity
CardRef wanted = deckModel->getDeckList()->getBannerCard(); CardRef wanted = deckStateManager->getMetadata().bannerCard;
// Block signals temporarily // Block signals temporarily
bool wasBlocked = bannerCardComboBox->blockSignals(true); bool wasBlocked = bannerCardComboBox->blockSignals(true);
@ -386,15 +380,15 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
// Collect unique (name, providerId) pairs // Collect unique (name, providerId) pairs
QSet<QPair<QString, QString>> bannerCardSet; QSet<QPair<QString, QString>> bannerCardSet;
QList<const DecklistCardNode *> cardsInDeck = deckModel->getDeckList()->getCardNodes(); QList<CardRef> cardsInDeck = getModel()->getCardRefs();
for (auto currentCard : cardsInDeck) { for (auto cardRef : cardsInDeck) {
if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) { if (!CardDatabaseManager::query()->getCard(cardRef)) {
continue; continue;
} }
// Insert one entry per distinct card, ignore copies // Insert one entry per distinct card, ignore copies
bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()}); bannerCardSet.insert({cardRef.name, cardRef.providerId});
} }
// Convert to sorted list // Convert to sorted list
@ -415,7 +409,6 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
// Handle results // Handle results
if (restoreIndex != -1) { if (restoreIndex != -1) {
bannerCardComboBox->setCurrentIndex(restoreIndex); bannerCardComboBox->setCurrentIndex(restoreIndex);
syncDeckListBannerCardWithComboBox();
} else { } else {
// Add a placeholder "-" and set it as the current selection // Add a placeholder "-" and set it as the current selection
bannerCardComboBox->insertItem(0, "-"); bannerCardComboBox->insertItem(0, "-");
@ -426,25 +419,14 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
bannerCardComboBox->blockSignals(wasBlocked); bannerCardComboBox->blockSignals(wasBlocked);
} }
void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) /**
* @brief Writes the selected bannerCard to the DeckStateManager
*/
void DeckEditorDeckDockWidget::writeBannerCard(int index)
{ {
emit requestDeckHistorySave(tr("Banner card changed")); auto [name, id] = bannerCardComboBox->itemData(index).value<QPair<QString, QString>>();
syncDeckListBannerCardWithComboBox(); CardRef bannerCard = {name, id};
deckEditor->setModified(true); deckStateManager->setBannerCard(bannerCard);
emit deckModified();
}
void DeckEditorDeckDockWidget::setTags(const QStringList &tags)
{
deckModel->getDeckList()->setTags(tags);
deckEditor->setModified(true);
emit deckModified();
}
void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox()
{
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
deckModel->getDeckList()->setBannerCard({name, id});
} }
void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible) void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible)
@ -460,7 +442,7 @@ void DeckEditorDeckDockWidget::updateShowTagsWidget(const bool visible)
void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
{ {
if (deckModel->getDeckList()->getBannerCard().name == "") { if (deckStateManager->getMetadata().bannerCard.name == "") {
if (bannerCardComboBox->findText("-") != -1) { if (bannerCardComboBox->findText("-") != -1) {
bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-")); bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-"));
} else { } else {
@ -468,37 +450,26 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
bannerCardComboBox->setCurrentIndex(0); bannerCardComboBox->setCurrentIndex(0);
} }
} else { } else {
bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().name); bannerCardComboBox->setCurrentText(deckStateManager->getMetadata().bannerCard.name);
} }
} }
/** void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex)
* Sets the currently active deck for this tab
* @param _deck The deck. Takes ownership of the object
*/
void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck)
{ {
deckLoader = _deck; deckView->clearSelection();
deckLoader->setParent(this); deckView->setCurrentIndex(newCardIndex);
deckModel->setDeckList(deckLoader->getDeckList()); recursiveExpand(newCardIndex);
connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); deckView->setFocus(Qt::FocusReason::MouseFocusReason);
emit requestDeckHistoryClear();
historyManagerWidget->setDeckListModel(deckModel);
syncDisplayWidgetsToModel();
emit deckChanged();
} }
void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
{ {
nameEdit->blockSignals(true); nameEdit->blockSignals(true);
nameEdit->setText(deckModel->getDeckList()->getName()); nameEdit->setText(deckStateManager->getMetadata().name);
nameEdit->blockSignals(false); nameEdit->blockSignals(false);
commentsEdit->blockSignals(true); commentsEdit->blockSignals(true);
commentsEdit->setText(deckModel->getDeckList()->getComments()); commentsEdit->setText(deckStateManager->getMetadata().comments);
commentsEdit->blockSignals(false); commentsEdit->blockSignals(false);
bannerCardComboBox->blockSignals(true); bannerCardComboBox->blockSignals(true);
@ -507,61 +478,100 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
bannerCardComboBox->blockSignals(false); bannerCardComboBox->blockSignals(false);
updateHash(); updateHash();
sortDeckModelToDeckView(); sortDeckModelToDeckView();
expandAll();
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags);
} }
void DeckEditorDeckDockWidget::sortDeckModelToDeckView() void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
{ {
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); getModel()->setActiveFormat(deckStateManager->getMetadata().gameFormat);
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat));
deckView->expandAll();
deckView->expandAll();
emit deckChanged();
}
DeckLoader *DeckEditorDeckDockWidget::getDeckLoader()
{
return deckLoader;
}
DeckList *DeckEditorDeckDockWidget::getDeckList()
{
return deckModel->getDeckList();
} }
/** /**
* Resets the tab to the state for a blank new tab. * @brief Convenience method to get the underlying model instance from the DeckStateManager
*/ */
void DeckEditorDeckDockWidget::cleanDeck() DeckListModel *DeckEditorDeckDockWidget::getModel() const
{ {
deckModel->cleanList(); return deckStateManager->getModel();
nameEdit->setText(QString());
emit nameChanged();
commentsEdit->setText(QString());
emit commentsChanged();
hashLabel->setText(QString());
emit hashChanged();
emit deckModified();
emit deckChanged();
updateBannerCardComboBox();
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
} }
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) void DeckEditorDeckDockWidget::selectPrevCard()
{ {
if (index.parent().isValid()) changeSelectedCard(-1);
recursiveExpand(index.parent()); }
void DeckEditorDeckDockWidget::selectNextCard()
{
changeSelectedCard(1);
}
/**
* @brief Selects a card based on the change direction.
*
* @param changeBy The direction to change, -1 for previous, 1 for next.
*/
void DeckEditorDeckDockWidget::changeSelectedCard(int changeBy)
{
if (changeBy == 0) {
return;
}
// Get the current index of the selected item
auto deckViewCurrentIndex = deckView->currentIndex();
// For some reason, if the deckModel is modified but the view is not manually reselected,
// currentIndex will return an index for the underlying deckModel instead of the proxy.
// That index will return an invalid index when indexBelow/indexAbove crosses a header node,
// causing the selection to fail to move down.
/// \todo Figure out why it's happening so we can do a proper fix instead of a hacky workaround
if (deckViewCurrentIndex.model() == proxy->sourceModel()) {
deckViewCurrentIndex = proxy->mapFromSource(deckViewCurrentIndex);
}
auto nextIndex = deckViewCurrentIndex.siblingAtRow(deckViewCurrentIndex.row() + changeBy);
if (!nextIndex.isValid()) {
nextIndex = deckViewCurrentIndex;
// Increment to the next valid index, skipping header rows
AbstractDecklistNode *node;
do {
if (changeBy > 0) {
nextIndex = deckView->indexBelow(nextIndex);
} else {
nextIndex = deckView->indexAbove(nextIndex);
}
node = static_cast<AbstractDecklistNode *>(nextIndex.internalPointer());
} while (node && node->isDeckHeader());
}
if (nextIndex.isValid()) {
deckView->setCurrentIndex(nextIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
}
}
/**
* @brief Expands all parents of the given index.
* @param sourceIndex The index to expand (model source index)
*/
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &sourceIndex)
{
auto index = proxy->mapFromSource(sourceIndex);
while (index.parent().isValid()) {
index = index.parent();
deckView->expand(index); deckView->expand(index);
}
} }
/**
* @brief Fully expands all levels of the deck view
*/
void DeckEditorDeckDockWidget::expandAll() void DeckEditorDeckDockWidget::expandAll()
{ {
deckView->expandAll(); deckView->expandRecursively(deckView->rootIndex());
deckView->expandAll();
} }
/** /**
@ -575,7 +585,7 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const
auto selectedRows = deckView->selectionModel()->selectedRows(); auto selectedRows = deckView->selectionModel()->selectedRows();
const auto notLeafNode = [this](const QModelIndex &index) { const auto notLeafNode = [this](const QModelIndex &index) {
return deckModel->hasChildren(proxy->mapToSource(index)); return getModel()->hasChildren(proxy->mapToSource(index));
}; };
selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end()); selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end());
@ -583,16 +593,39 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const
return selectedRows; return selectedRows;
} }
void DeckEditorDeckDockWidget::actIncrement() 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);
}
void DeckEditorDeckDockWidget::actIncrementSelection()
{ {
auto selectedRows = getSelectedCardNodes(); auto selectedRows = getSelectedCardNodes();
for (const auto &index : selectedRows) { for (const auto &index : selectedRows) {
offsetCountAtIndex(index, 1); offsetCountAtIndex(index, true);
} }
} }
void DeckEditorDeckDockWidget::actSwapCard() void DeckEditorDeckDockWidget::actSwapCard(const ExactCard &card, const QString &zoneName)
{
QString providerId = card.getPrinting().getUuid();
QString collectorNumber = card.getPrinting().getProperty("num");
QModelIndex foundCard = getModel()->findCard(card.getName(), zoneName, providerId, collectorNumber);
if (!foundCard.isValid()) {
foundCard = getModel()->findCard(card.getName(), zoneName);
}
deckStateManager->swapCardAtIndex(foundCard);
}
void DeckEditorDeckDockWidget::actSwapSelection()
{ {
auto selectedRows = getSelectedCardNodes(); auto selectedRows = getSelectedCardNodes();
@ -602,52 +635,15 @@ void DeckEditorDeckDockWidget::actSwapCard()
deckView->setSelectionMode(QAbstractItemView::SingleSelection); deckView->setSelectionMode(QAbstractItemView::SingleSelection);
} }
bool isModified = false;
for (const auto &currentIndex : selectedRows) { for (const auto &currentIndex : selectedRows) {
if (swapCard(currentIndex)) { deckStateManager->swapCardAtIndex(currentIndex);
isModified = true;
}
} }
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
if (isModified) {
emit deckModified();
}
update(); update();
} }
/**
* Swaps the card at the index between the maindeck and sideboard
*
* @param currentIndex The index to swap.
* @return True if the swap was successful
*/
bool DeckEditorDeckDockWidget::swapCard(const QModelIndex &currentIndex)
{
if (!currentIndex.isValid())
return false;
const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString();
const QString cardProviderID = currentIndex.sibling(currentIndex.row(), 4).data().toString();
const QModelIndex gparent = currentIndex.parent().parent();
if (!gparent.isValid())
return false;
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
offsetCountAtIndex(currentIndex, -1);
const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
ExactCard card = CardDatabaseManager::query()->getCard({cardName, cardProviderID});
QModelIndex newCardIndex = card ? deckModel->addCard(card, otherZoneName)
// Third argument (true) says create the card no matter what, even if not in DB
: deckModel->addPreferredPrintingCard(cardName, otherZoneName, true);
recursiveExpand(proxy->mapToSource(newCardIndex));
return true;
}
void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName) void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName)
{ {
if (!card) if (!card)
@ -655,17 +651,7 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z
if (card.getInfo().getIsToken()) if (card.getInfo().getIsToken())
zoneName = DECK_ZONE_TOKENS; zoneName = DECK_ZONE_TOKENS;
QString providerId = card.getPrinting().getUuid(); deckStateManager->decrementCard(card, zoneName);
QString collectorNumber = card.getPrinting().getProperty("num");
QModelIndex idx = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
if (!idx.isValid()) {
return;
}
deckView->clearSelection();
deckView->setCurrentIndex(proxy->mapToSource(idx));
offsetCountAtIndex(idx, -1);
} }
void DeckEditorDeckDockWidget::actDecrementSelection() void DeckEditorDeckDockWidget::actDecrementSelection()
@ -679,7 +665,7 @@ void DeckEditorDeckDockWidget::actDecrementSelection()
} }
for (const auto &index : selectedRows) { for (const auto &index : selectedRows) {
offsetCountAtIndex(index, -1); offsetCountAtIndex(index, false);
} }
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
@ -695,58 +681,31 @@ void DeckEditorDeckDockWidget::actRemoveCard()
deckView->setSelectionMode(QAbstractItemView::SingleSelection); deckView->setSelectionMode(QAbstractItemView::SingleSelection);
} }
bool isModified = false; for (const auto &row : selectedRows) {
for (const auto &index : selectedRows) { deckStateManager->removeCardAtIndex(row);
if (!index.isValid() || deckModel->hasChildren(index)) {
continue;
}
QModelIndex sourceIndex = proxy->mapToSource(index);
QString cardName = sourceIndex.sibling(sourceIndex.row(), 1).data().toString();
emit requestDeckHistorySave(QString(tr("Removed \"%1\" (all copies)")).arg(cardName));
deckModel->removeRow(sourceIndex.row(), sourceIndex.parent());
isModified = true;
} }
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
if (isModified) {
emit deckModified();
}
} }
void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int offset) /**
* @brief Increments or decrements the amount of the card node at the index by 1.
* @param idx The proxy index
* @param isIncrement If true, increments the count. If false, decrements the count
*/
void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool isIncrement)
{ {
if (!idx.isValid() || deckModel->hasChildren(idx)) { if (!idx.isValid() || getModel()->hasChildren(idx)) {
return; return;
} }
QModelIndex sourceIndex = proxy->mapToSource(idx); QModelIndex sourceIndex = proxy->mapToSource(idx);
const QModelIndex numberIndex = sourceIndex.sibling(sourceIndex.row(), 0); if (isIncrement) {
const QModelIndex nameIndex = sourceIndex.sibling(sourceIndex.row(), 1); deckStateManager->incrementCountAtIndex(sourceIndex);
const QString cardName = deckModel->data(nameIndex, Qt::EditRole).toString();
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
const int new_count = count + offset;
const auto reason =
QString(tr("%1 %2 × \"%3\" (%4)"))
.arg(offset > 0 ? tr("Added") : tr("Removed"))
.arg(qAbs(offset))
.arg(cardName)
.arg(deckModel->data(sourceIndex.sibling(sourceIndex.row(), 4), Qt::DisplayRole).toString());
emit requestDeckHistorySave(reason);
if (new_count <= 0) {
deckModel->removeRow(sourceIndex.row(), sourceIndex.parent());
} else { } else {
deckModel->setData(numberIndex, new_count, Qt::EditRole); deckStateManager->decrementCountAtIndex(sourceIndex);
} }
emit deckModified();
} }
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point) void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)

View file

@ -28,22 +28,14 @@ class DeckEditorDeckDockWidget : public QDockWidget
Q_OBJECT Q_OBJECT
public: public:
explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent); explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent);
DeckLoader *deckLoader;
DeckListStyleProxy *proxy; DeckListStyleProxy *proxy;
DeckListModel *deckModel;
QTreeView *deckView; QTreeView *deckView;
QComboBox *bannerCardComboBox; QComboBox *bannerCardComboBox;
void createDeckDock(); void createDeckDock();
ExactCard getCurrentCard(); ExactCard getCurrentCard();
void retranslateUi(); void retranslateUi();
QString getDeckName()
{
return nameEdit->text();
}
QString getSimpleDeckName()
{
return nameEdit->text().simplified();
}
QComboBox *getGroupByComboBox() QComboBox *getGroupByComboBox()
{ {
return activeGroupCriteriaComboBox; return activeGroupCriteriaComboBox;
@ -55,35 +47,27 @@ public:
} }
public slots: public slots:
void cleanDeck(); void selectPrevCard();
void selectNextCard();
void updateBannerCardComboBox(); void updateBannerCardComboBox();
void setDeck(DeckLoader *_deck);
void syncDisplayWidgetsToModel(); void syncDisplayWidgetsToModel();
void sortDeckModelToDeckView(); void sortDeckModelToDeckView();
DeckLoader *getDeckLoader(); void actAddCard(const ExactCard &card, const QString &zoneName);
DeckList *getDeckList(); void actIncrementSelection();
void actIncrement();
bool swapCard(const QModelIndex &idx);
void actDecrementCard(const ExactCard &card, QString zoneName); void actDecrementCard(const ExactCard &card, QString zoneName);
void actDecrementSelection(); void actDecrementSelection();
void actSwapCard(); void actSwapCard(const ExactCard &card, const QString &zoneName);
void actSwapSelection();
void actRemoveCard(); void actRemoveCard();
void offsetCountAtIndex(const QModelIndex &idx, int offset);
void initializeFormats(); void initializeFormats();
void expandAll();
signals: signals:
void nameChanged(); void selectedCardChanged(const ExactCard &card);
void commentsChanged();
void hashChanged();
void deckChanged();
void deckModified();
void requestDeckHistorySave(const QString &modificationReason);
void requestDeckHistoryClear();
void cardChanged(const ExactCard &_card);
private: private:
AbstractTabDeckEditor *deckEditor; AbstractTabDeckEditor *deckEditor;
DeckStateManager *deckStateManager;
DeckListHistoryManagerWidget *historyManagerWidget; DeckListHistoryManagerWidget *historyManagerWidget;
KeySignals deckViewKeySignals; KeySignals deckViewKeySignals;
QLabel *nameLabel; QLabel *nameLabel;
@ -106,22 +90,25 @@ private:
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
void recursiveExpand(const QModelIndex &index); DeckListModel *getModel() const;
[[nodiscard]] QModelIndexList getSelectedCardNodes() const; [[nodiscard]] QModelIndexList getSelectedCardNodes() const;
void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement);
private slots: private slots:
void decklistCustomMenu(QPoint point); void decklistCustomMenu(QPoint point);
void updateCard(QModelIndex, const QModelIndex &current); void updateCard(QModelIndex, const QModelIndex &current);
void updateName(const QString &name); void writeName();
void updateComments(); void writeComments();
void setBannerCard(int); void writeBannerCard(int);
void setTags(const QStringList &tags); void setSelectedIndex(const QModelIndex &newCardIndex);
void syncDeckListBannerCardWithComboBox();
void updateHash(); void updateHash();
void refreshShortcuts(); void refreshShortcuts();
void updateShowBannerCardComboBox(bool visible); void updateShowBannerCardComboBox(bool visible);
void updateShowTagsWidget(bool visible); void updateShowTagsWidget(bool visible);
void syncBannerCardComboBoxSelectionWithDeck(); void syncBannerCardComboBoxSelectionWithDeck();
void changeSelectedCard(int changeBy);
void recursiveExpand(const QModelIndex &parent);
void expandAll();
}; };
#endif // DECK_EDITOR_DECK_DOCK_WIDGET_H #endif // DECK_EDITOR_DECK_DOCK_WIDGET_H

View file

@ -33,6 +33,10 @@ void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock()
installEventFilter(deckEditor); installEventFilter(deckEditor);
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged); connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
connect(printingSelector, &PrintingSelector::prevCardRequested, deckEditor->getDeckDockWidget(),
&DeckEditorDeckDockWidget::selectPrevCard);
connect(printingSelector, &PrintingSelector::nextCardRequested, deckEditor->getDeckDockWidget(),
&DeckEditorDeckDockWidget::selectNextCard);
} }
void DeckEditorPrintingSelectorDockWidget::retranslateUi() void DeckEditorPrintingSelectorDockWidget::retranslateUi()

View file

@ -1,10 +1,11 @@
#include "deck_list_history_manager_widget.h" #include "deck_list_history_manager_widget.h"
DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckListModel, #include "deck_state_manager.h"
DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_deckStateManager,
DeckListStyleProxy *_styleProxy, DeckListStyleProxy *_styleProxy,
DeckListHistoryManager *manager,
QWidget *parent) QWidget *parent)
: QWidget(parent), deckListModel(_deckListModel), styleProxy(_styleProxy), historyManager(manager) : QWidget(parent), deckStateManager(_deckStateManager), styleProxy(_styleProxy)
{ {
layout = new QHBoxLayout(this); layout = new QHBoxLayout(this);
@ -43,8 +44,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckL
connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked); connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked);
connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, connect(deckStateManager, &DeckStateManager::historyChanged, this, &DeckListHistoryManagerWidget::refreshList);
&DeckListHistoryManagerWidget::refreshList);
refreshList(); refreshList();
retranslateUi(); retranslateUi();
@ -58,15 +58,12 @@ void DeckListHistoryManagerWidget::retranslateUi()
historyLabel->setText(tr("Click on an entry to revert to that point in the history.")); historyLabel->setText(tr("Click on an entry to revert to that point in the history."));
} }
void DeckListHistoryManagerWidget::setDeckListModel(DeckListModel *_deckListModel)
{
deckListModel = _deckListModel;
}
void DeckListHistoryManagerWidget::refreshList() void DeckListHistoryManagerWidget::refreshList()
{ {
historyList->clear(); historyList->clear();
DeckListHistoryManager *historyManager = deckStateManager->getHistoryManager();
// Fill redo section first (oldest redo at top, newest redo closest to divider) // Fill redo section first (oldest redo at top, newest redo closest to divider)
const auto redoStack = historyManager->getRedoStack(); const auto redoStack = historyManager->getRedoStack();
for (int i = 0; i < redoStack.size(); ++i) { // iterate forward for (int i = 0; i < redoStack.size(); ++i) { // iterate forward
@ -98,36 +95,7 @@ void DeckListHistoryManagerWidget::refreshList()
redoButton->setEnabled(historyManager->canRedo()); redoButton->setEnabled(historyManager->canRedo());
} }
void DeckListHistoryManagerWidget::doUndo() void DeckListHistoryManagerWidget::onListClicked(const QListWidgetItem *item)
{
if (!historyManager->canUndo()) {
return;
}
historyManager->undo(deckListModel->getDeckList());
deckListModel->rebuildTree();
emit deckListModel->layoutChanged();
emit requestDisplayWidgetSync();
refreshList();
}
void DeckListHistoryManagerWidget::doRedo()
{
if (!historyManager->canRedo()) {
return;
}
historyManager->redo(deckListModel->getDeckList());
deckListModel->rebuildTree();
emit deckListModel->layoutChanged();
emit requestDisplayWidgetSync();
refreshList();
}
void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item)
{ {
// Ignore non-selectable items (like divider) // Ignore non-selectable items (like divider)
if (!(item->flags() & Qt::ItemIsSelectable)) { if (!(item->flags() & Qt::ItemIsSelectable)) {
@ -138,23 +106,24 @@ void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item)
int index = item->data(Qt::UserRole + 1).toInt(); int index = item->data(Qt::UserRole + 1).toInt();
if (mode == "redo") { if (mode == "redo") {
const auto redoStack = historyManager->getRedoStack(); const auto redoStack = deckStateManager->getHistoryManager()->getRedoStack();
int steps = redoStack.size() - index; int steps = redoStack.size() - index;
for (int i = 0; i < steps; ++i) { deckStateManager->redo(steps);
historyManager->redo(deckListModel->getDeckList());
}
} else if (mode == "undo") { } else if (mode == "undo") {
const auto undoStack = historyManager->getUndoStack(); const auto undoStack = deckStateManager->getHistoryManager()->getUndoStack();
int steps = undoStack.size() - 1 - index; int steps = undoStack.size() - index;
for (int i = 0; i < steps + 1; ++i) { deckStateManager->undo(steps);
historyManager->undo(deckListModel->getDeckList());
} }
}
deckListModel->rebuildTree();
emit deckListModel->layoutChanged();
emit requestDisplayWidgetSync();
refreshList(); refreshList();
} }
void DeckListHistoryManagerWidget::doUndo()
{
deckStateManager->undo();
}
void DeckListHistoryManagerWidget::doRedo()
{
deckStateManager->redo();
}

View file

@ -14,6 +14,8 @@
#include <libcockatrice/deck_list/deck_list_history_manager.h> #include <libcockatrice/deck_list/deck_list_history_manager.h>
#include <libcockatrice/models/deck_list/deck_list_model.h> #include <libcockatrice/models/deck_list/deck_list_model.h>
class DeckStateManager;
class DeckListHistoryManagerWidget : public QWidget class DeckListHistoryManagerWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -25,22 +27,19 @@ public slots:
void retranslateUi(); void retranslateUi();
public: public:
explicit DeckListHistoryManagerWidget(DeckListModel *deckListModel, explicit DeckListHistoryManagerWidget(DeckStateManager *deckStateManager,
DeckListStyleProxy *styleProxy, DeckListStyleProxy *styleProxy,
DeckListHistoryManager *manager,
QWidget *parent = nullptr); QWidget *parent = nullptr);
void setDeckListModel(DeckListModel *_deckListModel);
private slots: private slots:
void refreshList(); void refreshList();
void onListClicked(QListWidgetItem *item); void onListClicked(const QListWidgetItem *item);
void doUndo(); void doUndo();
void doRedo(); void doRedo();
private: private:
DeckListModel *deckListModel; DeckStateManager *deckStateManager;
DeckListStyleProxy *styleProxy; DeckListStyleProxy *styleProxy;
DeckListHistoryManager *historyManager;
QHBoxLayout *layout; QHBoxLayout *layout;
QAction *aUndo; QAction *aUndo;

View file

@ -0,0 +1,361 @@
#include "deck_state_manager.h"
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_list_history_manager.h>
DeckStateManager::DeckStateManager(QObject *parent)
: QObject(parent), deckList(QSharedPointer<DeckList>(new DeckList)),
deckListModel(new DeckListModel(this, deckList)), historyManager(new DeckListHistoryManager(this))
{
connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, [this] {
setModified(true);
emit historyChanged();
});
connect(deckListModel, &DeckListModel::rowsInserted, this, &DeckStateManager::uniqueCardsChanged);
connect(deckListModel, &DeckListModel::rowsRemoved, this, &DeckStateManager::uniqueCardsChanged);
}
const DeckList &DeckStateManager::getDeckList() const
{
return *deckList.get();
}
LoadedDeck DeckStateManager::toLoadedDeck() const
{
return {getDeckList(), lastLoadInfo};
}
DeckList::Metadata const &DeckStateManager::getMetadata() const
{
return deckList->getMetadata();
}
QString DeckStateManager::getSimpleDeckName() const
{
return deckList->getMetadata().name.simplified();
}
QString DeckStateManager::getDeckHash() const
{
return deckList->getDeckHash();
}
bool DeckStateManager::isModified() const
{
return modified;
}
void DeckStateManager::setModified(bool state)
{
if (state == modified) {
return;
}
modified = state;
emit isModifiedChanged(modified);
}
bool DeckStateManager::isBlankNewDeck() const
{
return !isModified() && deckList->isBlankDeck();
}
void DeckStateManager::replaceDeck(const LoadedDeck &deck)
{
lastLoadInfo = deck.lastLoadInfo;
deckList = QSharedPointer<DeckList>(new DeckList(deck.deckList));
deckListModel->setDeckList(deckList);
historyManager->clear();
setModified(false);
emit deckReplaced();
}
void DeckStateManager::clearDeck()
{
replaceDeck(LoadedDeck());
}
bool DeckStateManager::modifyDeck(const QString &reason, const std::function<bool(DeckListModel *)> &operation)
{
DeckListMemento memento = deckList->createMemento(reason);
bool success = operation(deckListModel);
if (success) {
historyManager->save(memento);
doCardModified();
}
return success;
}
QModelIndex DeckStateManager::modifyDeck(const QString &reason,
const std::function<QModelIndex(DeckListModel *)> &operation)
{
DeckListMemento memento = deckList->createMemento(reason);
QModelIndex idx = operation(deckListModel);
if (idx.isValid()) {
historyManager->save(memento);
doCardModified();
}
return idx;
}
void DeckStateManager::setName(const QString &name)
{
QString previous = deckList->getName();
if (previous == name) {
return;
}
requestHistorySave(tr("Rename deck to \"%1\" from \"%2\"").arg(name).arg(previous));
deckList->setName(name);
doMetadataModified();
}
void DeckStateManager::setComments(const QString &comments)
{
QString previous = deckList->getComments();
if (previous == comments) {
return;
}
requestHistorySave(tr("Updated comments (was %1 chars, now %2 chars)").arg(previous.size()).arg(comments.size()));
deckList->setComments(comments);
doMetadataModified();
}
void DeckStateManager::setBannerCard(const CardRef &bannerCard)
{
CardRef previous = deckList->getBannerCard();
if (previous == bannerCard) {
return;
}
requestHistorySave(tr("Set banner card to %1 (%2)").arg(bannerCard.name).arg(bannerCard.providerId));
deckList->setBannerCard(bannerCard);
doMetadataModified();
}
void DeckStateManager::setTags(const QStringList &tags)
{
QStringList previous = deckList->getTags();
if (previous == tags) {
return;
}
requestHistorySave(tr("Tags changed"));
deckList->setTags(tags);
doMetadataModified();
}
void DeckStateManager::setFormat(const QString &format)
{
if (deckList->getMetadata().gameFormat == format) {
return;
}
requestHistorySave(tr("Set format to %1").arg(format));
deckListModel->setActiveFormat(format);
doMetadataModified();
}
QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zoneName)
{
if (!card) {
return {};
}
QString reason = tr("Added (%1): %2 (%3) %4")
.arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(),
card.getPrinting().getProperty("num"));
QModelIndex idx = modifyDeck(reason, [&card, &zoneName](auto model) { return model->addCard(card, zoneName); });
if (idx.isValid()) {
emit focusIndexChanged(idx);
}
return idx;
}
QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString &zoneName)
{
if (!card)
return {};
QString providerId = card.getPrinting().getUuid();
QString collectorNumber = card.getPrinting().getProperty("num");
QModelIndex idx = deckListModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
if (!idx.isValid()) {
return {};
}
bool success = offsetCountAtIndex(idx, false);
if (!success) {
return {};
}
if (idx.isValid()) {
emit focusIndexChanged(idx);
}
return idx;
}
static bool doSwapCard(DeckListModel *model,
const QModelIndex &idx,
const QString &cardName,
const QString &providerId,
const QString &otherZone)
{
bool success = model->offsetCountAtIndex(idx, -1);
if (!success) {
return false;
}
if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, providerId})) {
model->addCard(card, otherZone);
} else {
// Third argument (true) says create the card no matter what, even if not in DB
model->addPreferredPrintingCard(cardName, otherZone, true);
}
return true;
}
bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx)
{
if (!idx.isValid())
return false;
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
QModelIndex gparent = idx.parent().parent();
if (!gparent.isValid())
return false;
QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
QString reason = tr("Moved to %1 1 × \"%2\" (%3)") //
.arg(otherZoneName)
.arg(cardName)
.arg(providerId);
return modifyDeck(reason, [&idx, &cardName, &providerId, &otherZoneName](auto model) {
return doSwapCard(model, idx, cardName, providerId, otherZoneName);
});
}
bool DeckStateManager::removeCardAtIndex(const QModelIndex &idx)
{
if (!idx.isValid() || deckListModel->hasChildren(idx)) {
return false;
}
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
QString reason = tr("Removed \"%1\" (all copies)").arg(cardName);
return modifyDeck(reason, [&idx](auto model) { return model->removeRow(idx.row(), idx.parent()); });
}
bool DeckStateManager::incrementCountAtIndex(const QModelIndex &idx)
{
return offsetCountAtIndex(idx, 1);
}
bool DeckStateManager::decrementCountAtIndex(const QModelIndex &idx)
{
return offsetCountAtIndex(idx, -1);
}
bool DeckStateManager::offsetCountAtIndex(const QModelIndex &idx, int offset)
{
if (!idx.isValid()) {
return false;
}
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString();
QString reason = tr("%1 1 × \"%2\" (%3)") //
.arg(offset > 0 ? tr("Added") : tr("Removed"))
.arg(cardName)
.arg(providerId);
return modifyDeck(reason, [&idx, &offset](auto model) { return model->offsetCountAtIndex(idx, offset); });
}
void DeckStateManager::undo(int steps)
{
if (!historyManager->canUndo()) {
return;
}
for (int i = 0; i < steps; i++) {
if (!historyManager->canUndo()) {
continue;
}
historyManager->undo(deckList.get());
}
deckListModel->rebuildTree();
emit deckListModel->layoutChanged();
}
void DeckStateManager::redo(int steps)
{
if (!historyManager->canRedo()) {
return;
}
for (int i = 0; i < steps; i++) {
if (!historyManager->canRedo()) {
continue;
}
historyManager->redo(deckList.get());
}
deckListModel->rebuildTree();
emit deckListModel->layoutChanged();
}
void DeckStateManager::requestHistorySave(const QString &reason)
{
historyManager->save(deckList->createMemento(reason));
}
/**
* @brief Handles updating state and emitting signals whenever the cards are modified
*/
void DeckStateManager::doCardModified()
{
setModified(true);
emit cardModified();
emit deckModified();
}
/**
* @brief Handles updating state and emitting signals whenever the metadata is modified
*/
void DeckStateManager::doMetadataModified()
{
setModified(true);
emit metadataModified();
emit deckModified();
}

View file

@ -0,0 +1,297 @@
#ifndef COCKATRICE_DECK_STATE_MANAGER_H
#define COCKATRICE_DECK_STATE_MANAGER_H
#include "../../deck_loader/loaded_deck.h"
#include "deck_list_model.h"
#include <QSharedPointer>
#include <libcockatrice/deck_list/deck_list.h>
class DeckListHistoryManager;
/**
* @brief This class centralizes the management of the state of the deck in the deck editor tab.
* It is responsible for owning and managing the DeckListModel, underlying DeckList, load info, and edit history.
*
* Although this class provides getters for the underlying DeckListModel, you should generally refrain from directly
* modifying the returned model. Outside modifications to the deck state should be done through @link
* DeckStateManager::modifyDeck and the metadata setters.
* Those methods ensure that the history is recorded and correct signals are emitted.
*/
class DeckStateManager : public QObject
{
Q_OBJECT
LoadedDeck::LoadInfo lastLoadInfo;
QSharedPointer<DeckList> deckList;
DeckListModel *deckListModel;
DeckListHistoryManager *historyManager;
bool modified = false;
public:
explicit DeckStateManager(QObject *parent = nullptr);
/**
* Gets the underlying HistoryManager.
* @return The DeckListHistoryManager instance
*/
DeckListHistoryManager *getHistoryManager() const
{
return historyManager;
}
/**
* @brief Gets the underlying DeckListModel.
* You should generally refrain modifying the returned model directly.
* However, it's fine (and intended) to perform queries on the returned model.
* @return The DeckListModel instance
*/
DeckListModel *getModel() const
{
return deckListModel;
}
/**
* @brief Gets a view of the current deck.
*/
const DeckList &getDeckList() const;
/**
* @brief Creates a LoadedDeck containing the contents of the current deck and the current LoadInfo.
*
* @return A new LoadedDeck instance.
*/
LoadedDeck toLoadedDeck() const;
/**
* @brief Gets a view of the metadata in the DeckList
*/
DeckList::Metadata const &getMetadata() const;
/**
* @brief Gets the deck's simplified name.
*/
QString getSimpleDeckName() const;
/**
* @brief Gets the deck hash.
*/
QString getDeckHash() const;
/**
* @brief Checks if the deck has been modified since it was last saved
*/
bool isModified() const;
/**
* @brief Sets the new isModified state, emitting a signal if the state changed.
* This class will automatically update its isModified state, but you may need to set it manually to handle, for
* example, saving.
* @param state The state
*/
void setModified(bool state);
/**
* @brief Checks if the deck state is as if it was a new deck
*/
bool isBlankNewDeck() const;
/**
* @brief Overwrites the current deck with a new deck, resetting all history
* @param deck The new deck.
*/
void replaceDeck(const LoadedDeck &deck);
/**
* @brief Resets the deck to a blank new deck, resetting all history.
*/
void clearDeck();
/**
* @brief Sets the lastLoadInfo.
* @param loadInfo The lastLoadInfo
*/
void setLastLoadInfo(const LoadedDeck::LoadInfo &loadInfo)
{
lastLoadInfo = loadInfo;
}
/**
* @brief Modifies the cards in the deck, in a wrapped operation that is saved to the history.
*
* The operation is a function that accepts a DeckListModel that it operates upon, and returns a bool.
*
* This method will pass the underlying DeckListModel into the operation function. The function can call methods on
* the model to modify the deck.
* The function should return a bool to indicate success/failure.
*
* If the operation returns true, the state of the deck before the operation is ran is saved to the history, and the
* isModified state is updated.
* If the operation returns false, the history and isModified state is not updated.
*
* Note that even if the operation fails, any modifications to the model will already have been made.
* It's recommended for the operation to always return true if any modification has already been made to the model,
* as not doing that may cause the state to become desynced.
*
* @param reason The reason to display in the history
* @param operation The modification operation.
* @return The bool returned from the operation
*/
bool modifyDeck(const QString &reason, const std::function<bool(DeckListModel *)> &operation);
/**
* @brief Modifies the cards in the deck, in a wrapped operation that is saved to the history.
*
* The operation is a function that accepts a DeckListModel that it operates upon, and returns a QModelIndex.
* If the index is invalid, then the operation is considered to be a failure.
*
* See the other @link DeckStateManager::modifyDeck for more info about the behavior of this method.
*
* @param reason The reason to display in the history
* @param operation The modification operation.
* @return The QModelIndex returned from the operation
*/
QModelIndex modifyDeck(const QString &reason, const std::function<QModelIndex(DeckListModel *)> &operation);
/// @name Metadata setters
/// @brief These methods set the metadata. Will no-op if the new value is the same as the current value.
/// Saves the operation to history if successful.
///@{
void setName(const QString &name);
void setComments(const QString &comments);
void setBannerCard(const CardRef &bannerCard);
void setTags(const QStringList &tags);
void setFormat(const QString &format);
///@}
/**
* @brief Adds the given card to the given zone.
* Saves the operation to history if successful.
*
* @param card The card to add
* @param zoneName The zone to add the card to
* @return The index of the added card
*/
QModelIndex addCard(const ExactCard &card, const QString &zoneName);
/**
* @brief Removes 1 copy of the given card from the given zone.
* Saves the operation to history if successful.
*
* @param card The card to remove
* @param zoneName The zone to remove the card from
* @return The index of the removed card. Will be invalid if the last copy was removed.
*/
QModelIndex decrementCard(const ExactCard &card, const QString &zoneName);
/**
* @brief Swaps one copy of the card at the given index between the maindeck and sideboard.
* No-ops if index is invalid or not a card node.
* Saves the operation to history if successful.
*
* @param idx The model index
* @return Whether the operation was successfully performed
*/
bool swapCardAtIndex(const QModelIndex &idx);
/**
* @brief Removes all copies of the card at the given index.
* No-ops if index is invalid or not a card node.
* Saves the operation to history if successful.
*
* @param idx The model index
* @return Whether the operation was successfully performed
*/
bool removeCardAtIndex(const QModelIndex &idx);
/**
* @brief Increments the number of copies of the card at the given index by 1.
* No-ops if index is invalid or not a card node.
* Saves the operation to history if successful.
*
* @param idx The model index
* @return Whether the operation was successfully performed
*/
bool incrementCountAtIndex(const QModelIndex &idx);
/**
* @brief Decrements the number of copies of the card at the given index by 1.
* No-ops if index is invalid or not a card node.
* Saves the operation to history if successful.
*
* @param idx The model index
* @return Whether the operation was successfully performed
*/
bool decrementCountAtIndex(const QModelIndex &idx);
/**
* Undoes n steps of the history, setting the decklist state and updating the current step in the historyManager.
* @param steps Number of steps to undo.
*/
void undo(int steps = 1);
/**
* Redoes n steps of the history, setting the decklist state and updating the current step in the historyManager.
* @param steps Number of steps to redo.
*/
void redo(int steps = 1);
public slots:
/**
* Saves the current decklist state to history.
* @param reason The reason that is shown in the history.
*/
void requestHistorySave(const QString &reason);
private:
bool offsetCountAtIndex(const QModelIndex &idx, int offset);
void doCardModified();
void doMetadataModified();
signals:
/**
* A modification has been made to the cards in the deck
*/
void cardModified();
/**
* A card that wasn't previously in the deck was added to the deck, or the last copy of a card was removed from the
* deck.
*/
void uniqueCardsChanged();
/**
* A modification has been made to the metadata in the deck
*/
void metadataModified();
/**
* A modification has been made to the cards or metadata in the deck
*/
void deckModified();
/**
* The history has been greatly changed and needs to be reloaded.
*/
void historyChanged();
/**
* The deck has been completely changed.
*/
void deckReplaced();
/**
* The isModified state of the deck has changed
* @param isModified the new state
*/
void isModifiedChanged(bool isModified);
/**
* The selected card on any views connected to this deck should be changed to this index.
* @param index The model index
*/
void focusIndexChanged(QModelIndex index);
};
#endif // COCKATRICE_DECK_STATE_MANAGER_H

View file

@ -59,7 +59,7 @@ DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent)
void DlgEditPassword::actOk() void DlgEditPassword::actOk()
{ {
// TODO this stuff should be using qvalidators //! \todo this stuff should be using qvalidators
if (newPasswordEdit->text().length() < 8) { if (newPasswordEdit->text().length() < 8) {
QMessageBox::critical(this, tr("Error"), tr("Your password is too short.")); QMessageBox::critical(this, tr("Error"), tr("Your password is too short."));
return; return;

View file

@ -121,7 +121,7 @@ void DlgForgotPasswordReset::actOk()
return; return;
} }
// TODO this stuff should be using qvalidators //! \todo this stuff should be using qvalidators
if (newpasswordEdit->text().length() < 8) { if (newpasswordEdit->text().length() < 8) {
QMessageBox::critical(this, tr("Error"), tr("Your password is too short.")); QMessageBox::critical(this, tr("Error"), tr("Your password is too short."));
return; return;

View file

@ -66,26 +66,26 @@ void AbstractDlgDeckTextEdit::setText(const QString &text)
} }
/** /**
* Tries to load the current contents of the contentsEdit into the DeckLoader * Tries to load the current contents of the contentsEdit into the deckList
* *
* @param deckLoader The DeckLoader to load the deck into * @param deckList The deckList to load the deck into
* @return Whether the loading was successful * @return Whether the loading was successful
*/ */
bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckList &deckList) const
{ {
QString buffer = contentsEdit->toPlainText(); QString buffer = contentsEdit->toPlainText();
if (buffer.contains("<cockatrice_deck version=\"1\">")) { if (buffer.contains("<cockatrice_deck version=\"1\">")) {
return deckLoader->getDeckList()->loadFromString_Native(buffer); return deckList.loadFromString_Native(buffer);
} }
QTextStream stream(&buffer); QTextStream stream(&buffer);
if (deckLoader->getDeckList()->loadFromStream_Plain(stream, true)) { if (deckList.loadFromStream_Plain(stream, true)) {
if (loadSetNameAndNumberCheckBox->isChecked()) { if (loadSetNameAndNumberCheckBox->isChecked()) {
deckLoader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); deckList.forEachCard(CardNodeFunction::ResolveProviderId());
} else { } else {
deckLoader->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); deckList.forEachCard(CardNodeFunction::ClearPrintingData());
} }
return true; return true;
} }
@ -108,7 +108,7 @@ void AbstractDlgDeckTextEdit::keyPressEvent(QKeyEvent *event)
* *
* @param parent The parent widget * @param parent The parent widget
*/ */
DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : AbstractDlgDeckTextEdit(parent), deckList(nullptr) DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : AbstractDlgDeckTextEdit(parent)
{ {
setWindowTitle(tr("Load deck from clipboard")); setWindowTitle(tr("Load deck from clipboard"));
@ -122,8 +122,6 @@ void DlgLoadDeckFromClipboard::actRefresh()
void DlgLoadDeckFromClipboard::actOK() void DlgLoadDeckFromClipboard::actOK()
{ {
deckList = new DeckLoader(this);
if (loadIntoDeck(deckList)) { if (loadIntoDeck(deckList)) {
accept(); accept();
} else { } else {
@ -134,18 +132,15 @@ void DlgLoadDeckFromClipboard::actOK()
/** /**
* Creates the dialog window for the "Edit deck in clipboard" action * Creates the dialog window for the "Edit deck in clipboard" action
* *
* @param _deckLoader The existing deck in the deck editor. Copies the instance * @param _deckList The existing deck in the deck editor.
* @param _annotated Whether to add annotations to the text that is loaded from the deck * @param _annotated Whether to add annotations to the text that is loaded from the deck
* @param parent The parent widget * @param parent The parent widget
*/ */
DlgEditDeckInClipboard::DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent) DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckList &_deckList, bool _annotated, QWidget *parent)
: AbstractDlgDeckTextEdit(parent), annotated(_annotated) : AbstractDlgDeckTextEdit(parent), deckList(_deckList), annotated(_annotated)
{ {
setWindowTitle(tr("Edit deck in clipboard")); setWindowTitle(tr("Edit deck in clipboard"));
deckLoader = new DeckLoader(this, _deckLoader->getDeckList());
deckLoader->setParent(this);
DlgEditDeckInClipboard::actRefresh(); DlgEditDeckInClipboard::actRefresh();
} }
@ -155,7 +150,7 @@ DlgEditDeckInClipboard::DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _an
* @param addComments Whether to add annotations * @param addComments Whether to add annotations
* @return A QString * @return A QString
*/ */
static QString deckListToString(const DeckList *deckList, bool addComments) static QString deckListToString(const DeckList &deckList, bool addComments)
{ {
QString buffer; QString buffer;
QTextStream stream(&buffer); QTextStream stream(&buffer);
@ -165,12 +160,12 @@ static QString deckListToString(const DeckList *deckList, bool addComments)
void DlgEditDeckInClipboard::actRefresh() void DlgEditDeckInClipboard::actRefresh()
{ {
setText(deckListToString(deckLoader->getDeckList(), annotated)); setText(deckListToString(deckList, annotated));
} }
void DlgEditDeckInClipboard::actOK() void DlgEditDeckInClipboard::actOK()
{ {
if (loadIntoDeck(deckLoader)) { if (loadIntoDeck(deckList)) {
accept(); accept();
} else { } else {
QMessageBox::critical(this, tr("Error"), tr("Invalid deck list.")); QMessageBox::critical(this, tr("Error"), tr("Invalid deck list."));

View file

@ -8,10 +8,11 @@
#ifndef DLG_LOAD_DECK_FROM_CLIPBOARD_H #ifndef DLG_LOAD_DECK_FROM_CLIPBOARD_H
#define DLG_LOAD_DECK_FROM_CLIPBOARD_H #define DLG_LOAD_DECK_FROM_CLIPBOARD_H
#include "../../deck_loader/loaded_deck.h"
#include <QCheckBox> #include <QCheckBox>
#include <QDialog> #include <QDialog>
class DeckLoader;
class QPlainTextEdit; class QPlainTextEdit;
class QPushButton; class QPushButton;
@ -35,15 +36,13 @@ public:
/** /**
* Gets the loaded deck. Only call this method after this dialog window has been successfully exec'd. * Gets the loaded deck. Only call this method after this dialog window has been successfully exec'd.
* *
* The returned DeckLoader is parented to this object; make sure to take ownership of the DeckLoader if you intend * @return The loaded decklist
* to use it, since otherwise it will get destroyed once this dlg is destroyed
* @return The DeckLoader
*/ */
[[nodiscard]] virtual DeckLoader *getDeckList() const = 0; [[nodiscard]] virtual const DeckList &getDeckList() = 0;
protected: protected:
void setText(const QString &text); void setText(const QString &text);
bool loadIntoDeck(DeckLoader *deckLoader) const; bool loadIntoDeck(DeckList &deckList) const;
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;
protected slots: protected slots:
@ -62,12 +61,12 @@ protected slots:
void actRefresh() override; void actRefresh() override;
private: private:
DeckLoader *deckList; DeckList deckList;
public: public:
explicit DlgLoadDeckFromClipboard(QWidget *parent = nullptr); explicit DlgLoadDeckFromClipboard(QWidget *parent = nullptr);
[[nodiscard]] DeckLoader *getDeckList() const override [[nodiscard]] const DeckList &getDeckList() override
{ {
return deckList; return deckList;
} }
@ -84,15 +83,15 @@ protected slots:
void actRefresh() override; void actRefresh() override;
private: private:
DeckLoader *deckLoader; DeckList deckList;
bool annotated; bool annotated;
public: public:
explicit DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent = nullptr); explicit DlgEditDeckInClipboard(const DeckList &_deckList, bool _annotated, QWidget *parent = nullptr);
[[nodiscard]] DeckLoader *getDeckList() const override [[nodiscard]] const DeckList &getDeckList() override
{ {
return deckLoader; return deckList;
} }
}; };

View file

@ -97,11 +97,11 @@ void DlgLoadDeckFromWebsite::accept()
} }
// Parse the plain text deck here // Parse the plain text deck here
DeckLoader *loader = new DeckLoader(this); DeckList deckList;
QTextStream stream(&deckText); QTextStream stream(&deckText);
loader->getDeckList()->loadFromStream_Plain(stream, false); deckList.loadFromStream_Plain(stream, false);
loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); deckList.forEachCard(CardNodeFunction::ResolveProviderId());
deck = loader; deck = deckList;
QDialog::accept(); QDialog::accept();
return; return;

View file

@ -26,9 +26,9 @@ public:
explicit DlgLoadDeckFromWebsite(QWidget *parent); explicit DlgLoadDeckFromWebsite(QWidget *parent);
void retranslateUi(); void retranslateUi();
bool testValidUrl(); bool testValidUrl();
DeckLoader *deck; DeckList deck;
DeckLoader *getDeck() const DeckList &getDeck() const
{ {
return deck; return deck;
} }

View file

@ -356,7 +356,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
void DlgRegister::actOk() void DlgRegister::actOk()
{ {
// TODO this stuff should be using qvalidators //! \todo this stuff should be using qvalidators
if (passwordEdit->text().length() < 8) { if (passwordEdit->text().length() < 8) {
QMessageBox::critical(this, tr("Registration Warning"), tr("Your password is too short.")); QMessageBox::critical(this, tr("Registration Warning"), tr("Your password is too short."));
return; return;

View file

@ -2,6 +2,7 @@
#include "../../deck_loader/card_node_function.h" #include "../../deck_loader/card_node_function.h"
#include "../../deck_loader/deck_loader.h" #include "../../deck_loader/deck_loader.h"
#include "../deck_editor/deck_state_manager.h"
#include "../interface/widgets/cards/card_info_picture_widget.h" #include "../interface/widgets/cards/card_info_picture_widget.h"
#include "../interface/widgets/general/layout_containers/flow_widget.h" #include "../interface/widgets/general/layout_containers/flow_widget.h"
@ -21,7 +22,8 @@
#include <qdrag.h> #include <qdrag.h>
#include <qevent.h> #include <qevent.h>
DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckListModel *_model) : QDialog(parent), model(_model) DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManger)
: QDialog(parent), deckStateManager(deckStateManger)
{ {
setMinimumSize(500, 500); setMinimumSize(500, 500);
setAcceptDrops(true); setAcceptDrops(true);
@ -143,52 +145,61 @@ void DlgSelectSetForCards::retranslateUi()
setAllToPreferredButton->setText(tr("Set all to preferred")); setAllToPreferredButton->setText(tr("Set all to preferred"));
} }
static bool swapPrinting(DeckListModel *model, const QString &modifiedSet, const QString &cardName)
{
QModelIndex idx = model->findCard(cardName, DECK_ZONE_MAIN);
if (!idx.isValid()) {
return false;
}
int amount = model->data(idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT), Qt::DisplayRole).toInt();
model->removeRow(idx.row(), idx.parent());
CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(cardName);
PrintingInfo printing = CardDatabaseManager::query()->getSpecificPrinting(cardName, modifiedSet, "");
for (int i = 0; i < amount; i++) {
model->addCard(ExactCard(cardInfo, printing), DECK_ZONE_MAIN);
}
return true;
}
void DlgSelectSetForCards::actOK() void DlgSelectSetForCards::actOK()
{ {
QMap<QString, QStringList> modifiedSetsAndCardsMap = getModifiedCards(); QMap<QString, QStringList> modifiedSetsAndCardsMap = getModifiedCards();
if (modifiedSetsAndCardsMap.isEmpty()) { if (modifiedSetsAndCardsMap.isEmpty()) {
accept(); // Nothing to do accept(); // Nothing to do
} else { return;
emit deckAboutToBeModified(tr("Bulk modified printings."));
} }
auto bulkModify = [&modifiedSetsAndCardsMap](DeckListModel *model) {
for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) {
for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) {
QModelIndex find_card = model->findCard(card, DECK_ZONE_MAIN); swapPrinting(model, modifiedSet, card);
if (!find_card.isValid()) {
continue;
}
int amount =
model->data(find_card.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT), Qt::DisplayRole).toInt();
model->removeRow(find_card.row(), find_card.parent());
CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(card);
PrintingInfo printing = CardDatabaseManager::query()->getSpecificPrinting(card, modifiedSet, "");
for (int i = 0; i < amount; i++) {
model->addCard(ExactCard(cardInfo, printing), DECK_ZONE_MAIN);
} }
} }
} return true;
if (!modifiedSetsAndCardsMap.isEmpty()) { };
emit deckModified();
} deckStateManager->modifyDeck(tr("Bulk modified printings."), bulkModify);
accept(); accept();
} }
void DlgSelectSetForCards::actClear() void DlgSelectSetForCards::actClear()
{ {
emit deckAboutToBeModified(tr("Cleared all printing information.")); deckStateManager->modifyDeck(tr("Cleared all printing information."), [](auto model) {
model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); model->forEachCard(CardNodeFunction::ClearPrintingData());
emit deckModified(); return true;
});
accept(); accept();
} }
void DlgSelectSetForCards::actSetAllToPreferred() void DlgSelectSetForCards::actSetAllToPreferred()
{ {
emit deckAboutToBeModified(tr("Set all printings to preferred.")); deckStateManager->modifyDeck(tr("Set all printings to preferred."), [](auto model) {
model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); model->forEachCard(CardNodeFunction::ClearPrintingData());
model->getDeckList()->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); model->forEachCard(CardNodeFunction::SetProviderIdToPreferred());
emit deckModified(); return true;
});
accept(); accept();
} }
@ -221,17 +232,11 @@ void DlgSelectSetForCards::sortSetsByCount()
QMap<QString, int> DlgSelectSetForCards::getSetsForCards() QMap<QString, int> DlgSelectSetForCards::getSetsForCards()
{ {
QMap<QString, int> setCounts; QMap<QString, int> setCounts;
if (!model)
return setCounts;
DeckList *decklist = model->getDeckList(); QList<QString> cardNames = deckStateManager->getModel()->getCardNames();
if (!decklist)
return setCounts;
QList<const DecklistCardNode *> cardsInDeck = decklist->getCardNodes(); for (auto cardName : cardNames) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName);
for (auto currentCard : cardsInDeck) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (!infoPtr) if (!infoPtr)
continue; continue;
@ -267,19 +272,15 @@ void DlgSelectSetForCards::updateCardLists()
} }
} }
DeckList *decklist = model->getDeckList(); QList<QString> cardNames = deckStateManager->getModel()->getCardNames();
if (!decklist)
return;
QList<const DecklistCardNode *> cardsInDeck = decklist->getCardNodes(); for (auto cardName : cardNames) {
for (auto currentCard : cardsInDeck) {
bool found = false; bool found = false;
QString foundSetName; QString foundSetName;
// Check across all sets if the card is present // Check across all sets if the card is present
for (auto it = selectedCardsBySet.begin(); it != selectedCardsBySet.end(); ++it) { for (auto it = selectedCardsBySet.begin(); it != selectedCardsBySet.end(); ++it) {
if (it.value().contains(currentCard->getName())) { if (it.value().contains(cardName)) {
found = true; found = true;
foundSetName = it.key(); // Store the set name where it was found foundSetName = it.key(); // Store the set name where it was found
break; // Stop at the first match break; // Stop at the first match
@ -288,15 +289,15 @@ void DlgSelectSetForCards::updateCardLists()
if (!found) { if (!found) {
// The card was not in any selected set // The card was not in any selected set
ExactCard card = CardDatabaseManager::query()->getCard({currentCard->getName()}); ExactCard card = CardDatabaseManager::query()->getCard({cardName});
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(uneditedCardsFlowWidget); CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(uneditedCardsFlowWidget);
picture_widget->setCard(card); picture_widget->setCard(card);
uneditedCardsFlowWidget->addWidget(picture_widget); uneditedCardsFlowWidget->addWidget(picture_widget);
} else { } else {
ExactCard card = CardDatabaseManager::query()->getCard( ExactCard card =
{currentCard->getName(), CardDatabaseManager::getInstance() CardDatabaseManager::query()->getCard({cardName, CardDatabaseManager::getInstance()
->query() ->query()
->getSpecificPrinting(currentCard->getName(), foundSetName, "") ->getSpecificPrinting(cardName, foundSetName, "")
.getUuid()}); .getUuid()});
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(modifiedCardsFlowWidget); CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(modifiedCardsFlowWidget);
picture_widget->setCard(card); picture_widget->setCard(card);
@ -353,23 +354,17 @@ void DlgSelectSetForCards::dropEvent(QDropEvent *event)
QMap<QString, QStringList> DlgSelectSetForCards::getCardsForSets() QMap<QString, QStringList> DlgSelectSetForCards::getCardsForSets()
{ {
QMap<QString, QStringList> setCards; QMap<QString, QStringList> setCards;
if (!model)
return setCards;
DeckList *decklist = model->getDeckList(); QList<QString> cardNames = deckStateManager->getModel()->getCardNames();
if (!decklist)
return setCards;
QList<const DecklistCardNode *> cardsInDeck = decklist->getCardNodes(); for (auto cardName : cardNames) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName);
for (auto currentCard : cardsInDeck) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (!infoPtr) if (!infoPtr)
continue; continue;
SetToPrintingsMap setMap = infoPtr->getSets(); SetToPrintingsMap setMap = infoPtr->getSets();
for (auto it = setMap.begin(); it != setMap.end(); ++it) { for (auto it = setMap.begin(); it != setMap.end(); ++it) {
setCards[it.key()].append(currentCard->getName()); setCards[it.key()].append(cardName);
} }
} }

View file

@ -18,6 +18,7 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <libcockatrice/models/deck_list/deck_list_model.h> #include <libcockatrice/models/deck_list/deck_list_model.h>
class DeckStateManager;
class SetEntryWidget; // Forward declaration class SetEntryWidget; // Forward declaration
class DlgSelectSetForCards : public QDialog class DlgSelectSetForCards : public QDialog
@ -25,7 +26,7 @@ class DlgSelectSetForCards : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit DlgSelectSetForCards(QWidget *parent, DeckListModel *_model); explicit DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManager);
void retranslateUi(); void retranslateUi();
void sortSetsByCount(); void sortSetsByCount();
QMap<QString, QStringList> getCardsForSets(); QMap<QString, QStringList> getCardsForSets();
@ -37,7 +38,6 @@ public:
signals: signals:
void widgetOrderChanged(); void widgetOrderChanged();
void orderChanged(); void orderChanged();
void deckAboutToBeModified(const QString &reason);
void deckModified(); void deckModified();
public slots: public slots:
@ -61,7 +61,7 @@ private:
QLabel *modifiedCardsLabel; QLabel *modifiedCardsLabel;
QWidget *listContainer; QWidget *listContainer;
QListWidget *listWidget; QListWidget *listWidget;
DeckListModel *model; DeckStateManager *deckStateManager;
QMap<QString, SetEntryWidget *> setEntries; QMap<QString, SetEntryWidget *> setEntries;
QPushButton *clearButton; QPushButton *clearButton;
QPushButton *setAllToPreferredButton; QPushButton *setAllToPreferredButton;

View file

@ -1913,7 +1913,7 @@ void DlgSettings::closeEvent(QCloseEvent *event)
} }
if (!QDir(SettingsCache::instance().getDeckPath()).exists() || SettingsCache::instance().getDeckPath().isEmpty()) { if (!QDir(SettingsCache::instance().getDeckPath()).exists() || SettingsCache::instance().getDeckPath().isEmpty()) {
// TODO: Prompt to create it //! \todo Prompt to create it
if (QMessageBox::critical( if (QMessageBox::critical(
this, tr("Error"), this, tr("Error"),
tr("The path to your deck directory is invalid. Would you like to go back and set the correct path?"), tr("The path to your deck directory is invalid. Would you like to go back and set the correct path?"),
@ -1924,7 +1924,7 @@ void DlgSettings::closeEvent(QCloseEvent *event)
} }
if (!QDir(SettingsCache::instance().getPicsPath()).exists() || SettingsCache::instance().getPicsPath().isEmpty()) { if (!QDir(SettingsCache::instance().getPicsPath()).exists() || SettingsCache::instance().getPicsPath().isEmpty()) {
// TODO: Prompt to create it //! \todo Prompt to create it
if (QMessageBox::critical(this, tr("Error"), if (QMessageBox::critical(this, tr("Error"),
tr("The path to your card pictures directory is invalid. Would you like to go back " tr("The path to your card pictures directory is invalid. Would you like to go back "
"and set the correct path?"), "and set the correct path?"),

View file

@ -20,10 +20,6 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
layout = new QGridLayout(this); layout = new QGridLayout(this);
backgroundSourceCard = new CardInfoPictureArtCropWidget(this); backgroundSourceCard = new CardInfoPictureArtCropWidget(this);
backgroundSourceDeck = new DeckLoader(this);
backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod",
DeckFileFormat::Cockatrice, false);
gradientColors = extractDominantColors(background); gradientColors = extractDominantColors(background);
@ -72,13 +68,20 @@ void HomeWidget::initializeBackgroundFromSource()
cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
break; break;
case BackgroundSources::DeckFileArt: case BackgroundSources::DeckFileArt:
backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", loadBackgroundSourceDeck();
DeckFileFormat::Cockatrice, false);
cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
break; break;
} }
} }
void HomeWidget::loadBackgroundSourceDeck()
{
DeckLoader deckLoader = DeckLoader(this);
deckLoader.loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", DeckFileFormat::Cockatrice,
false);
backgroundSourceDeck = deckLoader.getDeck().deckList;
}
void HomeWidget::updateRandomCard() void HomeWidget::updateRandomCard()
{ {
auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource()); auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource());
@ -95,7 +98,7 @@ void HomeWidget::updateRandomCard()
newCard.getCardPtr()->getProperty("layout") != "normal"); newCard.getCardPtr()->getProperty("layout") != "normal");
break; break;
case BackgroundSources::DeckFileArt: case BackgroundSources::DeckFileArt:
QList<CardRef> cardRefs = backgroundSourceDeck->getDeckList()->getCardRefList(); QList<CardRef> cardRefs = backgroundSourceDeck.getCardRefList();
ExactCard oldCard = backgroundSourceCard->getCard(); ExactCard oldCard = backgroundSourceCard->getCard();
if (!cardRefs.empty()) { if (!cardRefs.empty()) {
@ -183,7 +186,7 @@ QGroupBox *HomeWidget::createButtons()
auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors); auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors);
connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor, connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor,
[this] { tabSupervisor->openDeckInNewTab(nullptr); }); [this] { tabSupervisor->openDeckInNewTab(LoadedDeck()); });
boxLayout->addWidget(visualDeckEditorButton); boxLayout->addWidget(visualDeckEditorButton);
auto visualDeckStorageButton = new HomeStyledButton(tr("Browse Decks"), gradientColors); auto visualDeckStorageButton = new HomeStyledButton(tr("Browse Decks"), gradientColors);
connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor, connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor,

View file

@ -40,10 +40,12 @@ private:
TabSupervisor *tabSupervisor; TabSupervisor *tabSupervisor;
QPixmap background; QPixmap background;
CardInfoPictureArtCropWidget *backgroundSourceCard = nullptr; CardInfoPictureArtCropWidget *backgroundSourceCard = nullptr;
DeckLoader *backgroundSourceDeck; DeckList backgroundSourceDeck;
QPixmap overlay; QPixmap overlay;
QPair<QColor, QColor> gradientColors; QPair<QColor, QColor> gradientColors;
HomeStyledButton *connectButton; HomeStyledButton *connectButton;
void loadBackgroundSourceDeck();
}; };
#endif // HOME_WIDGET_H #endif // HOME_WIDGET_H

View file

@ -11,20 +11,15 @@
* UI elements for managing card counts in both the mainboard and sideboard zones. * UI elements for managing card counts in both the mainboard and sideboard zones.
* *
* @param parent The parent widget. * @param parent The parent widget.
* @param deckEditor Pointer to the TabDeckEditor. * @param deckStateManager Pointer to the DeckStateManager
* @param deckModel Pointer to the DeckListModel.
* @param deckView Pointer to the QTreeView for the deck display.
* @param cardSizeSlider Pointer to the QSlider used for dynamic font resizing. * @param cardSizeSlider Pointer to the QSlider used for dynamic font resizing.
* @param rootCard The root card for the widget. * @param rootCard The root card for the widget.
*/ */
AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent,
AbstractTabDeckEditor *deckEditor, DeckStateManager *deckStateManager,
DeckListModel *deckModel,
QTreeView *deckView,
QSlider *cardSizeSlider, QSlider *cardSizeSlider,
const ExactCard &rootCard) const ExactCard &rootCard)
: QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider), : QWidget(parent), cardSizeSlider(cardSizeSlider)
rootCard(rootCard)
{ {
layout = new QVBoxLayout(this); layout = new QVBoxLayout(this);
layout->setAlignment(Qt::AlignHCenter); layout->setAlignment(Qt::AlignHCenter);
@ -33,11 +28,9 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent,
setContentsMargins(5, 5, 5, 5); // Padding around the text setContentsMargins(5, 5, 5, 5); // Padding around the text
zoneLabelMainboard = new ShadowBackgroundLabel(this, tr("Mainboard")); zoneLabelMainboard = new ShadowBackgroundLabel(this, tr("Mainboard"));
buttonBoxMainboard = buttonBoxMainboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_MAIN);
new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_MAIN);
zoneLabelSideboard = new ShadowBackgroundLabel(this, tr("Sideboard")); zoneLabelSideboard = new ShadowBackgroundLabel(this, tr("Sideboard"));
buttonBoxSideboard = buttonBoxSideboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_SIDE);
new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_SIDE);
layout->addWidget(zoneLabelMainboard, 0, Qt::AlignHCenter | Qt::AlignBottom); layout->addWidget(zoneLabelMainboard, 0, Qt::AlignHCenter | Qt::AlignBottom);
layout->addWidget(buttonBoxMainboard, 0, Qt::AlignHCenter | Qt::AlignTop); layout->addWidget(buttonBoxMainboard, 0, Qt::AlignHCenter | Qt::AlignTop);

View file

@ -18,9 +18,7 @@ class AllZonesCardAmountWidget : public QWidget
Q_OBJECT Q_OBJECT
public: public:
explicit AllZonesCardAmountWidget(QWidget *parent, explicit AllZonesCardAmountWidget(QWidget *parent,
AbstractTabDeckEditor *deckEditor, DeckStateManager *deckStateManager,
DeckListModel *deckModel,
QTreeView *deckView,
QSlider *cardSizeSlider, QSlider *cardSizeSlider,
const ExactCard &rootCard); const ExactCard &rootCard);
int getMainboardAmount(); int getMainboardAmount();
@ -36,11 +34,7 @@ public slots:
private: private:
QVBoxLayout *layout; QVBoxLayout *layout;
AbstractTabDeckEditor *deckEditor;
DeckListModel *deckModel;
QTreeView *deckView;
QSlider *cardSizeSlider; QSlider *cardSizeSlider;
ExactCard rootCard;
QLabel *zoneLabelMainboard; QLabel *zoneLabelMainboard;
CardAmountWidget *buttonBoxMainboard; CardAmountWidget *buttonBoxMainboard;
QLabel *zoneLabelSideboard; QLabel *zoneLabelSideboard;

View file

@ -1,5 +1,7 @@
#include "card_amount_widget.h" #include "card_amount_widget.h"
#include "../deck_editor/deck_state_manager.h"
#include <QPainter> #include <QPainter>
#include <QTimer> #include <QTimer>
@ -7,22 +9,17 @@
* @brief Constructs a widget for displaying and controlling the card count in a specific zone. * @brief Constructs a widget for displaying and controlling the card count in a specific zone.
* *
* @param parent The parent widget. * @param parent The parent widget.
* @param deckEditor Pointer to the TabDeckEditor instance.
* @param deckModel Pointer to the DeckListModel instance.
* @param deckView Pointer to the QTreeView displaying the deck.
* @param cardSizeSlider Pointer to the QSlider for adjusting font size. * @param cardSizeSlider Pointer to the QSlider for adjusting font size.
* @param rootCard The root card to manage within the widget. * @param rootCard The root card to manage within the widget.
* @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE). * @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE).
*/ */
CardAmountWidget::CardAmountWidget(QWidget *parent, CardAmountWidget::CardAmountWidget(QWidget *parent,
AbstractTabDeckEditor *deckEditor, DeckStateManager *deckStateManager,
DeckListModel *deckModel,
QTreeView *deckView,
QSlider *cardSizeSlider, QSlider *cardSizeSlider,
const ExactCard &rootCard, const ExactCard &rootCard,
const QString &zoneName) const QString &zoneName)
: QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider), : QWidget(parent), deckStateManager(deckStateManager), cardSizeSlider(cardSizeSlider), rootCard(rootCard),
rootCard(rootCard), zoneName(zoneName), hovered(false) zoneName(zoneName), hovered(false)
{ {
layout = new QHBoxLayout(this); layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
@ -56,15 +53,10 @@ CardAmountWidget::CardAmountWidget(QWidget *parent,
layout->addWidget(incrementButton); layout->addWidget(incrementButton);
// React to model changes // React to model changes
connect(deckModel, &DeckListModel::dataChanged, this, &CardAmountWidget::updateCardCount); connect(deckStateManager, &DeckStateManager::cardModified, this, &CardAmountWidget::updateCardCount);
connect(deckModel, &QAbstractItemModel::rowsRemoved, this, &CardAmountWidget::updateCardCount);
// Connect slider for dynamic font size adjustment // Connect slider for dynamic font size adjustment
connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize);
if (deckEditor) {
connect(this, &CardAmountWidget::deckModified, deckEditor, &AbstractTabDeckEditor::onDeckHistorySaveRequested);
}
} }
/** /**
@ -137,6 +129,29 @@ void CardAmountWidget::updateCardCount()
layout->activate(); layout->activate();
} }
static QModelIndex addAndReplacePrintings(DeckListModel *model,
const QModelIndex &existing,
const ExactCard &rootCard,
const QString &zone,
int extraCopies,
bool replaceProviderless)
{
auto newCardIndex = model->addCard(rootCard, zone);
if (!newCardIndex.isValid()) {
return {};
}
// 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());
}
// Set Index and Focus as if the user had just clicked the new card and modify the deckEditor saveState
return model->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
rootCard.getPrinting().getProperty("num"));
}
/** /**
* @brief Adds a printing of the card to the specified zone (Mainboard or Sideboard). * @brief Adds a printing of the card to the specified zone (Mainboard or Sideboard).
* *
@ -144,25 +159,24 @@ void CardAmountWidget::updateCardCount()
*/ */
void CardAmountWidget::addPrinting(const QString &zone) void CardAmountWidget::addPrinting(const QString &zone)
{ {
int addedCount = 1;
// Check if we will need to add extra copies due to replacing copies without providerIds // Check if we will need to add extra copies due to replacing copies without providerIds
QModelIndex existing = deckModel->findCard(rootCard.getName(), zone); QModelIndex existing = deckStateManager->getModel()->findCard(rootCard.getName(), zone);
int extraCopies = 0; int extraCopies = 0;
bool replacingProviderless = false; bool replacingProviderless = false;
if (existing.isValid()) { if (existing.isValid()) {
QString providerId = deckModel->data(existing.sibling(existing.row(), 4), Qt::DisplayRole).toString(); QString foundProviderId =
if (providerId.isEmpty()) { existing.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString();
int amount = deckModel->data(existing, Qt::DisplayRole).toInt(); if (foundProviderId.isEmpty()) {
int amount = existing.data(Qt::DisplayRole).toInt();
extraCopies = amount - 1; // One less because we *always* add one extraCopies = amount - 1; // One less because we *always* add one
replacingProviderless = true; replacingProviderless = true;
} }
} }
addedCount += extraCopies;
QString reason = QString("Added %1 copies of '%2 (%3) %4' to %5 [ProviderID: %6]%7") QString reason = QString("Added %1 copies of '%2 (%3) %4' to %5 [ProviderID: %6]%7")
.arg(addedCount) .arg(1 + extraCopies)
.arg(rootCard.getName()) .arg(rootCard.getName())
.arg(rootCard.getPrinting().getSet()->getShortName()) .arg(rootCard.getPrinting().getSet()->getShortName())
.arg(rootCard.getPrinting().getProperty("num")) .arg(rootCard.getPrinting().getProperty("num"))
@ -170,29 +184,14 @@ void CardAmountWidget::addPrinting(const QString &zone)
.arg(rootCard.getPrinting().getUuid()) .arg(rootCard.getPrinting().getUuid())
.arg(replacingProviderless ? " (replaced providerless printings)" : ""); .arg(replacingProviderless ? " (replaced providerless printings)" : "");
emit deckModified(reason);
// Add the card and expand the list UI // Add the card and expand the list UI
auto newCardIndex = deckModel->addCard(rootCard, zone); QModelIndex newCardIndex = deckStateManager->modifyDeck(reason, [&](auto model) {
recursiveExpand(newCardIndex); return addAndReplacePrintings(model, existing, rootCard, zone, extraCopies, replacingProviderless);
});
// Check if a card without a providerId already exists in the deckModel and replace it, if so. if (newCardIndex.isValid()) {
QString foundProviderId = deckModel->data(existing.sibling(existing.row(), 4), Qt::DisplayRole).toString(); emit deckStateManager->focusIndexChanged(newCardIndex);
if (existing.isValid() && existing != newCardIndex && foundProviderId == "") {
auto amount = deckModel->data(existing, Qt::DisplayRole);
for (int i = 0; i < amount.toInt() - 1; i++) {
deckModel->addCard(rootCard, zone);
} }
deckModel->removeRow(existing.row(), existing.parent());
}
// Set Index and Focus as if the user had just clicked the new card and modify the deckEditor saveState
newCardIndex = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
rootCard.getPrinting().getProperty("num"));
deckView->setCurrentIndex(newCardIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
deckEditor->setModified(true);
} }
/** /**
@ -227,46 +226,6 @@ void CardAmountWidget::removePrintingSideboard()
decrementCardHelper(DECK_ZONE_SIDE); decrementCardHelper(DECK_ZONE_SIDE);
} }
/**
* @brief Recursively expands the card in the deck view starting from the given index.
*
* @param index The model index of the card to expand.
*/
void CardAmountWidget::recursiveExpand(const QModelIndex &index)
{
if (index.parent().isValid()) {
recursiveExpand(index.parent());
}
deckView->expand(index);
}
/**
* @brief Offsets the card count at the specified index by the given amount.
*
* @param idx The model index of the card.
* @param offset The amount to add or subtract from the card count.
*/
void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset)
{
if (!idx.isValid() || offset == 0) {
return;
}
const QModelIndex numberIndex = idx.sibling(idx.row(), 0);
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
const int new_count = count + offset;
deckView->setCurrentIndex(numberIndex);
if (new_count <= 0) {
deckModel->removeRow(idx.row(), idx.parent());
} else {
deckModel->setData(numberIndex, new_count, Qt::EditRole);
}
deckEditor->setModified(true);
}
/** /**
* @brief Helper function to decrement the card count for a given zone. * @brief Helper function to decrement the card count for a given zone.
* *
@ -281,13 +240,11 @@ void CardAmountWidget::decrementCardHelper(const QString &zone)
.arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard")
.arg(rootCard.getPrinting().getUuid()); .arg(rootCard.getPrinting().getUuid());
emit deckModified(reason); deckStateManager->modifyDeck(reason, [this, &zone](auto model) {
QModelIndex idx = model->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
rootCard.getPrinting().getProperty("num")); rootCard.getPrinting().getProperty("num"));
return model->offsetCountAtIndex(idx, -1);
offsetCountAtIndex(idx, -1); });
deckEditor->setModified(true);
} }
/** /**
@ -298,29 +255,14 @@ void CardAmountWidget::decrementCardHelper(const QString &zone)
*/ */
int CardAmountWidget::countCardsInZone(const QString &deckZone) int CardAmountWidget::countCardsInZone(const QString &deckZone)
{ {
if (rootCard.getPrinting().getUuid().isEmpty()) { QString uuid = rootCard.getPrinting().getUuid();
if (uuid.isEmpty()) {
return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us. return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us.
} }
if (!deckModel) { QList<ExactCard> cards = deckStateManager->getModel()->getCardsForZone(deckZone);
return -1;
}
DeckList *decklist = deckModel->getDeckList(); return std::count_if(cards.cbegin(), cards.cend(),
if (!decklist) { [&uuid](const ExactCard &card) { return card.getPrinting().getUuid() == uuid; });
return -1;
}
QList<const DecklistCardNode *> cardsInDeck = decklist->getCardNodes({deckZone});
int count = 0;
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
if (currentCard->getCardProviderId() == rootCard.getPrinting().getProperty("uuid")) {
count++;
}
}
}
return count;
} }

View file

@ -27,9 +27,7 @@ signals:
public: public:
explicit CardAmountWidget(QWidget *parent, explicit CardAmountWidget(QWidget *parent,
AbstractTabDeckEditor *deckEditor, DeckStateManager *deckStateManager,
DeckListModel *deckModel,
QTreeView *deckView,
QSlider *cardSizeSlider, QSlider *cardSizeSlider,
const ExactCard &rootCard, const ExactCard &rootCard,
const QString &zoneName); const QString &zoneName);
@ -44,9 +42,7 @@ protected:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
private: private:
AbstractTabDeckEditor *deckEditor; DeckStateManager *deckStateManager;
DeckListModel *deckModel;
QTreeView *deckView;
QSlider *cardSizeSlider; QSlider *cardSizeSlider;
ExactCard rootCard; ExactCard rootCard;
QString zoneName; QString zoneName;
@ -57,9 +53,7 @@ private:
bool hovered; bool hovered;
void offsetCountAtIndex(const QModelIndex &idx, int offset);
void decrementCardHelper(const QString &zoneName); void decrementCardHelper(const QString &zoneName);
void recursiveExpand(const QModelIndex &index);
private slots: private slots:
void addPrintingMainboard(); void addPrintingMainboard();

View file

@ -3,6 +3,7 @@
#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/cache_settings.h"
#include "../../../interface/card_picture_loader/card_picture_loader.h" #include "../../../interface/card_picture_loader/card_picture_loader.h"
#include "../../../interface/widgets/dialogs/dlg_select_set_for_cards.h" #include "../../../interface/widgets/dialogs/dlg_select_set_for_cards.h"
#include "../deck_editor/deck_state_manager.h"
#include "printing_selector_card_display_widget.h" #include "printing_selector_card_display_widget.h"
#include "printing_selector_card_search_widget.h" #include "printing_selector_card_search_widget.h"
#include "printing_selector_card_selection_widget.h" #include "printing_selector_card_selection_widget.h"
@ -21,12 +22,9 @@
* *
* @param parent The parent widget for the PrintingSelector. * @param parent The parent widget for the PrintingSelector.
* @param deckEditor The TabDeckEditor instance used for managing the deck. * @param deckEditor The TabDeckEditor instance used for managing the deck.
* @param deckModel The DeckListModel instance that provides data for the deck's contents.
* @param deckView The QTreeView instance used to display the deck and its contents.
*/ */
PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deckEditor) PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deckEditor)
: QWidget(parent), deckEditor(_deckEditor), deckModel(deckEditor->deckDockWidget->deckModel), : QWidget(parent), deckEditor(_deckEditor), deckStateManager(_deckEditor->deckStateManager)
deckView(deckEditor->deckDockWidget->deckView)
{ {
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout = new QVBoxLayout(this); layout = new QVBoxLayout(this);
@ -74,13 +72,12 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck
layout->addWidget(flowWidget); layout->addWidget(flowWidget);
cardSelectionBar = new PrintingSelectorCardSelectionWidget(this); cardSelectionBar = new PrintingSelectorCardSelectionWidget(this, deckStateManager);
cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible()); cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible());
layout->addWidget(cardSelectionBar); layout->addWidget(cardSelectionBar);
// Connect deck model data change signal to update display // Connect deck model data change signal to update display
connect(deckModel, &DeckListModel::rowsInserted, this, &PrintingSelector::printingsInDeckChanged); connect(deckStateManager, &DeckStateManager::uniqueCardsChanged, this, &PrintingSelector::printingsInDeckChanged);
connect(deckModel, &DeckListModel::rowsRemoved, this, &PrintingSelector::printingsInDeckChanged);
retranslateUi(); retranslateUi();
} }
@ -115,9 +112,8 @@ void PrintingSelector::updateDisplay()
* @brief Sets the current card for the selector and updates the display. * @brief Sets the current card for the selector and updates the display.
* *
* @param newCard The new card to set. * @param newCard The new card to set.
* @param _currentZone The current zone the card is in.
*/ */
void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_currentZone) void PrintingSelector::setCard(const CardInfoPtr &newCard)
{ {
if (newCard.isNull()) { if (newCard.isNull()) {
return; return;
@ -129,7 +125,6 @@ void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_curre
} }
selectedCard = newCard; selectedCard = newCard;
currentZone = _currentZone;
if (isVisible()) { if (isVisible()) {
updateDisplay(); updateDisplay();
} }
@ -138,58 +133,6 @@ void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_curre
flowWidget->repaint(); flowWidget->repaint();
} }
/**
* @brief Selects the previous card in the list.
*/
void PrintingSelector::selectPreviousCard()
{
selectCard(-1);
}
/**
* @brief Selects the next card in the list.
*/
void PrintingSelector::selectNextCard()
{
selectCard(1);
}
/**
* @brief Selects a card based on the change direction.
*
* @param changeBy The direction to change, -1 for previous, 1 for next.
*/
void PrintingSelector::selectCard(const int changeBy)
{
if (changeBy == 0) {
return;
}
// Get the current index of the selected item
auto deckViewCurrentIndex = deckView->currentIndex();
auto nextIndex = deckViewCurrentIndex.siblingAtRow(deckViewCurrentIndex.row() + changeBy);
if (!nextIndex.isValid()) {
nextIndex = deckViewCurrentIndex;
// Increment to the next valid index, skipping header rows
AbstractDecklistNode *node;
do {
if (changeBy > 0) {
nextIndex = deckView->indexBelow(nextIndex);
} else {
nextIndex = deckView->indexAbove(nextIndex);
}
node = static_cast<AbstractDecklistNode *>(nextIndex.internalPointer());
} while (node && node->isDeckHeader());
}
if (nextIndex.isValid()) {
deckView->setCurrentIndex(nextIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
}
}
/** /**
* @brief Loads and displays all sets for the current selected card. * @brief Loads and displays all sets for the current selected card.
*/ */
@ -206,7 +149,8 @@ void PrintingSelector::getAllSetsForCurrentCard()
QList<PrintingInfo> printingsToUse; QList<PrintingInfo> printingsToUse;
if (SettingsCache::instance().getBumpSetsWithCardsInDeckToTop()) { if (SettingsCache::instance().getBumpSetsWithCardsInDeckToTop()) {
printingsToUse = sortToolBar->prependPrintingsInDeck(filteredPrintings, selectedCard, deckModel); printingsToUse =
sortToolBar->prependPrintingsInDeck(filteredPrintings, selectedCard, deckStateManager->getModel());
} else { } else {
printingsToUse = filteredPrintings; printingsToUse = filteredPrintings;
} }
@ -218,8 +162,8 @@ void PrintingSelector::getAllSetsForCurrentCard()
connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable { connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable {
for (int i = 0; i < BATCH_SIZE && currentIndex < printingsToUse.size(); ++i, ++currentIndex) { for (int i = 0; i < BATCH_SIZE && currentIndex < printingsToUse.size(); ++i, ++currentIndex) {
auto card = ExactCard(selectedCard, printingsToUse[currentIndex]); auto card = ExactCard(selectedCard, printingsToUse[currentIndex]);
auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget( auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckStateManager,
this, deckEditor, deckModel, deckView, cardSizeWidget->getSlider(), card, currentZone); cardSizeWidget->getSlider(), card);
flowWidget->addWidget(cardDisplayWidget); flowWidget->addWidget(cardDisplayWidget);
cardDisplayWidget->clampSetNameToPicture(); cardDisplayWidget->clampSetNameToPicture();
connect(cardDisplayWidget, &PrintingSelectorCardDisplayWidget::cardPreferenceChanged, this, connect(cardDisplayWidget, &PrintingSelectorCardDisplayWidget::cardPreferenceChanged, this,

View file

@ -21,6 +21,7 @@
#define BATCH_SIZE 10 #define BATCH_SIZE 10
class DeckStateManager;
class PrintingSelectorCardSearchWidget; class PrintingSelectorCardSearchWidget;
class PrintingSelectorCardSelectionWidget; class PrintingSelectorCardSelectionWidget;
class PrintingSelectorCardSortingWidget; class PrintingSelectorCardSortingWidget;
@ -33,28 +34,27 @@ class PrintingSelector : public QWidget
public: public:
PrintingSelector(QWidget *parent, AbstractTabDeckEditor *deckEditor); PrintingSelector(QWidget *parent, AbstractTabDeckEditor *deckEditor);
void setCard(const CardInfoPtr &newCard, const QString &_currentZone); void setCard(const CardInfoPtr &newCard);
void getAllSetsForCurrentCard(); void getAllSetsForCurrentCard();
[[nodiscard]] DeckListModel *getDeckModel() const
{
return deckModel;
}
[[nodiscard]] AbstractTabDeckEditor *getDeckEditor() const
{
return deckEditor;
}
public slots: public slots:
void retranslateUi(); void retranslateUi();
void updateDisplay(); void updateDisplay();
void selectPreviousCard();
void selectNextCard();
void toggleVisibilityNavigationButtons(bool _state); void toggleVisibilityNavigationButtons(bool _state);
private slots: private slots:
void printingsInDeckChanged(); void printingsInDeckChanged();
signals:
/**
* Requests the previous card in the list
*/
void prevCardRequested();
/**
* Requests the next card in the list
*/
void nextCardRequested();
private: private:
QVBoxLayout *layout; QVBoxLayout *layout;
SettingsButtonWidget *displayOptionsWidget; SettingsButtonWidget *displayOptionsWidget;
@ -67,13 +67,10 @@ private:
CardSizeWidget *cardSizeWidget; CardSizeWidget *cardSizeWidget;
PrintingSelectorCardSelectionWidget *cardSelectionBar; PrintingSelectorCardSelectionWidget *cardSelectionBar;
AbstractTabDeckEditor *deckEditor; AbstractTabDeckEditor *deckEditor;
DeckListModel *deckModel; DeckStateManager *deckStateManager;
QTreeView *deckView;
CardInfoPtr selectedCard; CardInfoPtr selectedCard;
QString currentZone;
QTimer *widgetLoadingBufferTimer; QTimer *widgetLoadingBufferTimer;
int currentIndex = 0; int currentIndex = 0;
void selectCard(int changeBy);
}; };
#endif // PRINTING_SELECTOR_H #endif // PRINTING_SELECTOR_H

View file

@ -17,30 +17,24 @@
* display. * display.
* *
* @param parent The parent widget for this display. * @param parent The parent widget for this display.
* @param _deckEditor The TabDeckEditor instance for deck management. * @param deckEditor The TabDeckEditor instance for deck management.
* @param _deckModel The DeckListModel instance providing deck data. * @param deckStateManager The DeckStateManager instance providing deck data.
* @param _deckView The QTreeView instance displaying the deck. * @param cardSizeSlider The slider controlling the size of the displayed card.
* @param _cardSizeSlider The slider controlling the size of the displayed card. * @param rootCard The root card object, representing the card to be displayed.
* @param _rootCard The root card object, representing the card to be displayed.
* @param _currentZone The current zone in which the card is located.
*/ */
PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *parent, PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *parent,
AbstractTabDeckEditor *_deckEditor, AbstractTabDeckEditor *deckEditor,
DeckListModel *_deckModel, DeckStateManager *deckStateManager,
QTreeView *_deckView, QSlider *cardSizeSlider,
QSlider *_cardSizeSlider, const ExactCard &rootCard)
const ExactCard &_rootCard, : QWidget(parent)
QString &_currentZone)
: QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView),
cardSizeSlider(_cardSizeSlider), rootCard(_rootCard), currentZone(_currentZone)
{ {
layout = new QVBoxLayout(this); layout = new QVBoxLayout(this);
setLayout(layout); setLayout(layout);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// Create the overlay widget for the card display // Create the overlay widget for the card display
overlayWidget = overlayWidget = new PrintingSelectorCardOverlayWidget(this, deckEditor, deckStateManager, cardSizeSlider, rootCard);
new PrintingSelectorCardOverlayWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard);
connect(overlayWidget, &PrintingSelectorCardOverlayWidget::cardPreferenceChanged, this, connect(overlayWidget, &PrintingSelectorCardOverlayWidget::cardPreferenceChanged, this,
[this]() { emit cardPreferenceChanged(); }); [this]() { emit cardPreferenceChanged(); });

View file

@ -20,12 +20,10 @@ class PrintingSelectorCardDisplayWidget : public QWidget
public: public:
PrintingSelectorCardDisplayWidget(QWidget *parent, PrintingSelectorCardDisplayWidget(QWidget *parent,
AbstractTabDeckEditor *_deckEditor, AbstractTabDeckEditor *deckEditor,
DeckListModel *_deckModel, DeckStateManager *deckStateManager,
QTreeView *_deckView, QSlider *cardSizeSlider,
QSlider *_cardSizeSlider, const ExactCard &rootCard);
const ExactCard &_rootCard,
QString &_currentZone);
public slots: public slots:
void clampSetNameToPicture(); void clampSetNameToPicture();
@ -36,12 +34,6 @@ signals:
private: private:
QVBoxLayout *layout; QVBoxLayout *layout;
SetNameAndCollectorsNumberDisplayWidget *setNameAndCollectorsNumberDisplayWidget; SetNameAndCollectorsNumberDisplayWidget *setNameAndCollectorsNumberDisplayWidget;
AbstractTabDeckEditor *deckEditor;
DeckListModel *deckModel;
QTreeView *deckView;
QSlider *cardSizeSlider;
ExactCard rootCard;
QString currentZone;
PrintingSelectorCardOverlayWidget *overlayWidget; PrintingSelectorCardOverlayWidget *overlayWidget;
}; };

View file

@ -22,19 +22,16 @@
* *
* @param parent The parent widget for this overlay. * @param parent The parent widget for this overlay.
* @param _deckEditor The TabDeckEditor instance for deck management. * @param _deckEditor The TabDeckEditor instance for deck management.
* @param _deckModel The DeckListModel instance providing deck data. * @param deckStateManager The DeckStateManager instance providing deck data.
* @param _deckView The QTreeView instance displaying the deck. * @param cardSizeSlider The slider controlling the size of the card.
* @param _cardSizeSlider The slider controlling the size of the card.
* @param _rootCard The root card object that contains information about the card. * @param _rootCard The root card object that contains information about the card.
*/ */
PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent, PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent,
AbstractTabDeckEditor *_deckEditor, AbstractTabDeckEditor *_deckEditor,
DeckListModel *_deckModel, DeckStateManager *deckStateManager,
QTreeView *_deckView, QSlider *cardSizeSlider,
QSlider *_cardSizeSlider,
const ExactCard &_rootCard) const ExactCard &_rootCard)
: QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView), : QWidget(parent), deckEditor(_deckEditor), rootCard(_rootCard)
cardSizeSlider(_cardSizeSlider), rootCard(_rootCard)
{ {
// Set up the main layout // Set up the main layout
auto *mainLayout = new QVBoxLayout(this); auto *mainLayout = new QVBoxLayout(this);
@ -59,8 +56,7 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa
updatePinBadgeVisibility(); updatePinBadgeVisibility();
// Add AllZonesCardAmountWidget // Add AllZonesCardAmountWidget
allZonesCardAmountWidget = allZonesCardAmountWidget = new AllZonesCardAmountWidget(this, deckStateManager, cardSizeSlider, _rootCard);
new AllZonesCardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, _rootCard);
allZonesCardAmountWidget->raise(); // Ensure it's on top of the picture allZonesCardAmountWidget->raise(); // Ensure it's on top of the picture
// Set initial visibility based on amounts // Set initial visibility based on amounts

View file

@ -20,8 +20,7 @@ class PrintingSelectorCardOverlayWidget : public QWidget
public: public:
explicit PrintingSelectorCardOverlayWidget(QWidget *parent, explicit PrintingSelectorCardOverlayWidget(QWidget *parent,
AbstractTabDeckEditor *_deckEditor, AbstractTabDeckEditor *_deckEditor,
DeckListModel *_deckModel, DeckStateManager *_deckStateManager,
QTreeView *_deckView,
QSlider *_cardSizeSlider, QSlider *_cardSizeSlider,
const ExactCard &_rootCard); const ExactCard &_rootCard);
@ -48,9 +47,6 @@ private:
AllZonesCardAmountWidget *allZonesCardAmountWidget; AllZonesCardAmountWidget *allZonesCardAmountWidget;
QLabel *pinBadge = nullptr; QLabel *pinBadge = nullptr;
AbstractTabDeckEditor *deckEditor; AbstractTabDeckEditor *deckEditor;
DeckListModel *deckModel;
QTreeView *deckView;
QSlider *cardSizeSlider;
ExactCard rootCard; ExactCard rootCard;
}; };

View file

@ -11,7 +11,9 @@
* *
* @param parent The parent PrintingSelector widget responsible for managing card selection. * @param parent The parent PrintingSelector widget responsible for managing card selection.
*/ */
PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(PrintingSelector *parent) : parent(parent) PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(PrintingSelector *parent,
DeckStateManager *deckStateManager)
: parent(parent), deckStateManager(deckStateManager)
{ {
cardSelectionBarLayout = new QHBoxLayout(this); cardSelectionBarLayout = new QHBoxLayout(this);
cardSelectionBarLayout->setContentsMargins(9, 0, 9, 0); cardSelectionBarLayout->setContentsMargins(9, 0, 9, 0);
@ -42,18 +44,12 @@ PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(Printin
*/ */
void PrintingSelectorCardSelectionWidget::connectSignals() void PrintingSelectorCardSelectionWidget::connectSignals()
{ {
connect(previousCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectPreviousCard); connect(previousCardButton, &QPushButton::clicked, parent, &PrintingSelector::prevCardRequested);
connect(nextCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectNextCard); connect(nextCardButton, &QPushButton::clicked, parent, &PrintingSelector::nextCardRequested);
} }
void PrintingSelectorCardSelectionWidget::selectSetForCards() void PrintingSelectorCardSelectionWidget::selectSetForCards()
{ {
auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel()); auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, deckStateManager);
connect(setSelectionDialog, &DlgSelectSetForCards::deckAboutToBeModified, parent->getDeckEditor(), setSelectionDialog->exec();
&AbstractTabDeckEditor::onDeckHistorySaveRequested);
connect(setSelectionDialog, &DlgSelectSetForCards::deckModified, parent->getDeckEditor(),
&AbstractTabDeckEditor::onDeckModified);
if (!setSelectionDialog->exec()) {
return;
}
} }

View file

@ -18,7 +18,7 @@ class PrintingSelectorCardSelectionWidget : public QWidget
Q_OBJECT Q_OBJECT
public: public:
explicit PrintingSelectorCardSelectionWidget(PrintingSelector *parent); explicit PrintingSelectorCardSelectionWidget(PrintingSelector *parent, DeckStateManager *deckStateManager);
void connectSignals(); void connectSignals();
@ -27,6 +27,7 @@ public slots:
private: private:
PrintingSelector *parent; PrintingSelector *parent;
DeckStateManager *deckStateManager;
QHBoxLayout *cardSelectionBarLayout; QHBoxLayout *cardSelectionBarLayout;
QPushButton *previousCardButton; QPushButton *previousCardButton;
QPushButton *selectSetForCardsButton; QPushButton *selectSetForCardsButton;

View file

@ -183,7 +183,7 @@ QList<PrintingInfo> PrintingSelectorCardSortingWidget::prependPinnedPrintings(co
*/ */
QList<PrintingInfo> PrintingSelectorCardSortingWidget::prependPrintingsInDeck(const QList<PrintingInfo> &printings, QList<PrintingInfo> PrintingSelectorCardSortingWidget::prependPrintingsInDeck(const QList<PrintingInfo> &printings,
const CardInfoPtr &selectedCard, const CardInfoPtr &selectedCard,
DeckListModel *deckModel) const DeckListModel *deckModel)
{ {
if (!selectedCard) { if (!selectedCard) {
return {}; return {};

View file

@ -23,7 +23,7 @@ public:
QList<PrintingInfo> prependPinnedPrintings(const QList<PrintingInfo> &printings, const QString &cardName); QList<PrintingInfo> prependPinnedPrintings(const QList<PrintingInfo> &printings, const QString &cardName);
QList<PrintingInfo> prependPrintingsInDeck(const QList<PrintingInfo> &printings, QList<PrintingInfo> prependPrintingsInDeck(const QList<PrintingInfo> &printings,
const CardInfoPtr &selectedCard, const CardInfoPtr &selectedCard,
DeckListModel *deckModel); const DeckListModel *deckModel);
public slots: public slots:
void updateSortOrder(); void updateSortOrder();

View file

@ -13,7 +13,7 @@ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget
const QString &_setName, const QString &_setName,
const QString &_collectorsNumber, const QString &_collectorsNumber,
QSlider *_cardSizeSlider) QSlider *_cardSizeSlider)
: QWidget(parent) : QWidget(parent), cardSizeSlider(_cardSizeSlider)
{ {
// Set up the layout for the widget // Set up the layout for the widget
layout = new QVBoxLayout(this); layout = new QVBoxLayout(this);
@ -35,7 +35,6 @@ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget
collectorsNumber->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); collectorsNumber->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
// Store the card size slider and connect its signal to the font size adjustment slot // Store the card size slider and connect its signal to the font size adjustment slot
cardSizeSlider = _cardSizeSlider;
connect(cardSizeSlider, &QSlider::valueChanged, this, &SetNameAndCollectorsNumberDisplayWidget::adjustFontSize); connect(cardSizeSlider, &QSlider::valueChanged, this, &SetNameAndCollectorsNumberDisplayWidget::adjustFontSize);
// Add labels to the layout // Add labels to the layout

View file

@ -206,7 +206,7 @@ void ChatView::appendMessage(QString message,
defaultFormat = QTextCharFormat(); defaultFormat = QTextCharFormat();
if (!isUserMessage) { if (!isUserMessage) {
if (messageType == Event_RoomSay::ChatHistory) { if (messageType == Event_RoomSay::ChatHistory) {
defaultFormat.setForeground(Qt::gray); // FIXME : hardcoded color defaultFormat.setForeground(Qt::gray); //! \todo hardcoded color
defaultFormat.setFontWeight(QFont::Light); defaultFormat.setFontWeight(QFont::Light);
defaultFormat.setFontItalic(true); defaultFormat.setFontItalic(true);
static const QRegularExpression userNameRegex("^(\\[[^\\]]*\\]\\s)(\\S+):\\s"); static const QRegularExpression userNameRegex("^(\\[[^\\]]*\\]\\s)(\\S+):\\s");
@ -229,18 +229,14 @@ void ChatView::appendMessage(QString message,
message.remove(0, pos.relativePosition - 2); // do not remove semicolon message.remove(0, pos.relativePosition - 2); // do not remove semicolon
} }
} else { } else {
defaultFormat.setForeground(Qt::darkGreen); // FIXME : hardcoded color defaultFormat.setForeground(Qt::darkGreen); //! \todo hardcoded color
defaultFormat.setFontWeight(QFont::Bold); defaultFormat.setFontWeight(QFont::Bold);
} }
} }
cursor.setCharFormat(defaultFormat); cursor.setCharFormat(defaultFormat);
bool mentionEnabled = SettingsCache::instance().getChatMention(); bool mentionEnabled = SettingsCache::instance().getChatMention();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
highlightedWords = SettingsCache::instance().getHighlightWords().split(' ', Qt::SkipEmptyParts); highlightedWords = SettingsCache::instance().getHighlightWords().split(' ', Qt::SkipEmptyParts);
#else
highlightedWords = SettingsCache::instance().getHighlightWords().split(' ', QString::SkipEmptyParts);
#endif
// parse the message // parse the message
while (message.size()) { while (message.size()) {

View file

@ -28,9 +28,6 @@ class UserListProxy;
class UserMessagePosition class UserMessagePosition
{ {
public: public:
#if (QT_VERSION < QT_VERSION_CHECK(5, 13, 0))
UserMessagePosition() = default; // older qt versions require a default constructor to use in containers
#endif
UserMessagePosition(QTextCursor &cursor); UserMessagePosition(QTextCursor &cursor);
int relativePosition; int relativePosition;
QTextBlock block; QTextBlock block;

View file

@ -421,10 +421,8 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const
{ {
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, QTimeZone::UTC).date(); static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, QTimeZone::UTC).date();
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, Qt::UTC).date();
#else #else
static const QDate epochDate = QDateTime::fromTime_t(0, Qt::UTC).date(); static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, Qt::UTC).date();
#endif #endif
auto *model = qobject_cast<GamesModel *>(sourceModel()); auto *model = qobject_cast<GamesModel *>(sourceModel());
if (!model) if (!model)

View file

@ -12,6 +12,7 @@
#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/cache_settings.h"
#include "../client/network/interfaces/deck_stats_interface.h" #include "../client/network/interfaces/deck_stats_interface.h"
#include "../client/network/interfaces/tapped_out_interface.h" #include "../client/network/interfaces/tapped_out_interface.h"
#include "../deck_editor/deck_state_manager.h"
#include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/card_picture_loader/card_picture_loader.h"
#include "../interface/pixel_map_generator.h" #include "../interface/pixel_map_generator.h"
#include "../interface/widgets/dialogs/dlg_load_deck.h" #include "../interface/widgets/dialogs/dlg_load_deck.h"
@ -52,7 +53,7 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta
{ {
setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
historyManager = new DeckListHistoryManager(this); deckStateManager = new DeckStateManager(this);
databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this); databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this);
deckDockWidget = new DeckEditorDeckDockWidget(this); deckDockWidget = new DeckEditorDeckDockWidget(this);
@ -64,14 +65,8 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta
}); });
// Connect deck signals to this tab // Connect deck signals to this tab
connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged); connect(deckStateManager, &DeckStateManager::isModifiedChanged, this, &AbstractTabDeckEditor::onDeckModified);
connect(deckDockWidget, &DeckEditorDeckDockWidget::deckModified, this, &AbstractTabDeckEditor::onDeckModified); connect(deckDockWidget, &DeckEditorDeckDockWidget::selectedCardChanged, this, &AbstractTabDeckEditor::updateCard);
connect(deckDockWidget, &DeckEditorDeckDockWidget::requestDeckHistorySave, this,
&AbstractTabDeckEditor::onDeckHistorySaveRequested);
connect(deckDockWidget, &DeckEditorDeckDockWidget::requestDeckHistoryClear, this,
&AbstractTabDeckEditor::onDeckHistoryClearRequested);
connect(deckDockWidget, &DeckEditorDeckDockWidget::cardChanged, this, &AbstractTabDeckEditor::updateCard);
connect(this, &AbstractTabDeckEditor::decrementCard, deckDockWidget, &DeckEditorDeckDockWidget::actDecrementCard);
// Connect database display signals to this tab // Connect database display signals to this tab
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this, connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this,
@ -101,13 +96,12 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta
void AbstractTabDeckEditor::updateCard(const ExactCard &card) void AbstractTabDeckEditor::updateCard(const ExactCard &card)
{ {
cardInfoDockWidget->updateCard(card); cardInfoDockWidget->updateCard(card);
printingSelectorDockWidget->printingSelector->setCard(card.getCardPtr(), DECK_ZONE_MAIN); printingSelectorDockWidget->printingSelector->setCard(card.getCardPtr());
} }
/** @brief Placeholder: called when the deck changes. */ /** @brief Placeholder: called when the deck changes. */
void AbstractTabDeckEditor::onDeckChanged() void AbstractTabDeckEditor::onDeckChanged()
{ {
historyManager->clear();
} }
/** /**
@ -115,24 +109,8 @@ void AbstractTabDeckEditor::onDeckChanged()
*/ */
void AbstractTabDeckEditor::onDeckModified() void AbstractTabDeckEditor::onDeckModified()
{ {
setModified(!isBlankNewDeck()); deckMenu->setSaveStatus(!deckStateManager->isBlankNewDeck());
deckMenu->setSaveStatus(!isBlankNewDeck()); emit tabTextChanged(this, getTabText());
}
/**
* @brief Marks the tab as modified and updates the save menu status.
*/
void AbstractTabDeckEditor::onDeckHistorySaveRequested(const QString &modificationReason)
{
historyManager->save(deckDockWidget->getDeckList()->createMemento(modificationReason));
}
/**
* @brief Marks the tab as modified and updates the save menu status.
*/
void AbstractTabDeckEditor::onDeckHistoryClearRequested()
{
historyManager->clear();
} }
/** /**
@ -140,23 +118,9 @@ void AbstractTabDeckEditor::onDeckHistoryClearRequested()
* @param card Card to add. * @param card Card to add.
* @param zoneName Zone to add the card to. * @param zoneName Zone to add the card to.
*/ */
void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneName) void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, const QString &zoneName)
{ {
if (!card) deckStateManager->addCard(card, zoneName);
return;
if (card.getInfo().getIsToken())
zoneName = DECK_ZONE_TOKENS;
onDeckHistorySaveRequested(QString(tr("Added (%1): %2 (%3) %4"))
.arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(),
card.getPrinting().getProperty("num")));
QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName);
deckDockWidget->expandAll();
deckDockWidget->deckView->clearSelection();
deckDockWidget->deckView->setCurrentIndex(newCardIndex);
setModified(true);
databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length()); databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length());
} }
@ -184,91 +148,41 @@ void AbstractTabDeckEditor::actAddCardToSideboard(const ExactCard &card)
/** @brief Decrements a card from the main deck. */ /** @brief Decrements a card from the main deck. */
void AbstractTabDeckEditor::actDecrementCard(const ExactCard &card) void AbstractTabDeckEditor::actDecrementCard(const ExactCard &card)
{ {
emit decrementCard(card, DECK_ZONE_MAIN); deckStateManager->decrementCard(card, DECK_ZONE_MAIN);
} }
/** @brief Decrements a card from the sideboard. */ /** @brief Decrements a card from the sideboard. */
void AbstractTabDeckEditor::actDecrementCardFromSideboard(const ExactCard &card) void AbstractTabDeckEditor::actDecrementCardFromSideboard(const ExactCard &card)
{ {
emit decrementCard(card, DECK_ZONE_SIDE); deckStateManager->decrementCard(card, DECK_ZONE_SIDE);
}
/**
* @brief Swaps a card in a deck zone.
* @param card Card to swap.
* @param zoneName Zone to swap in.
*/
void AbstractTabDeckEditor::actSwapCard(const ExactCard &card, const QString &zoneName)
{
QString providerId = card.getPrinting().getUuid();
QString collectorNumber = card.getPrinting().getProperty("num");
QModelIndex foundCard = deckDockWidget->deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
if (!foundCard.isValid()) {
foundCard = deckDockWidget->deckModel->findCard(card.getName(), zoneName);
}
deckDockWidget->swapCard(foundCard);
} }
/** /**
* @brief Opens a deck in this tab. * @brief Opens a deck in this tab.
* @param deck DeckLoader object (takes ownership). * @param deck The deck
*/ */
void AbstractTabDeckEditor::openDeck(DeckLoader *deck) void AbstractTabDeckEditor::openDeck(const LoadedDeck &deck)
{ {
setDeck(deck); setDeck(deck);
if (!deck->getLastLoadInfo().fileName.isEmpty()) { if (!deck.lastLoadInfo.fileName.isEmpty()) {
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastLoadInfo().fileName); SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck.lastLoadInfo.fileName);
} }
} }
/** /**
* @brief Sets the currently active deck. * @brief Sets the currently active deck.
* @param _deck DeckLoader object. * @param _deck The deck
*/ */
void AbstractTabDeckEditor::setDeck(DeckLoader *_deck) void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck)
{ {
deckDockWidget->setDeck(_deck); deckStateManager->replaceDeck(_deck);
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList()->getCardRefList())); CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(_deck.deckList.getCardRefList()));
setModified(false);
aDeckDockVisible->setChecked(true); aDeckDockVisible->setChecked(true);
deckDockWidget->setVisible(aDeckDockVisible->isChecked()); deckDockWidget->setVisible(aDeckDockVisible->isChecked());
} }
/** @brief Returns the currently loaded deck. */
DeckLoader *AbstractTabDeckEditor::getDeckLoader() const
{
return deckDockWidget->getDeckLoader();
}
/** @brief Returns the currently loaded deck list. */
DeckList *AbstractTabDeckEditor::getDeckList() const
{
return deckDockWidget->getDeckList();
}
/**
* @brief Sets the modified state of the tab.
* @param _modified True if tab is modified, false otherwise.
*/
void AbstractTabDeckEditor::setModified(bool _modified)
{
modified = _modified;
emit tabTextChanged(this, getTabText());
}
/**
* @brief Returns true if the tab is a blank newly created deck.
*/
bool AbstractTabDeckEditor::isBlankNewDeck() const
{
DeckLoader *deck = deckDockWidget->getDeckLoader();
return !modified && deck->getDeckList()->isBlankDeck() && deck->hasNotBeenLoaded();
}
/** @brief Creates a new deck. Handles opening in new tab if needed. */ /** @brief Creates a new deck. Handles opening in new tab if needed. */
void AbstractTabDeckEditor::actNewDeck() void AbstractTabDeckEditor::actNewDeck()
{ {
@ -277,7 +191,7 @@ void AbstractTabDeckEditor::actNewDeck()
return; return;
if (deckOpenLocation == NEW_TAB) { if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(nullptr); emit openDeckEditor(LoadedDeck());
return; return;
} }
@ -287,9 +201,8 @@ void AbstractTabDeckEditor::actNewDeck()
/** @brief Clears the current deck and resets modified flag. */ /** @brief Clears the current deck and resets modified flag. */
void AbstractTabDeckEditor::cleanDeckAndResetModified() void AbstractTabDeckEditor::cleanDeckAndResetModified()
{ {
deckStateManager->clearDeck();
deckMenu->setSaveStatus(false); deckMenu->setSaveStatus(false);
deckDockWidget->cleanDeck();
setModified(false);
} }
/** /**
@ -300,13 +213,13 @@ void AbstractTabDeckEditor::cleanDeckAndResetModified()
AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank) AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank)
{ {
if (SettingsCache::instance().getOpenDeckInNewTab()) { if (SettingsCache::instance().getOpenDeckInNewTab()) {
if (openInSameTabIfBlank && isBlankNewDeck()) if (openInSameTabIfBlank && deckStateManager->isBlankNewDeck())
return SAME_TAB; return SAME_TAB;
else else
return NEW_TAB; return NEW_TAB;
} }
if (!modified) if (!deckStateManager->isModified())
return SAME_TAB; return SAME_TAB;
tabSupervisor->setCurrentWidget(this); tabSupervisor->setCurrentWidget(this);
@ -357,7 +270,6 @@ void AbstractTabDeckEditor::actLoadDeck()
QString fileName = dialog.selectedFiles().at(0); QString fileName = dialog.selectedFiles().at(0);
openDeckFromFile(fileName, deckOpenLocation); openDeckFromFile(fileName, deckOpenLocation);
deckDockWidget->updateBannerCardComboBox();
} }
/** /**
@ -382,17 +294,15 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo
{ {
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName);
auto *l = new DeckLoader(this); auto l = DeckLoader(this);
if (l->loadFromFile(fileName, fmt, true)) { if (l.loadFromFile(fileName, fmt, true)) {
if (deckOpenLocation == NEW_TAB) { if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(l); emit openDeckEditor(l.getDeck());
l->deleteLater();
} else { } else {
deckMenu->setSaveStatus(false); deckMenu->setSaveStatus(false);
openDeck(l); openDeck(l.getDeck());
} }
} else { } else {
l->deleteLater();
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName)); QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName));
} }
deckMenu->setSaveStatus(true); deckMenu->setSaveStatus(true);
@ -405,16 +315,16 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo
*/ */
bool AbstractTabDeckEditor::actSaveDeck() bool AbstractTabDeckEditor::actSaveDeck()
{ {
DeckLoader *const deck = getDeckLoader(); const auto loadedDeck = deckStateManager->toLoadedDeck();
if (deck->getLastLoadInfo().remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) { if (loadedDeck.lastLoadInfo.remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) {
QString deckString = deck->getDeckList()->writeToString_Native(); QString deckString = loadedDeck.deckList.writeToString_Native();
if (deckString.length() > MAX_FILE_LENGTH) { if (deckString.length() > MAX_FILE_LENGTH) {
QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck")); QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck"));
return false; return false;
} }
Command_DeckUpload cmd; Command_DeckUpload cmd;
cmd.set_deck_id(static_cast<google::protobuf::uint32>(deck->getLastLoadInfo().remoteDeckId)); cmd.set_deck_id(static_cast<google::protobuf::uint32>(loadedDeck.lastLoadInfo.remoteDeckId));
cmd.set_deck_list(deckString.toStdString()); cmd.set_deck_list(deckString.toStdString());
PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd);
@ -422,10 +332,14 @@ bool AbstractTabDeckEditor::actSaveDeck()
tabSupervisor->getClient()->sendCommand(pend); tabSupervisor->getClient()->sendCommand(pend);
return true; return true;
} else if (deck->getLastLoadInfo().fileName.isEmpty()) }
if (loadedDeck.lastLoadInfo.fileName.isEmpty())
return actSaveDeckAs(); return actSaveDeckAs();
else if (deck->saveToFile(deck->getLastLoadInfo().fileName, deck->getLastLoadInfo().fileFormat)) {
setModified(false); auto deckLoader = DeckLoader(this);
deckLoader.setDeck(loadedDeck);
if (deckLoader.saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) {
deckStateManager->setModified(false);
return true; return true;
} }
@ -441,12 +355,14 @@ bool AbstractTabDeckEditor::actSaveDeck()
*/ */
bool AbstractTabDeckEditor::actSaveDeckAs() bool AbstractTabDeckEditor::actSaveDeckAs()
{ {
LoadedDeck loadedDeck = deckStateManager->toLoadedDeck();
QFileDialog dialog(this, tr("Save deck")); QFileDialog dialog(this, tr("Save deck"));
dialog.setDirectory(SettingsCache::instance().getDeckPath()); dialog.setDirectory(SettingsCache::instance().getDeckPath());
dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setDefaultSuffix("cod"); dialog.setDefaultSuffix("cod");
dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS);
dialog.selectFile(getDeckList()->getName().trimmed()); dialog.selectFile(loadedDeck.deckList.getName().trimmed());
if (!dialog.exec()) if (!dialog.exec())
return false; return false;
@ -454,14 +370,18 @@ bool AbstractTabDeckEditor::actSaveDeckAs()
QString fileName = dialog.selectedFiles().at(0); QString fileName = dialog.selectedFiles().at(0);
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName);
if (!getDeckLoader()->saveToFile(fileName, fmt)) { DeckLoader deckLoader = DeckLoader(this);
deckLoader.setDeck(loadedDeck);
if (!deckLoader.saveToFile(fileName, fmt)) {
QMessageBox::critical( QMessageBox::critical(
this, tr("Error"), this, tr("Error"),
tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
return false; return false;
} }
setModified(false); deckStateManager->setLastLoadInfo({.fileName = fileName, .fileFormat = fmt});
deckStateManager->setModified(false);
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName); SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName);
return true; return true;
} }
@ -475,7 +395,7 @@ void AbstractTabDeckEditor::saveDeckRemoteFinished(const Response &response)
if (response.response_code() != Response::RespOk) if (response.response_code() != Response::RespOk)
QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.")); QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved."));
else else
setModified(false); deckStateManager->setModified(false);
} }
/** /**
@ -493,10 +413,10 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard()
return; return;
if (deckOpenLocation == NEW_TAB) { if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeckList()); emit openDeckEditor({.deckList = dlg.getDeckList()});
} else { } else {
setDeck(dlg.getDeckList()); setDeck({.deckList = dlg.getDeckList()});
setModified(true); deckStateManager->setModified(true);
} }
deckMenu->setSaveStatus(true); deckMenu->setSaveStatus(true);
@ -508,12 +428,13 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard()
*/ */
void AbstractTabDeckEditor::editDeckInClipboard(bool annotated) void AbstractTabDeckEditor::editDeckInClipboard(bool annotated)
{ {
DlgEditDeckInClipboard dlg(getDeckLoader(), annotated, this); LoadedDeck loadedDeck = deckStateManager->toLoadedDeck();
DlgEditDeckInClipboard dlg(loadedDeck.deckList, annotated, this);
if (!dlg.exec()) if (!dlg.exec())
return; return;
setDeck(dlg.getDeckList()); setDeck({dlg.getDeckList(), loadedDeck.lastLoadInfo});
setModified(true); deckStateManager->setModified(true);
deckMenu->setSaveStatus(true); deckMenu->setSaveStatus(true);
} }
@ -532,25 +453,25 @@ void AbstractTabDeckEditor::actEditDeckInClipboardRaw()
/** @brief Saves deck to clipboard with set info and annotation. */ /** @brief Saves deck to clipboard with set info and annotation. */
void AbstractTabDeckEditor::actSaveDeckToClipboard() void AbstractTabDeckEditor::actSaveDeckToClipboard()
{ {
DeckLoader::saveToClipboard(getDeckList(), true, true); DeckLoader::saveToClipboard(deckStateManager->getDeckList(), true, true);
} }
/** @brief Saves deck to clipboard with annotation, without set info. */ /** @brief Saves deck to clipboard with annotation, without set info. */
void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo() void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo()
{ {
DeckLoader::saveToClipboard(getDeckList(), true, false); DeckLoader::saveToClipboard(deckStateManager->getDeckList(), true, false);
} }
/** @brief Saves deck to clipboard without annotations, with set info. */ /** @brief Saves deck to clipboard without annotations, with set info. */
void AbstractTabDeckEditor::actSaveDeckToClipboardRaw() void AbstractTabDeckEditor::actSaveDeckToClipboardRaw()
{ {
DeckLoader::saveToClipboard(getDeckList(), false, true); DeckLoader::saveToClipboard(deckStateManager->getDeckList(), false, true);
} }
/** @brief Saves deck to clipboard without annotations or set info. */ /** @brief Saves deck to clipboard without annotations or set info. */
void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo() void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo()
{ {
DeckLoader::saveToClipboard(getDeckList(), false, false); DeckLoader::saveToClipboard(deckStateManager->getDeckList(), false, false);
} }
/** @brief Prints the deck using a QPrintPreviewDialog. */ /** @brief Prints the deck using a QPrintPreviewDialog. */
@ -558,7 +479,7 @@ void AbstractTabDeckEditor::actPrintDeck()
{ {
auto *dlg = new QPrintPreviewDialog(this); auto *dlg = new QPrintPreviewDialog(this);
connect(dlg, &QPrintPreviewDialog::paintRequested, this, connect(dlg, &QPrintPreviewDialog::paintRequested, this,
[this](QPrinter *printer) { DeckLoader::printDeckList(printer, getDeckList()); }); [this](QPrinter *printer) { DeckLoader::printDeckList(printer, deckStateManager->getDeckList()); });
dlg->exec(); dlg->exec();
} }
@ -576,10 +497,10 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite()
return; return;
if (deckOpenLocation == NEW_TAB) { if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeck()); emit openDeckEditor({.deckList = dlg.getDeck()});
} else { } else {
setDeck(dlg.getDeck()); setDeck({.deckList = dlg.getDeck()});
setModified(true); deckStateManager->setModified(true);
} }
deckMenu->setSaveStatus(true); deckMenu->setSaveStatus(true);
@ -591,8 +512,7 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite()
*/ */
void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
{ {
if (DeckLoader *const deck = getDeckLoader()) { QString decklistUrlString = DeckLoader::exportDeckToDecklist(deckStateManager->getDeckList(), website);
QString decklistUrlString = deck->exportDeckToDecklist(getDeckList(), website);
// Check to make sure the string isn't empty. // Check to make sure the string isn't empty.
if (decklistUrlString.isEmpty()) { if (decklistUrlString.isEmpty()) {
// Show an error if the deck is empty, and return. // Show an error if the deck is empty, and return.
@ -607,10 +527,6 @@ void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite
decklistUrlString = decklistUrl.toEncoded(); decklistUrlString = decklistUrl.toEncoded();
// We open the url in the user's default browser // We open the url in the user's default browser
QDesktopServices::openUrl(decklistUrlString); QDesktopServices::openUrl(decklistUrlString);
} else {
// if there's no deck loader object, return an error
QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be exported."));
}
} }
/** @brief Exports deck to www.decklist.org. */ /** @brief Exports deck to www.decklist.org. */
@ -629,14 +545,14 @@ void AbstractTabDeckEditor::actExportDeckDecklistXyz()
void AbstractTabDeckEditor::actAnalyzeDeckDeckstats() void AbstractTabDeckEditor::actAnalyzeDeckDeckstats()
{ {
auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this); auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this);
interface->analyzeDeck(getDeckList()); interface->analyzeDeck(deckStateManager->getDeckList());
} }
/** @brief Analyzes the deck using TappedOut. */ /** @brief Analyzes the deck using TappedOut. */
void AbstractTabDeckEditor::actAnalyzeDeckTappedout() void AbstractTabDeckEditor::actAnalyzeDeckTappedout()
{ {
auto *interface = new TappedOutInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this); auto *interface = new TappedOutInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this);
interface->analyzeDeck(getDeckList()); interface->analyzeDeck(deckStateManager->getDeckList());
} }
/** @brief Applies a new filter tree to the database display. */ /** @brief Applies a new filter tree to the database display. */
@ -695,7 +611,7 @@ bool AbstractTabDeckEditor::eventFilter(QObject *o, QEvent *e)
/** @brief Shows a confirmation dialog before closing. */ /** @brief Shows a confirmation dialog before closing. */
bool AbstractTabDeckEditor::confirmClose() bool AbstractTabDeckEditor::confirmClose()
{ {
if (modified) { if (deckStateManager->isModified()) {
tabSupervisor->setCurrentWidget(this); tabSupervisor->setCurrentWidget(this);
int ret = createSaveConfirmationWindow()->exec(); int ret = createSaveConfirmationWindow()->exec();
if (ret == QMessageBox::Save) if (ret == QMessageBox::Save)

View file

@ -19,6 +19,7 @@
#include <libcockatrice/deck_list/deck_list_history_manager.h> #include <libcockatrice/deck_list/deck_list_history_manager.h>
class DeckStateManager;
class CardDatabaseModel; class CardDatabaseModel;
class CardDatabaseDisplayModel; class CardDatabaseDisplayModel;
@ -77,7 +78,6 @@ class QAction;
* *
* - actAddCard(const ExactCard &card) Adds a card to the deck. * - actAddCard(const ExactCard &card) Adds a card to the deck.
* - actDecrementCard(const ExactCard &card) Removes a single instance of a card from the deck. * - actDecrementCard(const ExactCard &card) Removes a single instance of a card from the deck.
* - actSwapCard(const ExactCard &card, const QString &zone) Swaps a card between zones.
* - actRemoveCard() Removes the currently selected card from the deck. * - actRemoveCard() Removes the currently selected card from the deck.
* - actSaveDeckAs() Performs a "Save As" action for the deck. * - actSaveDeckAs() Performs a "Save As" action for the deck.
* - updateCard(const ExactCard &card) Updates the currently displayed card info in the dock. * - updateCard(const ExactCard &card) Updates the currently displayed card info in the dock.
@ -114,34 +114,17 @@ public:
virtual void retranslateUi() override = 0; virtual void retranslateUi() override = 0;
/** @brief Opens a deck in this tab. /** @brief Opens a deck in this tab.
* @param deck Pointer to a DeckLoader object. * @param deck The deck to open
*/ */
void openDeck(DeckLoader *deck); void openDeck(const LoadedDeck &deck);
/** @brief Returns the currently active deck loader. */
DeckLoader *getDeckLoader() const;
/** @brief Returns the currently active deck list. */
DeckList *getDeckList() const;
/** @brief Sets the modified state of the tab.
* @param _windowModified Whether the tab is modified.
*/
void setModified(bool _windowModified);
DeckEditorDeckDockWidget *getDeckDockWidget() const DeckEditorDeckDockWidget *getDeckDockWidget() const
{ {
return deckDockWidget; return deckDockWidget;
} }
DeckListHistoryManager *getHistoryManager() const
{
return historyManager;
}
DeckListHistoryManager *historyManager;
// UI Elements // UI Elements
DeckStateManager *deckStateManager;
DeckEditorMenu *deckMenu; ///< Menu for deck operations DeckEditorMenu *deckMenu; ///< Menu for deck operations
DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget; ///< Database dock DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget; ///< Database dock
DeckEditorCardInfoDockWidget *cardInfoDockWidget; ///< Card info dock DeckEditorCardInfoDockWidget *cardInfoDockWidget; ///< Card info dock
@ -156,14 +139,6 @@ public slots:
/** @brief Called when the deck is modified. */ /** @brief Called when the deck is modified. */
virtual void onDeckModified(); virtual void onDeckModified();
/** @brief Called when a widget is about to modify the state of the DeckList.
* @param modificationReason The reason for the state modification
*/
virtual void onDeckHistorySaveRequested(const QString &modificationReason);
/** @brief Called when a widget would like to clear the history. */
virtual void onDeckHistoryClearRequested();
/** @brief Updates the card info panel. /** @brief Updates the card info panel.
* @param card The card to display. * @param card The card to display.
*/ */
@ -198,14 +173,11 @@ public slots:
signals: signals:
/** @brief Emitted when a deck should be opened in a new editor tab. */ /** @brief Emitted when a deck should be opened in a new editor tab. */
void openDeckEditor(DeckLoader *deckLoader); void openDeckEditor(const LoadedDeck &deck);
/** @brief Emitted before the tab is closed. */ /** @brief Emitted before the tab is closed. */
void deckEditorClosing(AbstractTabDeckEditor *tab); void deckEditorClosing(AbstractTabDeckEditor *tab);
/** @brief Emitted when a card should be decremented. */
void decrementCard(const ExactCard &card, QString zoneName);
protected slots: protected slots:
/** @brief Starts a new deck in this tab. */ /** @brief Starts a new deck in this tab. */
virtual void actNewDeck(); virtual void actNewDeck();
@ -286,7 +258,7 @@ private:
/** @brief Sets the deck for this tab. /** @brief Sets the deck for this tab.
* @param _deck The deck object. * @param _deck The deck object.
*/ */
virtual void setDeck(DeckLoader *_deck); virtual void setDeck(const LoadedDeck &_deck);
/** @brief Helper for editing decks from the clipboard. */ /** @brief Helper for editing decks from the clipboard. */
void editDeckInClipboard(bool annotated); void editDeckInClipboard(bool annotated);
@ -316,14 +288,8 @@ protected:
*/ */
QMessageBox *createSaveConfirmationWindow(); QMessageBox *createSaveConfirmationWindow();
/** @brief Returns true if the tab is a blank newly created deck. */
bool isBlankNewDeck() const;
/** @brief Helper function to add a card to a specific deck zone. */ /** @brief Helper function to add a card to a specific deck zone. */
void addCardHelper(const ExactCard &card, QString zoneName); void addCardHelper(const ExactCard &card, const QString &zoneName);
/** @brief Swaps a card in the deck view. */
void actSwapCard(const ExactCard &card, const QString &zoneName);
/** @brief Opens a deck from a file. */ /** @brief Opens a deck from a file. */
virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation); virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation);
@ -334,8 +300,6 @@ protected:
QAction *aResetLayout; QAction *aResetLayout;
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating; QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating;
QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating; QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating;
bool modified = false; ///< Whether the deck/tab has unsaved changes
}; };
#endif // TAB_GENERIC_DECK_EDITOR_H #endif // TAB_GENERIC_DECK_EDITOR_H

View file

@ -0,0 +1,227 @@
#ifndef COCKATRICE_ARCHIDEKT_FORMATS_H
#define COCKATRICE_ARCHIDEKT_FORMATS_H
#include <QHash>
#include <QString>
namespace ArchidektFormats
{
enum class DeckFormat
{
Standard = 0,
Modern = 1,
Commander = 2,
Legacy = 3,
Vintage = 4,
Pauper = 5,
Custom = 6,
Frontier = 7,
FutureStandard = 8,
PennyDreadful = 9,
Commander1v1 = 10,
DualCommander = 11,
Brawl = 12,
// Values outside Archidekt range
Alchemy = 1000,
Historic = 1001,
Gladiator = 1002,
Oathbreaker = 1003,
OldSchool = 1004,
PauperCommander = 1005,
Pioneer = 1006,
PreDH = 1007,
Premodern = 1008,
StandardBrawl = 1009,
Timeless = 1010,
Unknown = 1011
};
inline static QString formatToApiName(DeckFormat format)
{
switch (format) {
case DeckFormat::Standard:
return "Standard";
case DeckFormat::Modern:
return "Modern";
case DeckFormat::Commander:
return "Commander";
case DeckFormat::Legacy:
return "Legacy";
case DeckFormat::Vintage:
return "Vintage";
case DeckFormat::Pauper:
return "Pauper";
case DeckFormat::Custom:
return "Custom";
case DeckFormat::Frontier:
return "Frontier";
case DeckFormat::FutureStandard:
return "Future Std";
case DeckFormat::PennyDreadful:
return "Penny Dreadful";
case DeckFormat::Commander1v1:
return "1v1 Commander";
case DeckFormat::DualCommander:
return "Dual Commander";
case DeckFormat::Brawl:
return "Brawl";
case DeckFormat::Alchemy:
return "Alchemy";
case DeckFormat::Historic:
return "Historic";
case DeckFormat::Gladiator:
return "Gladiator";
case DeckFormat::Oathbreaker:
return "Oathbreaker";
case DeckFormat::OldSchool:
return "Old School";
case DeckFormat::PauperCommander:
return "Pauper Commander";
case DeckFormat::Pioneer:
return "Pioneer";
case DeckFormat::PreDH:
return "PreDH";
case DeckFormat::Premodern:
return "Premodern";
case DeckFormat::StandardBrawl:
return "Standard Brawl";
case DeckFormat::Timeless:
return "Timeless";
default:
return "Unknown";
}
}
inline static DeckFormat apiNameToFormat(const QString &name)
{
const QString n = name.trimmed();
if (n.compare("Standard", Qt::CaseInsensitive) == 0)
return DeckFormat::Standard;
if (n.compare("Modern", Qt::CaseInsensitive) == 0)
return DeckFormat::Modern;
if (n.compare("Commander", Qt::CaseInsensitive) == 0)
return DeckFormat::Commander;
if (n.compare("Legacy", Qt::CaseInsensitive) == 0)
return DeckFormat::Legacy;
if (n.compare("Vintage", Qt::CaseInsensitive) == 0)
return DeckFormat::Vintage;
if (n.compare("Pauper", Qt::CaseInsensitive) == 0)
return DeckFormat::Pauper;
if (n.compare("Custom", Qt::CaseInsensitive) == 0)
return DeckFormat::Custom;
if (n.compare("Frontier", Qt::CaseInsensitive) == 0)
return DeckFormat::Frontier;
if (n.compare("Future Std", Qt::CaseInsensitive) == 0)
return DeckFormat::FutureStandard;
if (n.compare("Penny Dreadful", Qt::CaseInsensitive) == 0)
return DeckFormat::PennyDreadful;
if (n.compare("1v1 Commander", Qt::CaseInsensitive) == 0)
return DeckFormat::Commander1v1;
if (n.compare("Dual Commander", Qt::CaseInsensitive) == 0)
return DeckFormat::DualCommander;
if (n.compare("Brawl", Qt::CaseInsensitive) == 0)
return DeckFormat::Brawl;
if (n.compare("Alchemy", Qt::CaseInsensitive) == 0)
return DeckFormat::Alchemy;
if (n.compare("Historic", Qt::CaseInsensitive) == 0)
return DeckFormat::Historic;
if (n.compare("Gladiator", Qt::CaseInsensitive) == 0)
return DeckFormat::Gladiator;
if (n.compare("Oathbreaker", Qt::CaseInsensitive) == 0)
return DeckFormat::Oathbreaker;
if (n.compare("Old School", Qt::CaseInsensitive) == 0)
return DeckFormat::OldSchool;
if (n.compare("Pauper Commander", Qt::CaseInsensitive) == 0)
return DeckFormat::PauperCommander;
if (n.compare("Pioneer", Qt::CaseInsensitive) == 0)
return DeckFormat::Pioneer;
if (n.compare("PreDH", Qt::CaseInsensitive) == 0)
return DeckFormat::PreDH;
if (n.compare("Premodern", Qt::CaseInsensitive) == 0)
return DeckFormat::Premodern;
if (n.compare("Standard Brawl", Qt::CaseInsensitive) == 0)
return DeckFormat::StandardBrawl;
if (n.compare("Timeless", Qt::CaseInsensitive) == 0)
return DeckFormat::Timeless;
return DeckFormat::Unknown;
}
inline static QString formatToCockatriceName(DeckFormat format)
{
switch (format) {
case DeckFormat::Standard:
return "standard";
case DeckFormat::Modern:
return "modern";
case DeckFormat::Commander:
return "commander";
case DeckFormat::Legacy:
return "legacy";
case DeckFormat::Vintage:
return "vintage";
case DeckFormat::Pauper:
return "pauper";
case DeckFormat::Brawl:
return "brawl";
case DeckFormat::PennyDreadful:
return "penny";
case DeckFormat::FutureStandard:
return "future";
case DeckFormat::Commander1v1:
return "duel";
case DeckFormat::DualCommander:
return "duel";
case DeckFormat::Alchemy:
return "alchemy";
case DeckFormat::Historic:
return "historic";
case DeckFormat::Gladiator:
return "gladiator";
case DeckFormat::Oathbreaker:
return "oathbreaker";
case DeckFormat::OldSchool:
return "oldschool";
case DeckFormat::PauperCommander:
return "paupercommander";
case DeckFormat::Pioneer:
return "pioneer";
case DeckFormat::PreDH:
return "predh";
case DeckFormat::Premodern:
return "premodern";
case DeckFormat::StandardBrawl:
return "standardbrawl";
case DeckFormat::Timeless:
return "timeless";
default:
return {};
}
}
inline static DeckFormat cockatriceNameToFormat(const QString &apiName)
{
static const QHash<QString, DeckFormat> map = {
{"standard", DeckFormat::Standard}, {"modern", DeckFormat::Modern},
{"commander", DeckFormat::Commander}, {"legacy", DeckFormat::Legacy},
{"vintage", DeckFormat::Vintage}, {"pauper", DeckFormat::Pauper},
{"brawl", DeckFormat::Brawl}, {"penny", DeckFormat::PennyDreadful},
{"future", DeckFormat::FutureStandard}, {"duel", DeckFormat::Commander1v1},
{"alchemy", DeckFormat::Alchemy}, {"historic", DeckFormat::Historic},
{"gladiator", DeckFormat::Gladiator}, {"oathbreaker", DeckFormat::Oathbreaker},
{"oldschool", DeckFormat::OldSchool}, {"paupercommander", DeckFormat::PauperCommander},
{"pioneer", DeckFormat::Pioneer}, {"predh", DeckFormat::PreDH},
{"premodern", DeckFormat::Premodern}, {"standardbrawl", DeckFormat::StandardBrawl},
{"timeless", DeckFormat::Timeless}};
return map.value(apiName.toLower(), DeckFormat::Unknown);
}
} // namespace ArchidektFormats
#endif // COCKATRICE_ARCHIDEKT_FORMATS_H

View file

@ -21,16 +21,16 @@ void ArchidektApiResponseCard::fromJson(const QJsonObject &json)
edition.fromJson(json.value("edition").toObject()); edition.fromJson(json.value("edition").toObject());
flavor = json.value("flavor").toString(); flavor = json.value("flavor").toString();
// TODO but not really important //! \todo but not really important
// games = {""}; //! \todo games = {""};
// options = {""}; //! \todo options = {""};
scryfallImageHash = json.value("scryfallImageHash").toString(); scryfallImageHash = json.value("scryfallImageHash").toString();
oracleCard = json.value("oracleCard").toObject(); oracleCard = json.value("oracleCard").toObject();
owned = json.value("owned").toInt(); owned = json.value("owned").toInt();
pinnedStatus = json.value("pinnedStatus").toInt(); pinnedStatus = json.value("pinnedStatus").toInt();
rarity = json.value("rarity").toString(); rarity = json.value("rarity").toString();
// TODO but not really important //! \todo but not really important
// globalCategories = {""}; //! \todo globalCategories = {""};
} }
void ArchidektApiResponseCard::debugPrint() const void ArchidektApiResponseCard::debugPrint() const

View file

@ -39,6 +39,11 @@ public:
return name; return name;
}; };
int getDeckFormat() const
{
return deckFormat;
}
private: private:
int id; int id;
QString name; QString name;

View file

@ -6,6 +6,7 @@
#include "../../../../cards/card_size_widget.h" #include "../../../../cards/card_size_widget.h"
#include "../../../../cards/deck_card_zone_display_widget.h" #include "../../../../cards/deck_card_zone_display_widget.h"
#include "../../../../visual_deck_editor/visual_deck_display_options_widget.h" #include "../../../../visual_deck_editor/visual_deck_display_options_widget.h"
#include "../api_response/archidekt_formats.h"
#include "../api_response/deck/archidekt_api_response_deck.h" #include "../api_response/deck/archidekt_api_response_deck.h"
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
@ -67,11 +68,12 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
model = new DeckListModel(this); model = new DeckListModel(this);
connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset);
model->getDeckList()->loadFromStream_Plain(deckStream, false);
model->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); auto decklist = QSharedPointer<DeckList>(new DeckList);
decklist->loadFromStream_Plain(deckStream, false);
model->setDeckList(decklist);
model->rebuildTree(); model->forEachCard(CardNodeFunction::ResolveProviderId());
retranslateUi(); retranslateUi();
} }
@ -84,17 +86,19 @@ void ArchidektApiResponseDeckDisplayWidget::retranslateUi()
void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString &activeGroupCriteria) void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString &activeGroupCriteria)
{ {
model->setActiveGroupCriteria(DeckListModelGroupCriteria::fromString(activeGroupCriteria)); model->setActiveGroupCriteria(DeckListModelGroupCriteria::fromString(activeGroupCriteria));
model->sort(1, Qt::AscendingOrder); model->sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
} }
void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor() void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor()
{ {
auto loader = new DeckLoader(this); DeckList deckList(*model->getDeckList());
loader->getDeckList()->loadFromString_Native(model->getDeckList()->writeToString_Native()); deckList.setName(response.getDeckName());
deckList.setGameFormat(
ArchidektFormats::formatToCockatriceName(ArchidektFormats::DeckFormat(response.getDeckFormat() - 1)));
loader->getDeckList()->setName(response.getDeckName()); LoadedDeck loadedDeck = {deckList, {}};
emit openInDeckEditor(loader); emit openInDeckEditor(loadedDeck);
} }
void ArchidektApiResponseDeckDisplayWidget::clearAllDisplayWidgets() void ArchidektApiResponseDeckDisplayWidget::clearAllDisplayWidgets()
@ -119,7 +123,7 @@ void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListMode
QSortFilterProxyModel proxy; QSortFilterProxyModel proxy;
proxy.setSourceModel(model); proxy.setSourceModel(model);
proxy.setSortRole(Qt::EditRole); proxy.setSortRole(Qt::EditRole);
proxy.sort(1, Qt::AscendingOrder); proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder);
for (int i = 0; i < proxy.rowCount(); ++i) { for (int i = 0; i < proxy.rowCount(); ++i) {
QModelIndex proxyIndex = proxy.index(i, 0); QModelIndex proxyIndex = proxy.index(i, 0);
@ -132,10 +136,12 @@ void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListMode
continue; continue;
} }
QString zoneName =
persistent.sibling(persistent.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
DeckCardZoneDisplayWidget *zoneDisplayWidget = DeckCardZoneDisplayWidget *zoneDisplayWidget =
new DeckCardZoneDisplayWidget(zoneContainer, model, nullptr, persistent, new DeckCardZoneDisplayWidget(zoneContainer, model, nullptr, persistent, zoneName, "maintype", {"name"},
model->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), DisplayType::Overlap, 20, 10, cardSizeSlider);
"maintype", {"name"}, DisplayType::Overlap, 20, 10, cardSizeSlider);
connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, zoneDisplayWidget, connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, zoneDisplayWidget,
&DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged);

View file

@ -31,7 +31,7 @@
* *
* ### Signals * ### Signals
* - `requestNavigation(QString url)` triggered when navigation to a deck URL is requested. * - `requestNavigation(QString url)` triggered when navigation to a deck URL is requested.
* - `openInDeckEditor(DeckLoader *loader)` emitted when the user chooses to open the deck * - `openInDeckEditor(const LoadedDeck &deck)` emitted when the user chooses to open the deck
* in the deck editor. * in the deck editor.
* *
* ### Features * ### Features
@ -52,9 +52,9 @@ signals:
/** /**
* @brief Emitted when the deck should be opened in the deck editor. * @brief Emitted when the deck should be opened in the deck editor.
* @param loader Initialized DeckLoader containing the deck data. * @param deck LoadedDeck containing the deck data.
*/ */
void openInDeckEditor(DeckLoader *loader); void openInDeckEditor(const LoadedDeck &deck);
public: public:
/** /**
@ -75,7 +75,7 @@ public:
void retranslateUi(); void retranslateUi();
/** /**
* @brief Opens the deck in the deck editor via DeckLoader. * @brief Opens the deck in the deck editor.
*/ */
void actOpenInDeckEditor(); void actOpenInDeckEditor();

View file

@ -16,7 +16,7 @@
#define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg" #define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg"
QString timeAgo(const QString &timestamp) static QString timeAgo(const QString &timestamp)
{ {
QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate); QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate);

View file

@ -16,10 +16,7 @@ ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsD
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
imageNetworkManager = new QNetworkAccessManager(this); imageNetworkManager = new QNetworkAccessManager(this);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
imageNetworkManager->setTransferTimeout(); // Use Qt's default timeout imageNetworkManager->setTransferTimeout(); // Use Qt's default timeout
#endif
imageNetworkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); imageNetworkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
// Add widgets for deck listings // Add widgets for deck listings

View file

@ -27,10 +27,7 @@
TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
{ {
networkManager = new QNetworkAccessManager(this); networkManager = new QNetworkAccessManager(this);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
networkManager->setTransferTimeout(); // Use Qt's default timeout networkManager->setTransferTimeout(); // Use Qt's default timeout
#endif
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *)));

View file

@ -14,10 +14,8 @@ void EdhrecDeckApiResponse::fromJson(const QJsonArray &json)
deckList += cardlistValue.toString() + "\n"; deckList += cardlistValue.toString() + "\n";
} }
deckLoader = new DeckLoader(nullptr);
QTextStream stream(&deckList); QTextStream stream(&deckList);
deckLoader->getDeckList()->loadFromStream_Plain(stream, true); deck.loadFromStream_Plain(stream, true);
} }
void EdhrecDeckApiResponse::debugPrint() const void EdhrecDeckApiResponse::debugPrint() const

View file

@ -21,7 +21,7 @@ public:
// Debug method for logging // Debug method for logging
void debugPrint() const; void debugPrint() const;
DeckLoader *deckLoader; DeckList deck;
}; };
#endif // EDHREC_DECK_API_RESPONSE_H #endif // EDHREC_DECK_API_RESPONSE_H

View file

@ -19,17 +19,17 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi
nameLabel = new QLabel(this); nameLabel = new QLabel(this);
nameLabel->setText(toDisplay.name); nameLabel->setText(toDisplay.name);
nameLabel->setAlignment(Qt::AlignHCenter); nameLabel->setAlignment(Qt::AlignHCenter);
nameLabel->setStyleSheet("font-size: 20px; font-weight: bold");
inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay); inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay);
synergyDisplayWidget = new EdhrecApiResponseCardSynergyDisplayWidget(this, toDisplay); synergyDisplayWidget = new EdhrecApiResponseCardSynergyDisplayWidget(this, toDisplay);
layout->addWidget(nameLabel);
layout->addWidget(cardPictureWidget);
backgroundPlateWidget = new BackgroundPlateWidget(this); backgroundPlateWidget = new BackgroundPlateWidget(this);
auto plateLayout = new QVBoxLayout(backgroundPlateWidget); auto plateLayout = new QVBoxLayout(backgroundPlateWidget);
plateLayout->addWidget(nameLabel);
plateLayout->addWidget(cardPictureWidget);
plateLayout->addWidget(inclusionDisplayWidget); plateLayout->addWidget(inclusionDisplayWidget);
plateLayout->addWidget(synergyDisplayWidget); plateLayout->addWidget(synergyDisplayWidget);

View file

@ -3,6 +3,7 @@
#include "../../../../../cards/card_info_picture_widget.h" #include "../../../../../cards/card_info_picture_widget.h"
#include "../../tab_edhrec_main.h" #include "../../tab_edhrec_main.h"
#include "../card_prices/edhrec_api_response_card_prices_display_widget.h" #include "../card_prices/edhrec_api_response_card_prices_display_widget.h"
#include "edhrec_commander_api_response_bracket_navigation_widget.h"
#include <libcockatrice/card/database/card_database_manager.h> #include <libcockatrice/card/database/card_database_manager.h>
@ -12,9 +13,14 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom
QString baseUrl) QString baseUrl)
: QWidget(parent), commanderDetails(_commanderDetails) : QWidget(parent), commanderDetails(_commanderDetails)
{ {
layout = new QVBoxLayout(this); layout = new QHBoxLayout(this);
setLayout(layout); setLayout(layout);
commanderLayout = new QHBoxLayout();
commanderDetailsLayout = new QVBoxLayout();
commanderDetailsLayout->setAlignment(Qt::AlignCenter);
navigationAndPricesLayout = new QVBoxLayout();
commanderPicture = new CardInfoPictureWidget(this); commanderPicture = new CardInfoPictureWidget(this);
commanderPicture->setCard(CardDatabaseManager::query()->getCard({commanderDetails.getName()})); commanderPicture->setCard(CardDatabaseManager::query()->getCard({commanderDetails.getName()}));
@ -36,20 +42,35 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom
commanderDetails.debugPrint(); commanderDetails.debugPrint();
commanderName = new QLabel(this);
commanderName->setText(commanderDetails.getName());
commanderName->setAlignment(Qt::AlignCenter);
commanderName->setStyleSheet("font-size: 28px; font-weight: bold");
label = new QLabel(this); label = new QLabel(this);
label->setAlignment(Qt::AlignCenter); label->setAlignment(Qt::AlignCenter);
label->setStyleSheet("font-size: 16px");
salt = new QLabel(this); salt = new QLabel(this);
salt->setAlignment(Qt::AlignCenter); salt->setAlignment(Qt::AlignCenter);
salt->setStyleSheet("font-size: 16px");
cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices()); cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices());
navigationWidget = new EdhrecCommanderApiResponseNavigationWidget(this, commanderDetails, baseUrl); navigationWidget = new EdhrecCommanderApiResponseNavigationWidget(this, commanderDetails, baseUrl);
layout->addWidget(commanderPicture); commanderLayout->addWidget(commanderPicture);
layout->addWidget(label); commanderDetailsLayout->addWidget(commanderName);
layout->addWidget(salt); commanderDetailsLayout->addSpacing(1);
layout->addWidget(cardPricesDisplayWidget); commanderDetailsLayout->addWidget(label);
layout->addWidget(navigationWidget); commanderDetailsLayout->addWidget(salt);
commanderDetailsLayout->addWidget(cardPricesDisplayWidget);
commanderLayout->addLayout(commanderDetailsLayout);
navigationAndPricesLayout->addWidget(navigationWidget);
// navigationAndPricesLayout->addWidget(cardPricesDisplayWidget);
layout->addLayout(commanderLayout);
layout->addLayout(navigationAndPricesLayout);
retranslateUi(); retranslateUi();
} }

View file

@ -29,8 +29,12 @@ public:
private: private:
EdhrecCommanderApiResponseCommanderDetails commanderDetails; EdhrecCommanderApiResponseCommanderDetails commanderDetails;
QVBoxLayout *layout; QHBoxLayout *layout;
QHBoxLayout *commanderLayout;
QVBoxLayout *commanderDetailsLayout;
QVBoxLayout *navigationAndPricesLayout;
CardInfoPictureWidget *commanderPicture; CardInfoPictureWidget *commanderPicture;
QLabel *commanderName;
QLabel *label; QLabel *label;
QLabel *salt; QLabel *salt;
EdhrecApiResponseCardPricesDisplayWidget *cardPricesDisplayWidget; EdhrecApiResponseCardPricesDisplayWidget *cardPricesDisplayWidget;

View file

@ -0,0 +1,95 @@
#include "edhrec_commander_api_response_bracket_navigation_widget.h"
#include <QSet>
EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBracketNavigationWidget(
QWidget *parent,
const QString &baseUrl)
: QWidget(parent)
{
layout = new QGridLayout(this);
setLayout(layout);
gameChangerLabel = new QLabel(this);
layout->addWidget(gameChangerLabel, 1, 0, 1, 2);
for (int i = 0; i < gameChangerOptions.length(); i++) {
QString option = gameChangerOptions.at(i);
QString label = option.isEmpty() ? "All" : option.at(0).toUpper() + option.mid(1);
QPushButton *optionButton = new QPushButton(label, this);
optionButton->setMinimumHeight(84);
optionButton->setStyleSheet("font-size: 24px");
gameChangerButtons[option] = optionButton;
layout->addWidget(optionButton, 2, i);
connect(optionButton, &QPushButton::clicked, this, [=, this]() {
selectedGameChanger = option;
updateOptionButtonSelection(gameChangerButtons, option);
emit requestNavigation();
});
}
updateOptionButtonSelection(gameChangerButtons, "");
retranslateUi();
applyOptionsFromUrl(baseUrl);
}
void EdhrecCommanderApiResponseBracketNavigationWidget::retranslateUi()
{
gameChangerLabel->setText(tr("Game Changers"));
}
void EdhrecCommanderApiResponseBracketNavigationWidget::applyOptionsFromUrl(const QString &url)
{
QString cleanedUrl = url;
// Remove base and file extension
if (cleanedUrl.startsWith("https://json.edhrec.com/pages/")) {
cleanedUrl = cleanedUrl.mid(QString("https://json.edhrec.com/pages/").length());
}
if (cleanedUrl.endsWith(".json")) {
cleanedUrl.chop(5);
}
// Expecting something like: "commanders/the-ur-dragon/core/expensive"
QStringList parts = cleanedUrl.split('/', Qt::SkipEmptyParts);
if (parts.size() < 2) {
return;
}
QString commanderName = parts[1];
QString gameChangerOpt;
// Define valid sets
QSet<QString> validGameChangers = {"exhibition", "core", "upgraded", "optimized", "cedh"};
// Check remaining parts after commander
for (int i = 2; i < parts.size(); ++i) {
QString part = parts[i].toLower();
if (validGameChangers.contains(part)) {
gameChangerOpt = part;
}
}
// Validate and apply
if (!gameChangerButtons.contains(gameChangerOpt)) {
gameChangerOpt.clear();
}
selectedGameChanger = gameChangerOpt;
updateOptionButtonSelection(gameChangerButtons, selectedGameChanger);
}
void EdhrecCommanderApiResponseBracketNavigationWidget::updateOptionButtonSelection(
QMap<QString, QPushButton *> &buttons,
const QString &selectedKey)
{
for (auto it = buttons.begin(); it != buttons.end(); ++it) {
it.value()->setStyleSheet(it.key() == selectedKey
? "background-color: lightblue; font-weight: bold; font-size: 24px;"
: "font-size: 24px");
}
}

View file

@ -0,0 +1,37 @@
#ifndef COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H
#define COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H
#include <QGridLayout>
#include <QLabel>
#include <QMap>
#include <QPushButton>
#include <QWidget>
class EdhrecCommanderApiResponseBracketNavigationWidget : public QWidget
{
Q_OBJECT
public:
explicit EdhrecCommanderApiResponseBracketNavigationWidget(QWidget *parent, const QString &baseUrl);
void retranslateUi();
void applyOptionsFromUrl(const QString &url);
QString getSelectedGameChanger() const
{
return selectedGameChanger;
}
signals:
void requestNavigation();
private:
QGridLayout *layout;
QLabel *gameChangerLabel;
QStringList gameChangerOptions = {"", "exhibition", "core", "upgraded", "optimized", "cedh"};
QString selectedGameChanger;
QMap<QString, QPushButton *> gameChangerButtons;
void updateOptionButtonSelection(QMap<QString, QPushButton *> &buttons, const QString &selectedKey);
};
#endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H

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