diff --git a/.ci/compile.sh b/.ci/compile.sh index 5d2e18390..424527f96 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -122,7 +122,7 @@ if [[ $MAKE_SERVER ]]; then flags+=("-DWITH_SERVER=1") fi if [[ $MAKE_NO_CLIENT ]]; then - flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0" "-DWITH_DBCONVERTER=0") + flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0") fi if [[ $MAKE_TEST ]]; then flags+=("-DTEST=1") @@ -258,7 +258,7 @@ fi if [[ $RUNNER_OS == macOS ]]; then 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" echo "Inspecting $app..." vtool -show-build "$binary" diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 44bee4959..d87f20732 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -142,7 +142,6 @@ jobs: - distro: Ubuntu version: 22.04 package: DEB - test: skip # Running tests on all distros is superfluous - distro: Ubuntu version: 24.04 @@ -166,7 +165,7 @@ jobs: - name: Restore compiler cache (ccache) id: ccache_restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: @@ -205,7 +204,7 @@ jobs: - name: Save compiler cache (ccache) if: github.ref == 'refs/heads/master' - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: ${{env.CACHE}} key: ${{ steps.ccache_restore.outputs.cache-primary-key }} @@ -213,7 +212,7 @@ jobs: - name: Upload artifact id: upload_artifact if: matrix.package != 'skip' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ${{matrix.distro}}${{matrix.version}}-package path: ${{steps.build.outputs.path}} @@ -336,6 +335,11 @@ jobs: name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} needs: configure 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: - name: Checkout @@ -350,16 +354,20 @@ jobs: with: 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 + if: matrix.use_ccache == 1 && matrix.os == 'macOS' + run: brew install ccache + + - name: Restore compiler cache (ccache) 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: - install-type: "binary" - ccache-key-prefix: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}} - max-size: 500M - gh-token: ${{ secrets.GITHUB_TOKEN }} + path: ${{env.CCACHE_DIR}} + key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}} + restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}- - name: Restore thin Qt ${{matrix.qt_version}} libraries if: matrix.os == 'macOS' @@ -429,6 +437,15 @@ jobs: TARGET_MACOS_VERSION: ${{ matrix.override_target }} 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 if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null) env: @@ -476,7 +493,7 @@ jobs: - name: Upload artifact id: upload_artifact if: matrix.make_package - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ${{matrix.artifact_name}} path: ${{steps.build.outputs.path}} @@ -484,7 +501,7 @@ jobs: - name: Upload pdb database if: matrix.os == 'Windows' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: Windows${{matrix.target}}-debug-pdbs path: | diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml index 81d6f4e75..ed61e3b19 100644 --- a/.github/workflows/translations-pull.yml +++ b/.github/workflows/translations-pull.yml @@ -33,7 +33,7 @@ jobs: - name: Create pull request if: github.event_name != 'pull_request' id: create_pr - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: add-paths: | cockatrice/translations/*.ts diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml index d201e7ad8..777e9e6ac 100644 --- a/.github/workflows/translations-push.yml +++ b/.github/workflows/translations-push.yml @@ -57,7 +57,7 @@ jobs: - name: Create pull request if: github.event_name != 'pull_request' id: create_pr - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: add-paths: | cockatrice/cockatrice_en@source.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index affc6472d..574ccecb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,6 @@ option(WITH_SERVER "build servatrice" OFF) option(WITH_CLIENT "build cockatrice" ON) # Compile oracle option(WITH_ORACLE "build oracle" ON) -# Compile dbconverter -option(WITH_DBCONVERTER "build dbconverter" ON) # Compile tests option(TEST "build tests" OFF) # Use vcpkg regardless of OS @@ -356,11 +354,6 @@ if(WITH_ORACLE) set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) endif() -if(WITH_DBCONVERTER) - add_subdirectory(dbconverter) - set(CPACK_INSTALL_CMAKE_PROJECTS "Dbconverter;Dbconverter;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) -endif() - if(TEST) include(CTest) add_subdirectory(tests) diff --git a/Dockerfile b/Dockerfile index 80f9f69f2..d185b746a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ WORKDIR /src COPY . . 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 install diff --git a/Doxyfile b/Doxyfile index d5a3a4b7a..ecacc6b8f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1068,7 +1068,6 @@ RECURSIVE = YES EXCLUDE = build/ \ cmake/ \ - dbconverter/ \ vcpkg/ \ webclient/ diff --git a/README.md b/README.md index b1233f749..c08aee0e7 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,10 @@ Latest beta version: # Related Repositories -- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to 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-Token](https://github.com/Cockatrice/Magic-Token): File with MtG token data for 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 @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) @@ -55,15 +55,31 @@ Latest beta 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. - [Official Website](https://cockatrice.github.io) - [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) -- [Official Code Documentation](https://cockatrice.github.io/docs) - [Official Discord](https://discord.gg/3Z9yzmA) - [reddit r/Cockatrice](https://reddit.com/r/cockatrice) ->[!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. +> [!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. # Contribute +

+ Code | + Documentation | + Translation +

+ +#### Repository Activity +![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats") + +
+Kudos to all our amazing contributors ❤️ +
+ + +
+ Made with contrib.rocks +
### 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.
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. -
-Kudos to our amazing contributors ❤️ -
- - -
- Made with contrib.rocks -
- -### Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/) +### Translation [![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 Cockatrice, Oracle and Webatrice 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/).
@@ -126,8 +143,8 @@ You can then make package ``` ->[!NOTE] ->Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice) +> [!NOTE] +> Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
diff --git a/cmake/CMakeDMGSetup.script b/cmake/CMakeDMGSetup.script index fe02eea13..e9a826eb8 100644 --- a/cmake/CMakeDMGSetup.script +++ b/cmake/CMakeDMGSetup.script @@ -42,7 +42,6 @@ tell disk image_name set position of item "Cockatrice.app" to { 139, 214 } set position of item "Oracle.app" to { 139, 414 } 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 } end tell update without registering applications diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake index 769f9b463..c205ebdcf 100644 --- a/cmake/FindQtRuntime.cmake +++ b/cmake/FindQtRuntime.cmake @@ -1,12 +1,11 @@ # 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: QT5_DIR -- Hint as to where Qt5 lives on the system # Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6 # Output: SERVATRICE_QT_MODULES # Output: COCKATRICE_QT_MODULES # Output: ORACLE_QT_MODULES -# Output: DBCONVERTER_QT_MODULES # Output: TEST_QT_MODULES set(REQUIRED_QT_COMPONENTS Core) @@ -29,15 +28,12 @@ endif() if(WITH_ORACLE) set(_ORACLE_NEEDED Concurrent Network Svg Widgets) endif() -if(WITH_DBCONVERTER) - set(_DBCONVERTER_NEEDED Network Widgets) -endif() if(TEST) set(_TEST_NEEDED Widgets) endif() 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) @@ -63,7 +59,7 @@ if(Qt6_FOUND) endif() else() find_package( - Qt5 5.8.0 + Qt5 5.15.2 COMPONENTS ${REQUIRED_QT_COMPONENTS} 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" 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" DB_CONVERTER_QT_MODULES "${_DBCONVERTER_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}") # Core-only export (useful for headless libs) diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in index fbdde0b86..a0d51fa2b 100644 --- a/cmake/NSIS.template.in +++ b/cmake/NSIS.template.in @@ -213,7 +213,6 @@ ${AndIf} ${FileExists} "$INSTDIR\portable.dat" Delete "$INSTDIR\uninstall.exe" Delete "$INSTDIR\cockatrice.exe" Delete "$INSTDIR\oracle.exe" - Delete "$INSTDIR\dbconverter.exe" Delete "$INSTDIR\servatrice.exe" Delete "$INSTDIR\Qt*.dll" Delete "$INSTDIR\libmysql.dll" diff --git a/cmake/SignMacApplications.cmake b/cmake/SignMacApplications.cmake index b91e53725..c08c30c1e 100644 --- a/cmake/SignMacApplications.cmake +++ b/cmake/SignMacApplications.cmake @@ -3,7 +3,7 @@ string(LENGTH "$ENV{MACOS_CERTIFICATE_NAME}" MACOS_CERTIFICATE_NAME_LEN) 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) set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app") diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index c9fa80e6b..c6a29969f 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -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_printing_selector_dock_widget.cpp src/interface/widgets/deck_editor/deck_list_style_proxy.cpp + src/interface/widgets/deck_editor/deck_state_manager.cpp src/interface/widgets/general/background_sources.cpp src/interface/widgets/general/display/background_plate_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/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_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_entry.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/key_signals.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) diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md index 6d58ee590..c42e41c42 100644 --- a/cockatrice/resources/help/deck_search.md +++ b/cockatrice/resources/help/deck_search.md @@ -15,15 +15,18 @@ searches are case insensitive.
[n:red n:deck n:wins](#n:red n:deck n:wins) (Any deck with a name containing the words red, deck, and wins)
[n:"red deck wins"](#n:%22red deck wins%22) (Any deck with a name containing the exact phrase "red deck wins")
-
File Name:
-
[f:aggro](#f:aggro) (Any deck with a filename containing the word aggro)
-
[f:red f:deck f:wins](#f:red f:deck f:wins) (Any deck with a filename containing the words red, deck, and wins)
-
[f:"red deck wins"](#f:%22red deck wins%22) (Any deck with a filename containing the exact phrase "red deck wins")
+
File Name:
+
[fn:aggro](#fn:aggro) (Any deck with a filename containing the word aggro)
+
[fn:red fn:deck fn:wins](#fn:red fn:deck fn:wins) (Any deck with a filename containing the words red, deck, and wins)
+
[fn:"red deck wins"](#fn:%22red deck wins%22) (Any deck with a filename containing the exact phrase "red deck wins")
Relative Path (starting from the deck folder):
[p:aggro](#p:aggro) (Any deck that has "aggro" somewhere in its relative path)
[p:edh/](#p:edh/) (Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)
+
Format:
+
[f:standard](#f:standard) (Any deck with format set to standard)
+
Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):
[[plains]] (Any deck that contains at least one card with "plains" in its name)
[[t:legendary]] (Any deck that contains at least one legendary)
diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp index 5e9b874d0..0298daa6b 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp @@ -43,23 +43,23 @@ void DeckStatsInterface::queryFinished(QNetworkReply *reply) deleteLater(); } -void DeckStatsInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data) +void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data) { DeckList deckWithoutTokens; - copyDeckWithoutTokens(*deck, deckWithoutTokens); + copyDeckWithoutTokens(deck, deckWithoutTokens); QUrl params; QUrlQuery urlQuery; urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain()); - urlQuery.addQueryItem("decktitle", deck->getName()); + urlQuery.addQueryItem("decktitle", deck.getName()); 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; - getAnalyzeRequestData(deck, &data); + getAnalyzeRequestData(deck, data); QNetworkRequest request(QUrl("https://deckstats.net/index.php")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -68,7 +68,7 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck) 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) { CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.h b/cockatrice/src/client/network/interfaces/deck_stats_interface.h index fd1691a8b..7dc841027 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.h +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.h @@ -28,15 +28,15 @@ private: * closest non-token card instead. So we construct a new deck which has no * tokens. */ - void copyDeckWithoutTokens(DeckList &source, DeckList &destination); + void copyDeckWithoutTokens(const DeckList &source, DeckList &destination); private slots: void queryFinished(QNetworkReply *reply); - void getAnalyzeRequestData(DeckList *deck, QByteArray *data); + void getAnalyzeRequestData(const DeckList &deck, QByteArray &data); public: explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); - void analyzeDeck(DeckList *deck); + void analyzeDeck(const DeckList &deck); }; #endif diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp index 137c7728c..a30a7f531 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp @@ -67,24 +67,24 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply) deleteLater(); } -void TappedOutInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data) +void TappedOutInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data) { DeckList mainboard, sideboard; - copyDeckSplitMainAndSide(*deck, mainboard, sideboard); + copyDeckSplitMainAndSide(deck, mainboard, sideboard); QUrl params; QUrlQuery urlQuery; - urlQuery.addQueryItem("name", deck->getName()); + urlQuery.addQueryItem("name", deck.getName()); urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true)); urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true)); params.setQuery(urlQuery); - data->append(params.query(QUrl::EncodeReserved).toUtf8()); + data.append(params.query(QUrl::EncodeReserved).toUtf8()); } -void TappedOutInterface::analyzeDeck(DeckList *deck) +void TappedOutInterface::analyzeDeck(const DeckList &deck) { QByteArray data; - getAnalyzeRequestData(deck, &data); + getAnalyzeRequestData(deck, data); QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -93,7 +93,7 @@ void TappedOutInterface::analyzeDeck(DeckList *deck) 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) { CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.h b/cockatrice/src/client/network/interfaces/tapped_out_interface.h index 056d7fd9a..0ea9c8358 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.h +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.h @@ -30,14 +30,14 @@ private: QNetworkAccessManager *manager; CardDatabase &cardDatabase; - void copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard); + void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard); private slots: void queryFinished(QNetworkReply *reply); - void getAnalyzeRequestData(DeckList *deck, QByteArray *data); + void getAnalyzeRequestData(const DeckList &deck, QByteArray &data); public: explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); - void analyzeDeck(DeckList *deck); + void analyzeDeck(const DeckList &deck); }; #endif diff --git a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h index e0fe4967a..e19e4f4e4 100644 --- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h +++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h @@ -18,21 +18,21 @@ class IJsonDeckParser public: virtual ~IJsonDeckParser() = default; - virtual DeckLoader *parse(const QJsonObject &obj) = 0; + virtual DeckList parse(const QJsonObject &obj) = 0; }; class ArchidektJsonParser : public IJsonDeckParser { 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 deckDescription = obj.value("description").toString(); - loader->getDeckList()->setName(deckName); - loader->getDeckList()->setComments(deckDescription); + deckList.setName(deckName); + deckList.setComments(deckDescription); QString outputText; QTextStream outStream(&outputText); @@ -49,25 +49,25 @@ public: outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; } - loader->getDeckList()->loadFromStream_Plain(outStream, false); - loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); + deckList.loadFromStream_Plain(outStream, false); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); - return loader; + return deckList; } }; class MoxfieldJsonParser : public IJsonDeckParser { 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 deckDescription = obj.value("description").toString(); - loader->getDeckList()->setName(deckName); - loader->getDeckList()->setComments(deckDescription); + deckList.setName(deckName); + deckList.setComments(deckDescription); QString outputText; QTextStream outStream(&outputText); @@ -96,8 +96,8 @@ public: outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; } - loader->getDeckList()->loadFromStream_Plain(outStream, false); - loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); + deckList.loadFromStream_Plain(outStream, false); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); QJsonObject commandersObj = obj.value("commanders").toObject(); if (!commandersObj.isEmpty()) { @@ -108,12 +108,12 @@ public: QString collectorNumber = cardData.value("cn").toString(); QString providerId = cardData.value("scryfall_id").toString(); - loader->getDeckList()->setBannerCard({commanderName, providerId}); - loader->getDeckList()->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId); + deckList.setBannerCard({commanderName, providerId}); + deckList.addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId); } } - return loader; + return deckList; } }; diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 7a518df67..fde8e9b34 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -2,6 +2,7 @@ #include "../network/update/client/release_channel.h" #include "card_counter_settings.h" +#include "version_string.h" #include #include @@ -198,7 +199,13 @@ SettingsCache::SettingsCache() mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool(); - checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool(); + if (settings->contains("personal/startupUpdateCheck")) { + 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 = settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool(); startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool(); @@ -206,7 +213,15 @@ SettingsCache::SettingsCache() lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate(); notifyAboutUpdates = settings->value("personal/updatenotification", 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(); keepalive = settings->value("personal/keepalive", 3).toInt(); diff --git a/cockatrice/src/client/settings/shortcut_treeview.cpp b/cockatrice/src/client/settings/shortcut_treeview.cpp index 6b329b23d..b909d47ac 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.cpp +++ b/cockatrice/src/client/settings/shortcut_treeview.cpp @@ -150,12 +150,7 @@ void ShortcutTreeView::currentChanged(const QModelIndex ¤t, const QModelIn */ void ShortcutTreeView::updateSearchString(const QString &searchString) { -#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0) - const auto skipEmptyParts = Qt::SkipEmptyParts; -#else - const auto skipEmptyParts = QString::SkipEmptyParts; -#endif - QStringList searchWords = searchString.split(" ", skipEmptyParts); + QStringList searchWords = searchString.split(" ", Qt::SkipEmptyParts); auto escapeRegex = [](const QString &s) { return QRegularExpression::escape(s); }; std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex); diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index dfc54afe0..5cce5d323 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -13,7 +13,7 @@ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws* ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart -QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / GenericQuery +QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / GenericQuery NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart @@ -22,8 +22,9 @@ CardSearch <- '[[' CardFilterString ']]' CardFilterString <- (!']]'.)* DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String -FileNameQuery <- [Ff] ('ile' 'name'?)? [:] String +FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String PathQuery <- [Pp] 'ath'? [:] String +FormatQuery <- [Ff] 'ormat'? [:] String GenericQuery <- String @@ -118,7 +119,7 @@ static void setupParserRules() return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool { int count = 0; - auto cardNodes = deck->deckLoader->getDeckList()->getCardNodes(); + auto cardNodes = deck->deckLoader->getDeck().deckList.getCardNodes(); for (auto node : cardNodes) { auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName()); if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) { @@ -138,7 +139,7 @@ static void setupParserRules() search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { auto name = std::any_cast(sv[0]); 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(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 { auto name = std::any_cast(sv[0]); return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index 5d4a07bdf..c7876a12f 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -269,12 +269,12 @@ void DeckViewContainer::loadDeckFromFile(const QString &filePath) 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) { QMessageBox::critical(this, tr("Error"), tr("Deck is greater than maximum file size.")); @@ -308,8 +308,8 @@ void DeckViewContainer::loadFromClipboard() return; } - DeckLoader *deck = dlg.getDeckList(); - loadDeckFromDeckLoader(deck); + DeckList deck = dlg.getDeckList(); + loadDeckFromDeckList(deck); } void DeckViewContainer::loadFromWebsite() @@ -320,16 +320,15 @@ void DeckViewContainer::loadFromWebsite() return; } - DeckLoader *deck = dlg.getDeck(); - loadDeckFromDeckLoader(deck); + DeckList deck = dlg.getDeck(); + loadDeckFromDeckList(deck); } void DeckViewContainer::deckSelectFinished(const Response &r) { const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); - DeckLoader newDeck(this, new DeckList(QString::fromStdString(resp.deck()))); - CardPictureLoader::cacheCardPixmaps( - CardDatabaseManager::query()->getCards(newDeck.getDeckList()->getCardRefList())); + DeckList newDeck = DeckList(QString::fromStdString(resp.deck())); + CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(newDeck.getCardRefList())); setDeck(newDeck); switchToDeckLoadedView(); } @@ -410,8 +409,8 @@ void DeckViewContainer::setSideboardLocked(bool locked) deckView->resetSideboardPlan(); } -void DeckViewContainer::setDeck(DeckLoader &deck) +void DeckViewContainer::setDeck(const DeckList &deck) { - deckView->setDeck(*deck.getDeckList()); + deckView->setDeck(deck); switchToDeckLoadedView(); } \ No newline at end of file diff --git a/cockatrice/src/game/deckview/deck_view_container.h b/cockatrice/src/game/deckview/deck_view_container.h index 620929789..6d685cd79 100644 --- a/cockatrice/src/game/deckview/deck_view_container.h +++ b/cockatrice/src/game/deckview/deck_view_container.h @@ -85,12 +85,12 @@ public: void setReadyStart(bool ready); void readyAndUpdate(); void setSideboardLocked(bool locked); - void setDeck(DeckLoader &deck); + void setDeck(const DeckList &deck); void setVisualDeckStorageExists(bool exists); public slots: void loadDeckFromFile(const QString &filePath); - void loadDeckFromDeckLoader(DeckLoader *deck); + void loadDeckFromDeckList(const DeckList &deck); }; #endif // DECK_VIEW_CONTAINER_H diff --git a/cockatrice/src/game/dialogs/dlg_create_token.cpp b/cockatrice/src/game/dialogs/dlg_create_token.cpp index 1f6f9b08c..836c0c16a 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/game/dialogs/dlg_create_token.cpp @@ -117,11 +117,7 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa chooseTokenFromDeckRadioButton->setDisabled(true); // No tokens in deck = no need for option } else { chooseTokenFromDeckRadioButton->setChecked(true); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) cardDatabaseDisplayModel->setCardNameSet(QSet(predefinedTokens.begin(), predefinedTokens.end())); -#else - cardDatabaseDisplayModel->setCardNameSet(QSet::fromList(predefinedTokens)); -#endif } auto *tokenChooseLayout = new QVBoxLayout; @@ -223,11 +219,7 @@ void DlgCreateToken::actChooseTokenFromAll(bool checked) void DlgCreateToken::actChooseTokenFromDeck(bool checked) { if (checked) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) cardDatabaseDisplayModel->setCardNameSet(QSet(predefinedTokens.begin(), predefinedTokens.end())); -#else - cardDatabaseDisplayModel->setCardNameSet(QSet::fromList(predefinedTokens)); -#endif } } diff --git a/cockatrice/src/game/player/menu/utility_menu.cpp b/cockatrice/src/game/player/menu/utility_menu.cpp index ab14d629a..37bdcbbaf 100644 --- a/cockatrice/src/game/player/menu/utility_menu.cpp +++ b/cockatrice/src/game/player/menu/utility_menu.cpp @@ -60,13 +60,13 @@ void UtilityMenu::populatePredefinedTokensMenu() clear(); setEnabled(false); predefinedTokens.clear(); - DeckLoader *_deck = player->getDeck(); + const DeckList &deckList = player->getDeck(); - if (!_deck) { + if (deckList.isEmpty()) { return; } - auto tokenCardNodes = _deck->getDeckList()->getCardNodes({DECK_ZONE_TOKENS}); + auto tokenCardNodes = deckList.getCardNodes({DECK_ZONE_TOKENS}); if (!tokenCardNodes.isEmpty()) { setEnabled(true); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index efce40cd7..0723ae6bc 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -32,7 +32,7 @@ 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)), 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(); @@ -263,10 +263,9 @@ void Player::deleteCard(CardItem *card) } } -// TODO: Does a player need a DeckLoader? -void Player::setDeck(DeckLoader &_deck) +void Player::setDeck(const DeckList &_deck) { - deck = new DeckLoader(this, _deck.getDeckList()); + deck = _deck; emit deckChanged(); } diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 0a4fc9e69..0a03b3abe 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -9,6 +9,7 @@ #include "../../game_graphics/board/abstract_graphics_item.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/pile_zone_logic.h" #include "../zones/logic/stack_zone_logic.h" @@ -44,7 +45,6 @@ class ArrowTarget; class CardDatabase; class CardZone; class CommandContainer; -class DeckLoader; class GameCommand; class GameEvent; class PlayerInfo; @@ -66,7 +66,7 @@ class Player : public QObject Q_OBJECT signals: - void openDeckEditor(DeckLoader *deck); + void openDeckEditor(const LoadedDeck &deck); void deckChanged(); void newCardAdded(AbstractCardItem *card); void rearrangeCounters(); @@ -130,9 +130,9 @@ public: return playerMenu; } - void setDeck(DeckLoader &_deck); + void setDeck(const DeckList &_deck); - [[nodiscard]] DeckLoader *getDeck() const + [[nodiscard]] const DeckList &getDeck() const { return deck; } @@ -241,7 +241,7 @@ private: bool active; bool conceded; - DeckLoader *deck; + DeckList deck; int zoneId; QMap zones; diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 058fdb510..444eecb0d 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -218,7 +218,7 @@ void PlayerActions::actAlwaysLookAtTopCard() void PlayerActions::actOpenDeckInDeckEditor() { - emit player->openDeckEditor(player->getDeck()); + emit player->openDeckEditor({.deckList = player->getDeck()}); } void PlayerActions::actViewGraveyard() diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index 4cf814d86..331605918 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -78,14 +78,7 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event) void PlayerEventHandler::eventRollDie(const Event_RollDie &event) { if (!event.values().empty()) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QList rolls(event.values().begin(), event.values().end()); -#else - QList rolls; - for (const auto &value : event.values()) { - rolls.append(value); - } -#endif std::sort(rolls.begin(), rolls.end()); emit logRollDie(player, static_cast(event.sides()), rolls); } else if (event.value()) { diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 91abb4663..e15a466c3 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -13,12 +13,20 @@ #include #include #include +#include #include #include #include +#include #include #include +namespace +{ +constexpr qreal kTitleBarHeight = 24.0; +constexpr qreal kMinVisibleWidth = 100.0; +} // namespace + /** * @param _player the player the cards were revealed to. * @param _origZone the zone the cards were revealed from. @@ -241,33 +249,182 @@ void ZoneViewWidget::retranslateUi() pileViewCheckBox.setText(tr("pile view")); } -void ZoneViewWidget::moveEvent(QGraphicsSceneMoveEvent * /* event */) +void ZoneViewWidget::stopWindowDrag() { - if (!scene()) + if (!draggingWindow) 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) { - scenePos.setX(0); - } else { - qreal maxw = scene()->sceneRect().width() - 100; - if (scenePos.x() > maxw) - scenePos.setX(maxw); + // need to grab mouse to receive events and not miss initial movement + grabMouse(); +} + +QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const +{ + const QRectF frameRectF = windowFrameRect(); + const QRect titleBarRect(frameRectF.toRect().x(), frameRectF.toRect().y(), frameRectF.toRect().width(), + static_cast(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) { - scenePos.setY(titleBarHeight); - } else { - qreal maxh = scene()->sceneRect().height() - titleBarHeight; - if (scenePos.y() > maxh) - scenePos.setY(maxh); + // fallback: square at right end of titlebar (Windows/Linux style) + return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight); +} + +QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const +{ + QWidget *current = eventWidget; + while (current) { + if (auto *view = qobject_cast(current)) + return view; + current = current->parentWidget(); } - if (scenePos != pos()) - setPos(scenePos); + if (scene() && !scene()->views().isEmpty()) + 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(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) @@ -350,6 +507,7 @@ void ZoneViewWidget::handleScrollBarChange(int value) void ZoneViewWidget::closeEvent(QCloseEvent *event) { + stopWindowDrag(); disconnect(zone, &ZoneViewZone::closed, this, 0); // manually call zone->close in order to remove it from the origZones views zone->close(); diff --git a/cockatrice/src/game/zones/view_zone_widget.h b/cockatrice/src/game/zones/view_zone_widget.h index 272ff5560..4ed8f74d8 100644 --- a/cockatrice/src/game/zones/view_zone_widget.h +++ b/cockatrice/src/game/zones/view_zone_widget.h @@ -3,7 +3,6 @@ * @ingroup GameGraphicsZones * @brief TODO: Document this. */ - #ifndef ZONEVIEWWIDGET_H #define ZONEVIEWWIDGET_H @@ -14,6 +13,7 @@ #include #include #include +#include #include class QLabel; @@ -28,6 +28,8 @@ class ServerInfo_Card; class QGraphicsSceneMouseEvent; class QGraphicsSceneWheelEvent; class QStyleOption; +class QGraphicsView; +class QWidget; class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget { @@ -66,6 +68,33 @@ private: int extraHeight; Player *player; + bool draggingWindow = false; + QPoint dragStartScreenPos; + QPointF dragStartItemPos; + QPointer 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); signals: void closePressed(ZoneViewWidget *zv); @@ -76,7 +105,6 @@ private slots: void resizeToZoneContents(bool forceInitialHeight = false); void handleScrollBarChange(int value); void zoneDeleted(); - void moveEvent(QGraphicsSceneMoveEvent * /* event */) override; void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override; void expandWindow(); @@ -101,6 +129,10 @@ public: protected: void closeEvent(QCloseEvent *event) 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; }; diff --git a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp index 8ced8d5bc..05f4a41ab 100644 --- a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp @@ -17,12 +17,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number, font.setWeight(QFont::Bold); QFontMetrics fm(font); - double w = 1.3 * -#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) - fm.horizontalAdvance(numStr); -#else - fm.width(numStr); -#endif + double w = 1.3 * fm.horizontalAdvance(numStr); double h = fm.height() * 1.3; if (w < h) w = h; diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp index 77fddc9de..128b03c95 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp @@ -21,9 +21,7 @@ CardPictureLoaderWorker::CardPictureLoaderWorker() // 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 // Use Qt's default timeout (30s, as of 2023-02-22) -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) networkManager->setTransferTimeout(); -#endif cache = new QNetworkDiskCache(this); cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath()); cache->setMaximumCacheSize(1024L * 1024L * diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index a3f0ff619..45c3ec1ba 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -25,11 +25,7 @@ const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.d const QStringList DeckLoader::FILE_NAME_FILTERS = { 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, DeckList *_deckList) : QObject(parent), deckList(_deckList) +DeckLoader::DeckLoader(QObject *parent) : QObject(parent) { } @@ -41,17 +37,18 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm } bool result = false; + DeckList deckList = DeckList(); switch (fmt) { case DeckFileFormat::PlainText: - result = deckList->loadFromFile_Plain(&file); + result = deckList.loadFromFile_Plain(&file); break; case DeckFileFormat::Cockatrice: { - result = deckList->loadFromFile_Native(&file); + result = deckList.loadFromFile_Native(&file); qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result; if (!result) { qCInfo(DeckLoaderLog) << "Retrying as plain format"; file.seek(0); - result = deckList->loadFromFile_Plain(&file); + result = deckList.loadFromFile_Plain(&file); fmt = DeckFileFormat::PlainText; } break; @@ -62,7 +59,8 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm } if (result) { - lastLoadInfo = { + loadedDeck.deckList = deckList; + loadedDeck.lastLoadInfo = { .fileName = fileName, .fileFormat = fmt, }; @@ -86,7 +84,7 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form watcher->deleteLater(); if (result) { - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .fileName = fileName, .fileFormat = fmt, }; @@ -107,13 +105,13 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form switch (fmt) { case DeckFileFormat::PlainText: - return deckList->loadFromFile_Plain(&file); + return loadedDeck.deckList.loadFromFile_Plain(&file); case DeckFileFormat::Cockatrice: { bool result = false; - result = deckList->loadFromFile_Native(&file); + result = loadedDeck.deckList.loadFromFile_Native(&file); if (!result) { file.seek(0); - return deckList->loadFromFile_Plain(&file); + return loadedDeck.deckList.loadFromFile_Plain(&file); } return result; } @@ -129,9 +127,9 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) { - bool result = deckList->loadFromString_Native(nativeString); + bool result = loadedDeck.deckList.loadFromString_Native(nativeString); if (result) { - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .remoteDeckId = remoteDeckId, }; @@ -150,16 +148,16 @@ bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt) bool result = false; switch (fmt) { case DeckFileFormat::PlainText: - result = deckList->saveToFile_Plain(&file); + result = loadedDeck.deckList.saveToFile_Plain(&file); break; case DeckFileFormat::Cockatrice: - result = deckList->saveToFile_Native(&file); + result = loadedDeck.deckList.saveToFile_Native(&file); qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result; break; } if (result) { - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .fileName = fileName, .fileFormat = fmt, }; @@ -194,18 +192,18 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileForm // Perform file modifications switch (fmt) { case DeckFileFormat::PlainText: - result = deckList->saveToFile_Plain(&file); + result = loadedDeck.deckList.saveToFile_Plain(&file); break; case DeckFileFormat::Cockatrice: - deckList->setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); - result = deckList->saveToFile_Native(&file); + loadedDeck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); + result = loadedDeck.deckList.saveToFile_Native(&file); break; } file.close(); // Close the file to ensure changes are flushed if (result) { - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .fileName = fileName, .fileFormat = fmt, }; @@ -289,14 +287,14 @@ static QString toDecklistExportString(const QList &car * @param deckList The decklist to export * @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 QString deckString = "https://" + getDomainForWebsite(website) + "/?"; // export all cards in zone - QString mainBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_MAIN})); - QString sideBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_SIDE})); + QString mainBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_MAIN})); + QString sideBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_SIDE})); // Remove the extra return at the end of the last cards mainBoardCards.chop(3); @@ -312,7 +310,7 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi return deckString; } -void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber) +void DeckLoader::saveToClipboard(const DeckList &deckList, bool addComments, bool addSetNameAndNumber) { QString buffer; QTextStream stream(&buffer); @@ -322,7 +320,7 @@ void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, boo } bool DeckLoader::saveToStream_Plain(QTextStream &out, - const DeckList *deckList, + const DeckList &deckList, bool addComments, bool addSetNameAndNumber) { @@ -331,7 +329,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, } // loop zones - for (auto zoneNode : deckList->getZoneNodes()) { + for (auto zoneNode : deckList.getZoneNodes()) { saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber); // end of zone @@ -341,14 +339,14 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, return true; } -void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList) +void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList) { - if (!deckList->getName().isEmpty()) { - out << "// " << deckList->getName() << "\n\n"; + if (!deckList.getName().isEmpty()) { + out << "// " << deckList.getName() << "\n\n"; } - if (!deckList->getComments().isEmpty()) { - QStringList commentRows = deckList->getComments().split(QRegularExpression("\n|\r\n|\r")); + if (!deckList.getComments().isEmpty()) { + QStringList commentRows = deckList.getComments().split(QRegularExpression("\n|\r\n|\r")); for (const QString &row : commentRows) { 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 QFileInfo fileInfo(fileName); @@ -455,7 +453,7 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) switch (DeckFileFormat::getFormatFromName(fileName)) { case DeckFileFormat::PlainText: // Save in Cockatrice's native format - result = deckList->saveToFile_Native(&file); + result = loadedDeck.deckList.saveToFile_Native(&file); break; case DeckFileFormat::Cockatrice: qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; @@ -476,7 +474,7 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) } else { qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; } - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .fileName = newFileName, .fileFormat = DeckFileFormat::Cockatrice, }; @@ -485,29 +483,6 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) 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) { const int totalColumns = 2; @@ -568,7 +543,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode cursor->movePosition(QTextCursor::End); } -void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList) +void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList) { QTextDocument doc; @@ -584,14 +559,14 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList) headerCharFormat.setFontWeight(QFont::Bold); cursor.insertBlock(headerBlockFormat, headerCharFormat); - cursor.insertText(deckList->getName()); + cursor.insertText(deckList.getName()); headerCharFormat.setFontPointSize(12); cursor.insertBlock(headerBlockFormat, headerCharFormat); - cursor.insertText(deckList->getComments()); + cursor.insertText(deckList.getComments()); cursor.insertBlock(headerBlockFormat, headerCharFormat); - for (auto zoneNode : deckList->getZoneNodes()) { + for (auto zoneNode : deckList.getZoneNodes()) { cursor.insertHtml("
"); cursor.insertBlock(headerBlockFormat, headerCharFormat); diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index ae3088ff8..ec5636995 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -41,28 +41,16 @@ public: }; private: - DeckList *deckList; - LoadedDeck::LoadInfo lastLoadInfo; + LoadedDeck loadedDeck; public: DeckLoader(QObject *parent); - DeckLoader(QObject *parent, DeckList *_deckList); DeckLoader(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 { - return lastLoadInfo.isEmpty(); + return loadedDeck.lastLoadInfo.isEmpty(); } 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 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, - const DeckList *deckList, + const DeckList &deckList, bool addComments = true, bool addSetNameAndNumber = true); @@ -84,18 +72,26 @@ public: * @param printer The printer to render the decklist to. * @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: 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, const InnerDecklistNode *zoneNode, @@ -106,9 +102,6 @@ private: QList cards, bool addComments = true, bool addSetNameAndNumber = true); - - [[nodiscard]] static QString getCardZoneFromName(const QString &cardName, QString currentZoneName); - [[nodiscard]] static QString getCompleteCardName(const QString &cardName); }; #endif diff --git a/cockatrice/src/interface/deck_loader/loaded_deck.h b/cockatrice/src/interface/deck_loader/loaded_deck.h index 90f3853cf..e07cd0db6 100644 --- a/cockatrice/src/interface/deck_loader/loaded_deck.h +++ b/cockatrice/src/interface/deck_loader/loaded_deck.h @@ -30,8 +30,8 @@ struct LoadedDeck bool isEmpty() const; }; - DeckList deckList; ///< The decklist itself - LoadInfo lastLoadInfo; ///< info about where the deck was loaded from + DeckList deckList; ///< The decklist itself + LoadInfo lastLoadInfo = {}; ///< info about where the deck was loaded from bool isEmpty() const; }; diff --git a/cockatrice/src/interface/logger.cpp b/cockatrice/src/interface/logger.cpp index 37e07acec..d6df065e8 100644 --- a/cockatrice/src/interface/logger.cpp +++ b/cockatrice/src/interface/logger.cpp @@ -57,17 +57,10 @@ void Logger::openLogfileSession() return; } fileStream.setDevice(&fileHandle); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << Qt::endl; fileStream << getClientVersion() << Qt::endl; fileStream << getSystemArchitecture() << 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; } @@ -77,11 +70,7 @@ void Logger::closeLogfileSession() return; logToFileEnabled = false; -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl; -#else - fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << endl; -#endif fileHandle.close(); } @@ -103,11 +92,7 @@ void Logger::internalLog(const QString &message) std::cerr << message.toStdString() << std::endl; // Print to stdout if (logToFileEnabled) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) fileStream << message << Qt::endl; // Print to fileStream -#else - fileStream << message << endl; // Print to fileStream -#endif } } diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index cb83768f4..fdac9d0f0 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -91,8 +91,9 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex i if (indexToWidgetMap.contains(index)) { return indexToWidgetMap[index]; } - auto cardName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString(); - auto cardProviderId = deckListModel->data(index.sibling(index.row(), 4), Qt::EditRole).toString(); + auto cardName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + auto cardProviderId = + index.sibling(index.row(), DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::EditRole).toString(); auto widget = new CardInfoPictureWithTextOverlayWidget(getLayoutParent(), true); 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. 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 QModelIndex proxyParent = proxy.mapFromSource(trackedIndex); diff --git a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp index 819bc57d0..f8b581e33 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp @@ -27,7 +27,7 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *pa layout->addWidget(text, 0, Qt::AlignCenter); setLayout(layout); - setFrameStyle(QFrame::Panel | QFrame::Raised); + setFrameStyle(static_cast(QFrame::Panel) | QFrame::Raised); int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3; int pixmapWidth = static_cast(pixmapHeight / aspectRatio); diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp index fc1b9ebe2..85d210e1a 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp @@ -39,10 +39,6 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT setMouseTracking(true); } - enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(this->window()); - enlargedPixmapWidget->hide(); - connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater); - hoverTimer = new QTimer(this); hoverTimer->setSingleShot(true); connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap); @@ -277,7 +273,7 @@ void CardInfoPictureWidget::leaveEvent(QEvent *event) if (hoverToZoomEnabled) { hoverTimer->stop(); - enlargedPixmapWidget->hide(); + destroyEnlargedPixmapWidget(); } if (raiseOnEnter) { @@ -294,7 +290,7 @@ void CardInfoPictureWidget::moveEvent(QMoveEvent *event) QWidget::moveEvent(event); hoverTimer->stop(); - enlargedPixmapWidget->hide(); + destroyEnlargedPixmapWidget(); if (animation->state() == QAbstractAnimation::Running) { return; @@ -310,7 +306,7 @@ void CardInfoPictureWidget::mouseMoveEvent(QMouseEvent *event) { QWidget::mouseMoveEvent(event); - if (hoverToZoomEnabled && enlargedPixmapWidget->isVisible()) { + if (hoverToZoomEnabled && enlargedPixmapWidget && enlargedPixmapWidget->isVisible()) { const QPoint cursorPos = QCursor::pos(); const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry(); const QSize widgetSize = enlargedPixmapWidget->size(); @@ -344,7 +340,7 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event) void CardInfoPictureWidget::hideEvent(QHideEvent *event) { - enlargedPixmapWidget->hide(); + destroyEnlargedPixmapWidget(); QWidget::hideEvent(event); } @@ -444,12 +440,19 @@ QMenu *CardInfoPictureWidget::createAddToOpenDeckMenu() * If card information is available, the enlarged pixmap is loaded, positioned near the cursor, * and displayed. */ -void CardInfoPictureWidget::showEnlargedPixmap() const +void CardInfoPictureWidget::showEnlargedPixmap() { if (!exactCard) { return; } + // Lazy creation of the enlarged widget + if (!enlargedPixmapWidget) { + enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(const_cast(this)->window()); + enlargedPixmapWidget->hide(); + connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater); + } + const QSize enlargedSize(static_cast(size().width() * 2), static_cast(size().width() * aspectRatio * 2)); enlargedPixmapWidget->setCardPixmap(exactCard, enlargedSize); @@ -460,7 +463,6 @@ void CardInfoPictureWidget::showEnlargedPixmap() const int newX = cursorPos.x() + enlargedPixmapOffset; int newY = cursorPos.y() + enlargedPixmapOffset; - // Adjust if out of bounds if (newX + widgetSize.width() > screenGeometry.right()) { newX = cursorPos.x() - widgetSize.width() - enlargedPixmapOffset; } @@ -472,3 +474,11 @@ void CardInfoPictureWidget::showEnlargedPixmap() const enlargedPixmapWidget->show(); } + +void CardInfoPictureWidget::destroyEnlargedPixmapWidget() +{ + if (enlargedPixmapWidget) { + enlargedPixmapWidget->deleteLater(); + enlargedPixmapWidget = nullptr; + } +} diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h index 980d700f6..c9dbc47ab 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h @@ -63,7 +63,8 @@ protected: { return resizedPixmap; } - void showEnlargedPixmap() const; + void showEnlargedPixmap(); + void destroyEnlargedPixmapWidget(); private: ExactCard exactCard; diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp index 132964f13..4742467b1 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp @@ -82,10 +82,11 @@ void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget * void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index) { - auto categoryName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString(); if (indexToWidgetMap.contains(index)) { return; } + + auto categoryName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); if (displayType == DisplayType::Overlap) { auto *displayWidget = new OverlappedCardGroupDisplayWidget( cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria, @@ -120,7 +121,7 @@ void DeckCardZoneDisplayWidget::displayCards() QSortFilterProxyModel proxy; proxy.setSourceModel(deckListModel); 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 QModelIndex proxyParent = proxy.mapFromSource(trackedIndex); diff --git a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp index 486b3302d..b27fe274f 100644 --- a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp @@ -17,7 +17,6 @@ * @param outlineColor The color of the outline around the text. * @param fontSize The font size of the overlay text. * @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. */ diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp index a5ddf37a1..3ca18d21c 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp @@ -21,32 +21,26 @@ void DeckListStatisticsAnalyzer::update() manaCurveMap.clear(); manaDevotionMap.clear(); - auto nodes = model->getDeckList()->getCardNodes(); + QList cards = model->getCards(); - for (auto *node : nodes) { - CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName()); - if (!info) - continue; + for (const ExactCard &card : cards) { + // ---- Mana curve ---- + if (config.computeManaCurve) { + manaCurveMap[card.getInfo().getCmc().toInt()]++; + } - for (int i = 0; i < node->getNumber(); ++i) { - // ---- Mana curve ---- - if (config.computeManaCurve) { - manaCurveMap[info->getCmc().toInt()]++; - } + // ---- Mana base ---- + if (config.computeManaBase) { + auto mana = determineManaProduction(card.getInfo().getText()); + for (auto it = mana.begin(); it != mana.end(); ++it) + manaBaseMap[it.key()] += it.value(); + } - // ---- Mana base ---- - if (config.computeManaBase) { - auto mana = determineManaProduction(info->getText()); - for (auto it = mana.begin(); it != mana.end(); ++it) - manaBaseMap[it.key()] += it.value(); - } - - // ---- Devotion ---- - if (config.computeDevotion) { - auto devo = countManaSymbols(info->getManaCost()); - for (auto &d : devo) - manaDevotionMap[d.first] += d.second; - } + // ---- Devotion ---- + if (config.computeDevotion) { + auto devo = countManaSymbols(card.getInfo().getManaCost()); + for (auto &d : devo) + manaDevotionMap[d.first] += d.second; } } diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp index b4b27e48a..f5549a98c 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp @@ -134,13 +134,13 @@ void DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters() void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/) { - const QString cardName = current.sibling(current.row(), 0).data().toString(); - if (!current.isValid()) { 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)); } } @@ -172,7 +172,7 @@ ExactCard DeckEditorDatabaseDisplayWidget::currentCard() const 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); } diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index 268a13d5f..62195974b 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -1,8 +1,8 @@ #include "deck_editor_deck_dock_widget.h" #include "../../../client/settings/cache_settings.h" -#include "../../deck_loader/deck_loader.h" #include "deck_list_style_proxy.h" +#include "deck_state_manager.h" #include #include @@ -38,7 +38,7 @@ static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo) } DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent) - : QDockWidget(parent), deckEditor(parent) + : QDockWidget(parent), deckEditor(parent), deckStateManager(parent->deckStateManager) { setObjectName("deckDock"); @@ -52,19 +52,19 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent void DeckEditorDeckDockWidget::createDeckDock() { - deckModel = new DeckListModel(this); - deckModel->setObjectName("deckModel"); - connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); - - deckLoader = new DeckLoader(this, deckModel->getDeckList()); + connect(getModel(), &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); 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, &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); + connect(deckStateManager, &DeckStateManager::focusIndexChanged, this, &DeckEditorDeckDockWidget::setSelectedIndex); + connect(deckStateManager, &DeckStateManager::deckReplaced, this, + &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); + deckView = new QTreeView(); deckView->setObjectName("deckView"); deckView->setModel(proxy); @@ -76,14 +76,14 @@ void DeckEditorDeckDockWidget::createDeckDock() deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &DeckEditorDeckDockWidget::updateCard); - connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapCard); + connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapSelection); deckView->setContextMenuPolicy(Qt::CustomContextMenu); connect(deckView, &QTreeView::customContextMenuRequested, this, &DeckEditorDeckDockWidget::decklistCustomMenu); - connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapCard); - connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrement); - connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrement); + connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapSelection); + connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrementSelection); + connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrementSelection); 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::onDelete, this, &DeckEditorDeckDockWidget::actRemoveCard); @@ -97,7 +97,7 @@ void DeckEditorDeckDockWidget::createDeckDock() nameDebounceTimer = new QTimer(this); nameDebounceTimer->setSingleShot(true); 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]() { nameDebounceTimer->start(); // restart debounce timer @@ -141,7 +141,7 @@ void DeckEditorDeckDockWidget::createDeckDock() commentsDebounceTimer = new QTimer(this); commentsDebounceTimer->setSingleShot(true); 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]() { commentsDebounceTimer->start(); // restart debounce timer @@ -152,18 +152,21 @@ void DeckEditorDeckDockWidget::createDeckDock() bannerCardLabel->setText(tr("Banner Card")); bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); bannerCardComboBox = new QComboBox(this); - connect(deckModel, &DeckListModel::dataChanged, this, [this]() { + connect(getModel(), &DeckListModel::dataChanged, this, [this]() { // Delay the update to avoid race conditions QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox); }); + connect(getModel(), &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand); + connect(getModel(), &DeckListModel::modelReset, this, &DeckEditorDeckDockWidget::expandAll); + connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, - &DeckEditorDeckDockWidget::setBannerCard); + &DeckEditorDeckDockWidget::writeBannerCard); bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, {}); deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); - connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, - &DeckEditorDeckDockWidget::setTags); + connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, deckStateManager, + &DeckStateManager::setTags); activeGroupCriteriaLabel = new QLabel(this); @@ -172,16 +175,14 @@ void DeckEditorDeckDockWidget::createDeckDock() activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST); activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR); connect(activeGroupCriteriaComboBox, QOverload::of(&QComboBox::currentIndexChanged), [this]() { - deckModel->setActiveGroupCriteria(static_cast( + getModel()->setActiveGroupCriteria(static_cast( activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt())); - deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - deckView->expandAll(); - deckView->expandAll(); + getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); }); aIncrement = new QAction(QString(), this); 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); tbIncrement->setDefaultAction(aIncrement); @@ -199,7 +200,7 @@ void DeckEditorDeckDockWidget::createDeckDock() aSwapCard = new QAction(QString(), this); 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); tbSwapCard->setDefaultAction(aSwapCard); @@ -283,21 +284,21 @@ void DeckEditorDeckDockWidget::createDeckDock() void DeckEditorDeckDockWidget::initializeFormats() { - QMap allFormats = CardDatabaseManager::query()->getAllFormatsWithCount(); + QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys(); formatComboBox->clear(); // Remove "Loading Database..." formatComboBox->setEnabled(true); // Populate with formats formatComboBox->addItem("", ""); - for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); ++it) { - QString displayText = QString("%1").arg(it.key()); - formatComboBox->addItem(displayText, it.key()); // store the raw key in itemData + for (auto formatName : allFormats) { + formatComboBox->addItem(formatName, formatName); // store the raw key in itemData } - if (!deckModel->getDeckList()->getGameFormat().isEmpty()) { - deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); - formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); + QString format = deckStateManager->getMetadata().gameFormat; + if (!format.isEmpty()) { + getModel()->setActiveFormat(format); + formatComboBox->setCurrentIndex(formatComboBox->findData(format)); } else { // Ensure no selection is visible initially formatComboBox->setCurrentIndex(-1); @@ -306,11 +307,10 @@ void DeckEditorDeckDockWidget::initializeFormats() connect(formatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { if (index >= 0) { QString formatKey = formatComboBox->itemData(index).toString(); - deckModel->setActiveFormat(formatKey); + deckStateManager->setFormat(formatKey); } 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(); if (!current.isValid()) return {}; - const QString cardName = current.sibling(current.row(), 1).data().toString(); - const QString cardProviderID = current.sibling(current.row(), 4).data().toString(); + const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); + const QString cardProviderID = current.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); const QModelIndex gparent = current.parent().parent(); if (!gparent.isValid()) { 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})) { return selectedCard; } @@ -340,43 +340,37 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard() void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const QModelIndex & /*previous*/) { 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(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName())); - deckModel->getDeckList()->setName(name); - deckEditor->setModified(name.isEmpty()); - emit nameChanged(); - emit deckModified(); + QString name = nameEdit->text(); + deckStateManager->setName(name); } -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)") - .arg(deckLoader->getDeckList()->getComments().size()) - .arg(commentsEdit->toPlainText().size())); - - deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); - deckEditor->setModified(commentsEdit->toPlainText().isEmpty()); - emit commentsChanged(); - emit deckModified(); + QString comments = commentsEdit->toPlainText(); + deckStateManager->setComments(comments); } void DeckEditorDeckDockWidget::updateHash() { - hashLabel->setText(deckModel->getDeckList()->getDeckHash()); - emit hashChanged(); - emit deckModified(); + hashLabel->setText(deckStateManager->getDeckHash()); } void DeckEditorDeckDockWidget::updateBannerCardComboBox() { // Store current banner card identity - CardRef wanted = deckModel->getDeckList()->getBannerCard(); + CardRef wanted = deckStateManager->getMetadata().bannerCard; // Block signals temporarily bool wasBlocked = bannerCardComboBox->blockSignals(true); @@ -386,15 +380,15 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Collect unique (name, providerId) pairs QSet> bannerCardSet; - QList cardsInDeck = deckModel->getDeckList()->getCardNodes(); + QList cardsInDeck = getModel()->getCardRefs(); - for (auto currentCard : cardsInDeck) { - if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) { + for (auto cardRef : cardsInDeck) { + if (!CardDatabaseManager::query()->getCard(cardRef)) { continue; } // Insert one entry per distinct card, ignore copies - bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()}); + bannerCardSet.insert({cardRef.name, cardRef.providerId}); } // Convert to sorted list @@ -415,7 +409,6 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Handle results if (restoreIndex != -1) { bannerCardComboBox->setCurrentIndex(restoreIndex); - syncDeckListBannerCardWithComboBox(); } else { // Add a placeholder "-" and set it as the current selection bannerCardComboBox->insertItem(0, "-"); @@ -426,25 +419,14 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() 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")); - syncDeckListBannerCardWithComboBox(); - deckEditor->setModified(true); - 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>(); - deckModel->getDeckList()->setBannerCard({name, id}); + auto [name, id] = bannerCardComboBox->itemData(index).value>(); + CardRef bannerCard = {name, id}; + deckStateManager->setBannerCard(bannerCard); } void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible) @@ -460,7 +442,7 @@ void DeckEditorDeckDockWidget::updateShowTagsWidget(const bool visible) void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() { - if (deckModel->getDeckList()->getBannerCard().name == "") { + if (deckStateManager->getMetadata().bannerCard.name == "") { if (bannerCardComboBox->findText("-") != -1) { bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-")); } else { @@ -468,37 +450,26 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() bannerCardComboBox->setCurrentIndex(0); } } else { - bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().name); + bannerCardComboBox->setCurrentText(deckStateManager->getMetadata().bannerCard.name); } } -/** - * Sets the currently active deck for this tab - * @param _deck The deck. Takes ownership of the object - */ -void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck) +void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex) { - deckLoader = _deck; - deckLoader->setParent(this); - deckModel->setDeckList(deckLoader->getDeckList()); - connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); - - emit requestDeckHistoryClear(); - historyManagerWidget->setDeckListModel(deckModel); - - syncDisplayWidgetsToModel(); - - emit deckChanged(); + deckView->clearSelection(); + deckView->setCurrentIndex(newCardIndex); + recursiveExpand(newCardIndex); + deckView->setFocus(Qt::FocusReason::MouseFocusReason); } void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() { nameEdit->blockSignals(true); - nameEdit->setText(deckModel->getDeckList()->getName()); + nameEdit->setText(deckStateManager->getMetadata().name); nameEdit->blockSignals(false); commentsEdit->blockSignals(true); - commentsEdit->setText(deckModel->getDeckList()->getComments()); + commentsEdit->setText(deckStateManager->getMetadata().comments); commentsEdit->blockSignals(false); bannerCardComboBox->blockSignals(true); @@ -507,61 +478,100 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() bannerCardComboBox->blockSignals(false); updateHash(); sortDeckModelToDeckView(); - expandAll(); - deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); + deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags); } void DeckEditorDeckDockWidget::sortDeckModelToDeckView() { - deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); - formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); - deckView->expandAll(); - deckView->expandAll(); - - emit deckChanged(); -} - -DeckLoader *DeckEditorDeckDockWidget::getDeckLoader() -{ - return deckLoader; -} - -DeckList *DeckEditorDeckDockWidget::getDeckList() -{ - return deckModel->getDeckList(); + getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); + getModel()->setActiveFormat(deckStateManager->getMetadata().gameFormat); + formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat)); } /** - * 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(); - 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()); + return deckStateManager->getModel(); } -void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) +void DeckEditorDeckDockWidget::selectPrevCard() { - if (index.parent().isValid()) - recursiveExpand(index.parent()); - deckView->expand(index); + changeSelectedCard(-1); } +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(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); + } +} + +/** + * @brief Fully expands all levels of the deck view + */ void DeckEditorDeckDockWidget::expandAll() { - deckView->expandAll(); - deckView->expandAll(); + deckView->expandRecursively(deckView->rootIndex()); } /** @@ -575,7 +585,7 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const auto selectedRows = deckView->selectionModel()->selectedRows(); 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()); @@ -583,16 +593,39 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const 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(); 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(); @@ -602,52 +635,15 @@ void DeckEditorDeckDockWidget::actSwapCard() deckView->setSelectionMode(QAbstractItemView::SingleSelection); } - bool isModified = false; for (const auto ¤tIndex : selectedRows) { - if (swapCard(currentIndex)) { - isModified = true; - } + deckStateManager->swapCardAtIndex(currentIndex); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); - if (isModified) { - emit deckModified(); - } - 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 ¤tIndex) -{ - 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) { if (!card) @@ -655,17 +651,7 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z if (card.getInfo().getIsToken()) zoneName = DECK_ZONE_TOKENS; - QString providerId = card.getPrinting().getUuid(); - 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); + deckStateManager->decrementCard(card, zoneName); } void DeckEditorDeckDockWidget::actDecrementSelection() @@ -679,7 +665,7 @@ void DeckEditorDeckDockWidget::actDecrementSelection() } for (const auto &index : selectedRows) { - offsetCountAtIndex(index, -1); + offsetCountAtIndex(index, false); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -695,58 +681,31 @@ void DeckEditorDeckDockWidget::actRemoveCard() deckView->setSelectionMode(QAbstractItemView::SingleSelection); } - bool isModified = false; - for (const auto &index : selectedRows) { - 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; + for (const auto &row : selectedRows) { + deckStateManager->removeCardAtIndex(row); } 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; } QModelIndex sourceIndex = proxy->mapToSource(idx); - const QModelIndex numberIndex = sourceIndex.sibling(sourceIndex.row(), 0); - const QModelIndex nameIndex = sourceIndex.sibling(sourceIndex.row(), 1); - - 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()); + if (isIncrement) { + deckStateManager->incrementCountAtIndex(sourceIndex); } else { - deckModel->setData(numberIndex, new_count, Qt::EditRole); + deckStateManager->decrementCountAtIndex(sourceIndex); } - - emit deckModified(); } void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 08aa38e5e..1a82b00d1 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -28,22 +28,14 @@ class DeckEditorDeckDockWidget : public QDockWidget Q_OBJECT public: explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent); - DeckLoader *deckLoader; + DeckListStyleProxy *proxy; - DeckListModel *deckModel; QTreeView *deckView; QComboBox *bannerCardComboBox; void createDeckDock(); ExactCard getCurrentCard(); void retranslateUi(); - QString getDeckName() - { - return nameEdit->text(); - } - QString getSimpleDeckName() - { - return nameEdit->text().simplified(); - } + QComboBox *getGroupByComboBox() { return activeGroupCriteriaComboBox; @@ -55,35 +47,27 @@ public: } public slots: - void cleanDeck(); + void selectPrevCard(); + void selectNextCard(); void updateBannerCardComboBox(); - void setDeck(DeckLoader *_deck); void syncDisplayWidgetsToModel(); void sortDeckModelToDeckView(); - DeckLoader *getDeckLoader(); - DeckList *getDeckList(); - void actIncrement(); - bool swapCard(const QModelIndex &idx); + void actAddCard(const ExactCard &card, const QString &zoneName); + void actIncrementSelection(); void actDecrementCard(const ExactCard &card, QString zoneName); void actDecrementSelection(); - void actSwapCard(); + void actSwapCard(const ExactCard &card, const QString &zoneName); + void actSwapSelection(); void actRemoveCard(); - void offsetCountAtIndex(const QModelIndex &idx, int offset); void initializeFormats(); - void expandAll(); signals: - void nameChanged(); - void commentsChanged(); - void hashChanged(); - void deckChanged(); - void deckModified(); - void requestDeckHistorySave(const QString &modificationReason); - void requestDeckHistoryClear(); - void cardChanged(const ExactCard &_card); + void selectedCardChanged(const ExactCard &card); private: AbstractTabDeckEditor *deckEditor; + DeckStateManager *deckStateManager; + DeckListHistoryManagerWidget *historyManagerWidget; KeySignals deckViewKeySignals; QLabel *nameLabel; @@ -106,22 +90,25 @@ private: QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; - void recursiveExpand(const QModelIndex &index); + DeckListModel *getModel() const; [[nodiscard]] QModelIndexList getSelectedCardNodes() const; + void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement); private slots: void decklistCustomMenu(QPoint point); void updateCard(QModelIndex, const QModelIndex ¤t); - void updateName(const QString &name); - void updateComments(); - void setBannerCard(int); - void setTags(const QStringList &tags); - void syncDeckListBannerCardWithComboBox(); + void writeName(); + void writeComments(); + void writeBannerCard(int); + void setSelectedIndex(const QModelIndex &newCardIndex); void updateHash(); void refreshShortcuts(); void updateShowBannerCardComboBox(bool visible); void updateShowTagsWidget(bool visible); void syncBannerCardComboBoxSelectionWithDeck(); + void changeSelectedCard(int changeBy); + void recursiveExpand(const QModelIndex &parent); + void expandAll(); }; #endif // DECK_EDITOR_DECK_DOCK_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp index 0c068e043..03760c22d 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp @@ -33,6 +33,10 @@ void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock() installEventFilter(deckEditor); 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() diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp index c68934ea6..d2d748753 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp @@ -1,10 +1,11 @@ #include "deck_list_history_manager_widget.h" -DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckListModel, +#include "deck_state_manager.h" + +DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_deckStateManager, DeckListStyleProxy *_styleProxy, - DeckListHistoryManager *manager, QWidget *parent) - : QWidget(parent), deckListModel(_deckListModel), styleProxy(_styleProxy), historyManager(manager) + : QWidget(parent), deckStateManager(_deckStateManager), styleProxy(_styleProxy) { layout = new QHBoxLayout(this); @@ -43,8 +44,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckL connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked); - connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, - &DeckListHistoryManagerWidget::refreshList); + connect(deckStateManager, &DeckStateManager::historyChanged, this, &DeckListHistoryManagerWidget::refreshList); refreshList(); retranslateUi(); @@ -58,15 +58,12 @@ void DeckListHistoryManagerWidget::retranslateUi() 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() { historyList->clear(); + DeckListHistoryManager *historyManager = deckStateManager->getHistoryManager(); + // Fill redo section first (oldest redo at top, newest redo closest to divider) const auto redoStack = historyManager->getRedoStack(); for (int i = 0; i < redoStack.size(); ++i) { // iterate forward @@ -98,36 +95,7 @@ void DeckListHistoryManagerWidget::refreshList() redoButton->setEnabled(historyManager->canRedo()); } -void DeckListHistoryManagerWidget::doUndo() -{ - 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) +void DeckListHistoryManagerWidget::onListClicked(const QListWidgetItem *item) { // Ignore non-selectable items (like divider) if (!(item->flags() & Qt::ItemIsSelectable)) { @@ -138,23 +106,24 @@ void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item) int index = item->data(Qt::UserRole + 1).toInt(); if (mode == "redo") { - const auto redoStack = historyManager->getRedoStack(); + const auto redoStack = deckStateManager->getHistoryManager()->getRedoStack(); int steps = redoStack.size() - index; - for (int i = 0; i < steps; ++i) { - historyManager->redo(deckListModel->getDeckList()); - } + deckStateManager->redo(steps); } else if (mode == "undo") { - const auto undoStack = historyManager->getUndoStack(); - int steps = undoStack.size() - 1 - index; - for (int i = 0; i < steps + 1; ++i) { - historyManager->undo(deckListModel->getDeckList()); - } + const auto undoStack = deckStateManager->getHistoryManager()->getUndoStack(); + int steps = undoStack.size() - index; + deckStateManager->undo(steps); } - deckListModel->rebuildTree(); - - emit deckListModel->layoutChanged(); - emit requestDisplayWidgetSync(); - refreshList(); +} + +void DeckListHistoryManagerWidget::doUndo() +{ + deckStateManager->undo(); +} + +void DeckListHistoryManagerWidget::doRedo() +{ + deckStateManager->redo(); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h index 0e208ad2b..ab53912e2 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h @@ -14,6 +14,8 @@ #include #include +class DeckStateManager; + class DeckListHistoryManagerWidget : public QWidget { Q_OBJECT @@ -25,22 +27,19 @@ public slots: void retranslateUi(); public: - explicit DeckListHistoryManagerWidget(DeckListModel *deckListModel, + explicit DeckListHistoryManagerWidget(DeckStateManager *deckStateManager, DeckListStyleProxy *styleProxy, - DeckListHistoryManager *manager, QWidget *parent = nullptr); - void setDeckListModel(DeckListModel *_deckListModel); private slots: void refreshList(); - void onListClicked(QListWidgetItem *item); + void onListClicked(const QListWidgetItem *item); void doUndo(); void doRedo(); private: - DeckListModel *deckListModel; + DeckStateManager *deckStateManager; DeckListStyleProxy *styleProxy; - DeckListHistoryManager *historyManager; QHBoxLayout *layout; QAction *aUndo; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp new file mode 100644 index 000000000..628d33d51 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -0,0 +1,361 @@ +#include "deck_state_manager.h" + +#include +#include + +DeckStateManager::DeckStateManager(QObject *parent) + : QObject(parent), deckList(QSharedPointer(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(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 &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 &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(); +} diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h new file mode 100644 index 000000000..0f3ba3255 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h @@ -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 +#include + +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; + 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 &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 &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 \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp index 998dafb66..e3bbc7435 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp @@ -59,7 +59,7 @@ DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent) void DlgEditPassword::actOk() { - // TODO this stuff should be using qvalidators + //! \todo this stuff should be using qvalidators if (newPasswordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Error"), tr("Your password is too short.")); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp index e0d855d7e..c9c41722e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp @@ -121,7 +121,7 @@ void DlgForgotPasswordReset::actOk() return; } - // TODO this stuff should be using qvalidators + //! \todo this stuff should be using qvalidators if (newpasswordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Error"), tr("Your password is too short.")); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp index 952364e37..b72130c81 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp @@ -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 */ -bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const +bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckList &deckList) const { QString buffer = contentsEdit->toPlainText(); if (buffer.contains("")) { - return deckLoader->getDeckList()->loadFromString_Native(buffer); + return deckList.loadFromString_Native(buffer); } QTextStream stream(&buffer); - if (deckLoader->getDeckList()->loadFromStream_Plain(stream, true)) { + if (deckList.loadFromStream_Plain(stream, true)) { if (loadSetNameAndNumberCheckBox->isChecked()) { - deckLoader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); } else { - deckLoader->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); + deckList.forEachCard(CardNodeFunction::ClearPrintingData()); } return true; } @@ -108,7 +108,7 @@ void AbstractDlgDeckTextEdit::keyPressEvent(QKeyEvent *event) * * @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")); @@ -122,8 +122,6 @@ void DlgLoadDeckFromClipboard::actRefresh() void DlgLoadDeckFromClipboard::actOK() { - deckList = new DeckLoader(this); - if (loadIntoDeck(deckList)) { accept(); } else { @@ -134,18 +132,15 @@ void DlgLoadDeckFromClipboard::actOK() /** * 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 parent The parent widget */ -DlgEditDeckInClipboard::DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent) - : AbstractDlgDeckTextEdit(parent), annotated(_annotated) +DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckList &_deckList, bool _annotated, QWidget *parent) + : AbstractDlgDeckTextEdit(parent), deckList(_deckList), annotated(_annotated) { setWindowTitle(tr("Edit deck in clipboard")); - deckLoader = new DeckLoader(this, _deckLoader->getDeckList()); - deckLoader->setParent(this); - DlgEditDeckInClipboard::actRefresh(); } @@ -155,7 +150,7 @@ DlgEditDeckInClipboard::DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _an * @param addComments Whether to add annotations * @return A QString */ -static QString deckListToString(const DeckList *deckList, bool addComments) +static QString deckListToString(const DeckList &deckList, bool addComments) { QString buffer; QTextStream stream(&buffer); @@ -165,12 +160,12 @@ static QString deckListToString(const DeckList *deckList, bool addComments) void DlgEditDeckInClipboard::actRefresh() { - setText(deckListToString(deckLoader->getDeckList(), annotated)); + setText(deckListToString(deckList, annotated)); } void DlgEditDeckInClipboard::actOK() { - if (loadIntoDeck(deckLoader)) { + if (loadIntoDeck(deckList)) { accept(); } else { QMessageBox::critical(this, tr("Error"), tr("Invalid deck list.")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h index 982840228..0e59653ea 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h @@ -8,10 +8,11 @@ #ifndef DLG_LOAD_DECK_FROM_CLIPBOARD_H #define DLG_LOAD_DECK_FROM_CLIPBOARD_H +#include "../../deck_loader/loaded_deck.h" + #include #include -class DeckLoader; class QPlainTextEdit; class QPushButton; @@ -35,15 +36,13 @@ public: /** * 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 - * to use it, since otherwise it will get destroyed once this dlg is destroyed - * @return The DeckLoader + * @return The loaded decklist */ - [[nodiscard]] virtual DeckLoader *getDeckList() const = 0; + [[nodiscard]] virtual const DeckList &getDeckList() = 0; protected: void setText(const QString &text); - bool loadIntoDeck(DeckLoader *deckLoader) const; + bool loadIntoDeck(DeckList &deckList) const; void keyPressEvent(QKeyEvent *event) override; protected slots: @@ -62,12 +61,12 @@ protected slots: void actRefresh() override; private: - DeckLoader *deckList; + DeckList deckList; public: explicit DlgLoadDeckFromClipboard(QWidget *parent = nullptr); - [[nodiscard]] DeckLoader *getDeckList() const override + [[nodiscard]] const DeckList &getDeckList() override { return deckList; } @@ -84,15 +83,15 @@ protected slots: void actRefresh() override; private: - DeckLoader *deckLoader; + DeckList deckList; bool annotated; 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; } }; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp index 2b63af1a4..da4135246 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp @@ -97,11 +97,11 @@ void DlgLoadDeckFromWebsite::accept() } // Parse the plain text deck here - DeckLoader *loader = new DeckLoader(this); + DeckList deckList; QTextStream stream(&deckText); - loader->getDeckList()->loadFromStream_Plain(stream, false); - loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); - deck = loader; + deckList.loadFromStream_Plain(stream, false); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); + deck = deckList; QDialog::accept(); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h index 84adb760f..fe6f0a7e9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h @@ -26,9 +26,9 @@ public: explicit DlgLoadDeckFromWebsite(QWidget *parent); void retranslateUi(); bool testValidUrl(); - DeckLoader *deck; + DeckList deck; - DeckLoader *getDeck() + const DeckList &getDeck() const { return deck; } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp index 4be4cdf78..a3f232d9b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp @@ -356,7 +356,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) void DlgRegister::actOk() { - // TODO this stuff should be using qvalidators + //! \todo this stuff should be using qvalidators if (passwordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Registration Warning"), tr("Your password is too short.")); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp index 2bfe650c3..6ed6b67a4 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp @@ -2,6 +2,7 @@ #include "../../deck_loader/card_node_function.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/general/layout_containers/flow_widget.h" @@ -21,7 +22,8 @@ #include #include -DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckListModel *_model) : QDialog(parent), model(_model) +DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManger) + : QDialog(parent), deckStateManager(deckStateManger) { setMinimumSize(500, 500); setAcceptDrops(true); @@ -143,52 +145,61 @@ void DlgSelectSetForCards::retranslateUi() 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() { QMap modifiedSetsAndCardsMap = getModifiedCards(); if (modifiedSetsAndCardsMap.isEmpty()) { accept(); // Nothing to do - } else { - emit deckAboutToBeModified(tr("Bulk modified printings.")); + return; } - for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { - for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { - QModelIndex find_card = model->findCard(card, DECK_ZONE_MAIN); - 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); + auto bulkModify = [&modifiedSetsAndCardsMap](DeckListModel *model) { + for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { + for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { + swapPrinting(model, modifiedSet, card); } } - } - if (!modifiedSetsAndCardsMap.isEmpty()) { - emit deckModified(); - } + return true; + }; + + deckStateManager->modifyDeck(tr("Bulk modified printings."), bulkModify); + accept(); } void DlgSelectSetForCards::actClear() { - emit deckAboutToBeModified(tr("Cleared all printing information.")); - model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); - emit deckModified(); + deckStateManager->modifyDeck(tr("Cleared all printing information."), [](auto model) { + model->forEachCard(CardNodeFunction::ClearPrintingData()); + return true; + }); accept(); } void DlgSelectSetForCards::actSetAllToPreferred() { - emit deckAboutToBeModified(tr("Set all printings to preferred.")); - model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); - model->getDeckList()->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); - emit deckModified(); + deckStateManager->modifyDeck(tr("Set all printings to preferred."), [](auto model) { + model->forEachCard(CardNodeFunction::ClearPrintingData()); + model->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); + return true; + }); accept(); } @@ -221,17 +232,11 @@ void DlgSelectSetForCards::sortSetsByCount() QMap DlgSelectSetForCards::getSetsForCards() { QMap setCounts; - if (!model) - return setCounts; - DeckList *decklist = model->getDeckList(); - if (!decklist) - return setCounts; + QList cardNames = deckStateManager->getModel()->getCardNames(); - QList cardsInDeck = decklist->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); + for (auto cardName : cardNames) { + CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); if (!infoPtr) continue; @@ -267,19 +272,15 @@ void DlgSelectSetForCards::updateCardLists() } } - DeckList *decklist = model->getDeckList(); - if (!decklist) - return; + QList cardNames = deckStateManager->getModel()->getCardNames(); - QList cardsInDeck = decklist->getCardNodes(); - - for (auto currentCard : cardsInDeck) { + for (auto cardName : cardNames) { bool found = false; QString foundSetName; // Check across all sets if the card is present for (auto it = selectedCardsBySet.begin(); it != selectedCardsBySet.end(); ++it) { - if (it.value().contains(currentCard->getName())) { + if (it.value().contains(cardName)) { found = true; foundSetName = it.key(); // Store the set name where it was found break; // Stop at the first match @@ -288,16 +289,16 @@ void DlgSelectSetForCards::updateCardLists() if (!found) { // 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); picture_widget->setCard(card); uneditedCardsFlowWidget->addWidget(picture_widget); } else { - ExactCard card = CardDatabaseManager::query()->getCard( - {currentCard->getName(), CardDatabaseManager::getInstance() - ->query() - ->getSpecificPrinting(currentCard->getName(), foundSetName, "") - .getUuid()}); + ExactCard card = + CardDatabaseManager::query()->getCard({cardName, CardDatabaseManager::getInstance() + ->query() + ->getSpecificPrinting(cardName, foundSetName, "") + .getUuid()}); CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(modifiedCardsFlowWidget); picture_widget->setCard(card); modifiedCardsFlowWidget->addWidget(picture_widget); @@ -353,23 +354,17 @@ void DlgSelectSetForCards::dropEvent(QDropEvent *event) QMap DlgSelectSetForCards::getCardsForSets() { QMap setCards; - if (!model) - return setCards; - DeckList *decklist = model->getDeckList(); - if (!decklist) - return setCards; + QList cardNames = deckStateManager->getModel()->getCardNames(); - QList cardsInDeck = decklist->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); + for (auto cardName : cardNames) { + CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); if (!infoPtr) continue; SetToPrintingsMap setMap = infoPtr->getSets(); for (auto it = setMap.begin(); it != setMap.end(); ++it) { - setCards[it.key()].append(currentCard->getName()); + setCards[it.key()].append(cardName); } } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h index 5cdef5a30..795366b57 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h @@ -18,6 +18,7 @@ #include #include +class DeckStateManager; class SetEntryWidget; // Forward declaration class DlgSelectSetForCards : public QDialog @@ -25,7 +26,7 @@ class DlgSelectSetForCards : public QDialog Q_OBJECT public: - explicit DlgSelectSetForCards(QWidget *parent, DeckListModel *_model); + explicit DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManager); void retranslateUi(); void sortSetsByCount(); QMap getCardsForSets(); @@ -37,7 +38,6 @@ public: signals: void widgetOrderChanged(); void orderChanged(); - void deckAboutToBeModified(const QString &reason); void deckModified(); public slots: @@ -61,7 +61,7 @@ private: QLabel *modifiedCardsLabel; QWidget *listContainer; QListWidget *listWidget; - DeckListModel *model; + DeckStateManager *deckStateManager; QMap setEntries; QPushButton *clearButton; QPushButton *setAllToPreferredButton; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index c0b80c6c1..191cc5d0b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -1913,7 +1913,7 @@ void DlgSettings::closeEvent(QCloseEvent *event) } if (!QDir(SettingsCache::instance().getDeckPath()).exists() || SettingsCache::instance().getDeckPath().isEmpty()) { - // TODO: Prompt to create it + //! \todo Prompt to create it if (QMessageBox::critical( this, tr("Error"), 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()) { - // TODO: Prompt to create it + //! \todo Prompt to create it if (QMessageBox::critical(this, tr("Error"), tr("The path to your card pictures directory is invalid. Would you like to go back " "and set the correct path?"), diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 7aa6b40c7..f35f90256 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -20,10 +20,6 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) layout = new QGridLayout(this); backgroundSourceCard = new CardInfoPictureArtCropWidget(this); - backgroundSourceDeck = new DeckLoader(this); - - backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", - DeckFileFormat::Cockatrice, false); gradientColors = extractDominantColors(background); @@ -72,13 +68,20 @@ void HomeWidget::initializeBackgroundFromSource() cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); break; case BackgroundSources::DeckFileArt: - backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", - DeckFileFormat::Cockatrice, false); + loadBackgroundSourceDeck(); cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); 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() { auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource()); @@ -95,7 +98,7 @@ void HomeWidget::updateRandomCard() newCard.getCardPtr()->getProperty("layout") != "normal"); break; case BackgroundSources::DeckFileArt: - QList cardRefs = backgroundSourceDeck->getDeckList()->getCardRefList(); + QList cardRefs = backgroundSourceDeck.getCardRefList(); ExactCard oldCard = backgroundSourceCard->getCard(); if (!cardRefs.empty()) { @@ -183,7 +186,7 @@ QGroupBox *HomeWidget::createButtons() auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors); connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor, - [this] { tabSupervisor->openDeckInNewTab(nullptr); }); + [this] { tabSupervisor->openDeckInNewTab(LoadedDeck()); }); boxLayout->addWidget(visualDeckEditorButton); auto visualDeckStorageButton = new HomeStyledButton(tr("Browse Decks"), gradientColors); connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor, diff --git a/cockatrice/src/interface/widgets/general/home_widget.h b/cockatrice/src/interface/widgets/general/home_widget.h index 1db33446a..233fda543 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.h +++ b/cockatrice/src/interface/widgets/general/home_widget.h @@ -40,10 +40,12 @@ private: TabSupervisor *tabSupervisor; QPixmap background; CardInfoPictureArtCropWidget *backgroundSourceCard = nullptr; - DeckLoader *backgroundSourceDeck; + DeckList backgroundSourceDeck; QPixmap overlay; QPair gradientColors; HomeStyledButton *connectButton; + + void loadBackgroundSourceDeck(); }; #endif // HOME_WIDGET_H diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp index 5b67ae727..b1346e4fd 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -11,20 +11,15 @@ * UI elements for managing card counts in both the mainboard and sideboard zones. * * @param parent The parent widget. - * @param deckEditor Pointer to the TabDeckEditor. - * @param deckModel Pointer to the DeckListModel. - * @param deckView Pointer to the QTreeView for the deck display. + * @param deckStateManager Pointer to the DeckStateManager * @param cardSizeSlider Pointer to the QSlider used for dynamic font resizing. * @param rootCard The root card for the widget. */ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard) - : QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider), - rootCard(rootCard) + : QWidget(parent), cardSizeSlider(cardSizeSlider) { layout = new QVBoxLayout(this); layout->setAlignment(Qt::AlignHCenter); @@ -33,11 +28,9 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, setContentsMargins(5, 5, 5, 5); // Padding around the text zoneLabelMainboard = new ShadowBackgroundLabel(this, tr("Mainboard")); - buttonBoxMainboard = - new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_MAIN); + buttonBoxMainboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_MAIN); zoneLabelSideboard = new ShadowBackgroundLabel(this, tr("Sideboard")); - buttonBoxSideboard = - new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_SIDE); + buttonBoxSideboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_SIDE); layout->addWidget(zoneLabelMainboard, 0, Qt::AlignHCenter | Qt::AlignBottom); layout->addWidget(buttonBoxMainboard, 0, Qt::AlignHCenter | Qt::AlignTop); diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index 1a8ec8bfd..5a03c5f4a 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -18,9 +18,7 @@ class AllZonesCardAmountWidget : public QWidget Q_OBJECT public: explicit AllZonesCardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard); int getMainboardAmount(); @@ -36,11 +34,7 @@ public slots: private: QVBoxLayout *layout; - AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; QSlider *cardSizeSlider; - ExactCard rootCard; QLabel *zoneLabelMainboard; CardAmountWidget *buttonBoxMainboard; QLabel *zoneLabelSideboard; diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 0d0195d34..62c8e2d60 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -1,5 +1,7 @@ #include "card_amount_widget.h" +#include "../deck_editor/deck_state_manager.h" + #include #include @@ -7,22 +9,17 @@ * @brief Constructs a widget for displaying and controlling the card count in a specific zone. * * @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 rootCard The root card to manage within the widget. * @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE). */ CardAmountWidget::CardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard, const QString &zoneName) - : QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider), - rootCard(rootCard), zoneName(zoneName), hovered(false) + : QWidget(parent), deckStateManager(deckStateManager), cardSizeSlider(cardSizeSlider), rootCard(rootCard), + zoneName(zoneName), hovered(false) { layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -56,15 +53,10 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, layout->addWidget(incrementButton); // React to model changes - connect(deckModel, &DeckListModel::dataChanged, this, &CardAmountWidget::updateCardCount); - connect(deckModel, &QAbstractItemModel::rowsRemoved, this, &CardAmountWidget::updateCardCount); + connect(deckStateManager, &DeckStateManager::cardModified, this, &CardAmountWidget::updateCardCount); // Connect slider for dynamic font size adjustment 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(); } +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). * @@ -144,25 +159,24 @@ void CardAmountWidget::updateCardCount() */ void CardAmountWidget::addPrinting(const QString &zone) { - int addedCount = 1; // 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; bool replacingProviderless = false; if (existing.isValid()) { - QString providerId = deckModel->data(existing.sibling(existing.row(), 4), Qt::DisplayRole).toString(); - if (providerId.isEmpty()) { - int amount = deckModel->data(existing, Qt::DisplayRole).toInt(); + QString foundProviderId = + existing.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); + if (foundProviderId.isEmpty()) { + int amount = existing.data(Qt::DisplayRole).toInt(); extraCopies = amount - 1; // One less because we *always* add one replacingProviderless = true; } } - addedCount += extraCopies; - 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.getPrinting().getSet()->getShortName()) .arg(rootCard.getPrinting().getProperty("num")) @@ -170,29 +184,14 @@ void CardAmountWidget::addPrinting(const QString &zone) .arg(rootCard.getPrinting().getUuid()) .arg(replacingProviderless ? " (replaced providerless printings)" : ""); - emit deckModified(reason); - // Add the card and expand the list UI - auto newCardIndex = deckModel->addCard(rootCard, zone); - recursiveExpand(newCardIndex); + QModelIndex newCardIndex = deckStateManager->modifyDeck(reason, [&](auto model) { + 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. - QString foundProviderId = deckModel->data(existing.sibling(existing.row(), 4), Qt::DisplayRole).toString(); - 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()); + if (newCardIndex.isValid()) { + emit deckStateManager->focusIndexChanged(newCardIndex); } - - // 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); } -/** - * @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. * @@ -281,13 +240,11 @@ void CardAmountWidget::decrementCardHelper(const QString &zone) .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") .arg(rootCard.getPrinting().getUuid()); - emit deckModified(reason); - - QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), + deckStateManager->modifyDeck(reason, [this, &zone](auto model) { + QModelIndex idx = model->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), rootCard.getPrinting().getProperty("num")); - - offsetCountAtIndex(idx, -1); - deckEditor->setModified(true); + return model->offsetCountAtIndex(idx, -1); + }); } /** @@ -298,29 +255,14 @@ void CardAmountWidget::decrementCardHelper(const QString &zone) */ 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. } - if (!deckModel) { - return -1; - } + QList cards = deckStateManager->getModel()->getCardsForZone(deckZone); - DeckList *decklist = deckModel->getDeckList(); - if (!decklist) { - return -1; - } - - QList 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; + return std::count_if(cards.cbegin(), cards.cend(), + [&uuid](const ExactCard &card) { return card.getPrinting().getUuid() == uuid; }); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index 6d059bc04..983416782 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -27,9 +27,7 @@ signals: public: explicit CardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard, const QString &zoneName); @@ -44,9 +42,7 @@ protected: void showEvent(QShowEvent *event) override; private: - AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; + DeckStateManager *deckStateManager; QSlider *cardSizeSlider; ExactCard rootCard; QString zoneName; @@ -57,9 +53,7 @@ private: bool hovered; - void offsetCountAtIndex(const QModelIndex &idx, int offset); void decrementCardHelper(const QString &zoneName); - void recursiveExpand(const QModelIndex &index); private slots: void addPrintingMainboard(); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index f27101b93..95d6b2cdf 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -3,6 +3,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../../interface/card_picture_loader/card_picture_loader.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_search_widget.h" #include "printing_selector_card_selection_widget.h" @@ -21,12 +22,9 @@ * * @param parent The parent widget for the PrintingSelector. * @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) - : QWidget(parent), deckEditor(_deckEditor), deckModel(deckEditor->deckDockWidget->deckModel), - deckView(deckEditor->deckDockWidget->deckView) + : QWidget(parent), deckEditor(_deckEditor), deckStateManager(_deckEditor->deckStateManager) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout = new QVBoxLayout(this); @@ -74,13 +72,12 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck layout->addWidget(flowWidget); - cardSelectionBar = new PrintingSelectorCardSelectionWidget(this); + cardSelectionBar = new PrintingSelectorCardSelectionWidget(this, deckStateManager); cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible()); layout->addWidget(cardSelectionBar); // Connect deck model data change signal to update display - connect(deckModel, &DeckListModel::rowsInserted, this, &PrintingSelector::printingsInDeckChanged); - connect(deckModel, &DeckListModel::rowsRemoved, this, &PrintingSelector::printingsInDeckChanged); + connect(deckStateManager, &DeckStateManager::uniqueCardsChanged, this, &PrintingSelector::printingsInDeckChanged); retranslateUi(); } @@ -115,9 +112,8 @@ void PrintingSelector::updateDisplay() * @brief Sets the current card for the selector and updates the display. * * @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()) { return; @@ -129,7 +125,6 @@ void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_curre } selectedCard = newCard; - currentZone = _currentZone; if (isVisible()) { updateDisplay(); } @@ -138,58 +133,6 @@ void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_curre 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(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. */ @@ -206,7 +149,8 @@ void PrintingSelector::getAllSetsForCurrentCard() QList printingsToUse; if (SettingsCache::instance().getBumpSetsWithCardsInDeckToTop()) { - printingsToUse = sortToolBar->prependPrintingsInDeck(filteredPrintings, selectedCard, deckModel); + printingsToUse = + sortToolBar->prependPrintingsInDeck(filteredPrintings, selectedCard, deckStateManager->getModel()); } else { printingsToUse = filteredPrintings; } @@ -218,8 +162,8 @@ void PrintingSelector::getAllSetsForCurrentCard() connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable { for (int i = 0; i < BATCH_SIZE && currentIndex < printingsToUse.size(); ++i, ++currentIndex) { auto card = ExactCard(selectedCard, printingsToUse[currentIndex]); - auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget( - this, deckEditor, deckModel, deckView, cardSizeWidget->getSlider(), card, currentZone); + auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckStateManager, + cardSizeWidget->getSlider(), card); flowWidget->addWidget(cardDisplayWidget); cardDisplayWidget->clampSetNameToPicture(); connect(cardDisplayWidget, &PrintingSelectorCardDisplayWidget::cardPreferenceChanged, this, diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index fbe3b5b06..e1c07addf 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -21,6 +21,7 @@ #define BATCH_SIZE 10 +class DeckStateManager; class PrintingSelectorCardSearchWidget; class PrintingSelectorCardSelectionWidget; class PrintingSelectorCardSortingWidget; @@ -33,28 +34,27 @@ class PrintingSelector : public QWidget public: PrintingSelector(QWidget *parent, AbstractTabDeckEditor *deckEditor); - void setCard(const CardInfoPtr &newCard, const QString &_currentZone); + void setCard(const CardInfoPtr &newCard); void getAllSetsForCurrentCard(); - [[nodiscard]] DeckListModel *getDeckModel() const - { - return deckModel; - } - - [[nodiscard]] AbstractTabDeckEditor *getDeckEditor() const - { - return deckEditor; - } public slots: void retranslateUi(); void updateDisplay(); - void selectPreviousCard(); - void selectNextCard(); void toggleVisibilityNavigationButtons(bool _state); private slots: void printingsInDeckChanged(); +signals: + /** + * Requests the previous card in the list + */ + void prevCardRequested(); + /** + * Requests the next card in the list + */ + void nextCardRequested(); + private: QVBoxLayout *layout; SettingsButtonWidget *displayOptionsWidget; @@ -67,13 +67,10 @@ private: CardSizeWidget *cardSizeWidget; PrintingSelectorCardSelectionWidget *cardSelectionBar; AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; + DeckStateManager *deckStateManager; CardInfoPtr selectedCard; - QString currentZone; QTimer *widgetLoadingBufferTimer; int currentIndex = 0; - void selectCard(int changeBy); }; #endif // PRINTING_SELECTOR_H diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index 24c45a7e3..92cf2437c 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -17,30 +17,24 @@ * display. * * @param parent The parent widget for this display. - * @param _deckEditor The TabDeckEditor instance for deck management. - * @param _deckModel The DeckListModel instance providing deck data. - * @param _deckView The QTreeView instance displaying the deck. - * @param _cardSizeSlider The slider controlling the size of the displayed card. - * @param _rootCard The root card object, representing the card to be displayed. - * @param _currentZone The current zone in which the card is located. + * @param deckEditor The TabDeckEditor instance for deck management. + * @param deckStateManager The DeckStateManager instance providing deck data. + * @param cardSizeSlider The slider controlling the size of the displayed card. + * @param rootCard The root card object, representing the card to be displayed. */ PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *parent, - AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, - QSlider *_cardSizeSlider, - const ExactCard &_rootCard, - QString &_currentZone) - : QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView), - cardSizeSlider(_cardSizeSlider), rootCard(_rootCard), currentZone(_currentZone) + AbstractTabDeckEditor *deckEditor, + DeckStateManager *deckStateManager, + QSlider *cardSizeSlider, + const ExactCard &rootCard) + : QWidget(parent) { layout = new QVBoxLayout(this); setLayout(layout); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Create the overlay widget for the card display - overlayWidget = - new PrintingSelectorCardOverlayWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard); + overlayWidget = new PrintingSelectorCardOverlayWidget(this, deckEditor, deckStateManager, cardSizeSlider, rootCard); connect(overlayWidget, &PrintingSelectorCardOverlayWidget::cardPreferenceChanged, this, [this]() { emit cardPreferenceChanged(); }); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h index 2f6bd6920..2637b0e57 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h @@ -20,12 +20,10 @@ class PrintingSelectorCardDisplayWidget : public QWidget public: PrintingSelectorCardDisplayWidget(QWidget *parent, - AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, - QSlider *_cardSizeSlider, - const ExactCard &_rootCard, - QString &_currentZone); + AbstractTabDeckEditor *deckEditor, + DeckStateManager *deckStateManager, + QSlider *cardSizeSlider, + const ExactCard &rootCard); public slots: void clampSetNameToPicture(); @@ -36,12 +34,6 @@ signals: private: QVBoxLayout *layout; SetNameAndCollectorsNumberDisplayWidget *setNameAndCollectorsNumberDisplayWidget; - AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; - QSlider *cardSizeSlider; - ExactCard rootCard; - QString currentZone; PrintingSelectorCardOverlayWidget *overlayWidget; }; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp index 5f3a44b30..ac36f2cf4 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp @@ -22,19 +22,16 @@ * * @param parent The parent widget for this overlay. * @param _deckEditor The TabDeckEditor instance for deck management. - * @param _deckModel The DeckListModel instance providing deck data. - * @param _deckView The QTreeView instance displaying the deck. - * @param _cardSizeSlider The slider controlling the size of the card. + * @param deckStateManager The DeckStateManager instance providing deck data. + * @param cardSizeSlider The slider controlling the size of the card. * @param _rootCard The root card object that contains information about the card. */ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, - QSlider *_cardSizeSlider, + DeckStateManager *deckStateManager, + QSlider *cardSizeSlider, const ExactCard &_rootCard) - : QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView), - cardSizeSlider(_cardSizeSlider), rootCard(_rootCard) + : QWidget(parent), deckEditor(_deckEditor), rootCard(_rootCard) { // Set up the main layout auto *mainLayout = new QVBoxLayout(this); @@ -59,8 +56,7 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa updatePinBadgeVisibility(); // Add AllZonesCardAmountWidget - allZonesCardAmountWidget = - new AllZonesCardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, _rootCard); + allZonesCardAmountWidget = new AllZonesCardAmountWidget(this, deckStateManager, cardSizeSlider, _rootCard); allZonesCardAmountWidget->raise(); // Ensure it's on top of the picture // Set initial visibility based on amounts diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h index 10df9d53e..8550612bd 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h @@ -20,8 +20,7 @@ class PrintingSelectorCardOverlayWidget : public QWidget public: explicit PrintingSelectorCardOverlayWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, + DeckStateManager *_deckStateManager, QSlider *_cardSizeSlider, const ExactCard &_rootCard); @@ -48,9 +47,6 @@ private: AllZonesCardAmountWidget *allZonesCardAmountWidget; QLabel *pinBadge = nullptr; AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; - QSlider *cardSizeSlider; ExactCard rootCard; }; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp index fc17cecd0..2eb2ef245 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp @@ -11,7 +11,9 @@ * * @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->setContentsMargins(9, 0, 9, 0); @@ -42,18 +44,12 @@ PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(Printin */ void PrintingSelectorCardSelectionWidget::connectSignals() { - connect(previousCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectPreviousCard); - connect(nextCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectNextCard); + connect(previousCardButton, &QPushButton::clicked, parent, &PrintingSelector::prevCardRequested); + connect(nextCardButton, &QPushButton::clicked, parent, &PrintingSelector::nextCardRequested); } void PrintingSelectorCardSelectionWidget::selectSetForCards() { - auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel()); - connect(setSelectionDialog, &DlgSelectSetForCards::deckAboutToBeModified, parent->getDeckEditor(), - &AbstractTabDeckEditor::onDeckHistorySaveRequested); - connect(setSelectionDialog, &DlgSelectSetForCards::deckModified, parent->getDeckEditor(), - &AbstractTabDeckEditor::onDeckModified); - if (!setSelectionDialog->exec()) { - return; - } + auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, deckStateManager); + setSelectionDialog->exec(); } diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h index a1176a76c..ecd5c83e3 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h @@ -18,7 +18,7 @@ class PrintingSelectorCardSelectionWidget : public QWidget Q_OBJECT public: - explicit PrintingSelectorCardSelectionWidget(PrintingSelector *parent); + explicit PrintingSelectorCardSelectionWidget(PrintingSelector *parent, DeckStateManager *deckStateManager); void connectSignals(); @@ -27,6 +27,7 @@ public slots: private: PrintingSelector *parent; + DeckStateManager *deckStateManager; QHBoxLayout *cardSelectionBarLayout; QPushButton *previousCardButton; QPushButton *selectSetForCardsButton; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp index af7cedbeb..725e5df90 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp @@ -183,7 +183,7 @@ QList PrintingSelectorCardSortingWidget::prependPinnedPrintings(co */ QList PrintingSelectorCardSortingWidget::prependPrintingsInDeck(const QList &printings, const CardInfoPtr &selectedCard, - DeckListModel *deckModel) + const DeckListModel *deckModel) { if (!selectedCard) { return {}; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h index d0faea4ac..b5a00b81e 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h @@ -23,7 +23,7 @@ public: QList prependPinnedPrintings(const QList &printings, const QString &cardName); QList prependPrintingsInDeck(const QList &printings, const CardInfoPtr &selectedCard, - DeckListModel *deckModel); + const DeckListModel *deckModel); public slots: void updateSortOrder(); diff --git a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp index b1a6a61c1..5962680cd 100644 --- a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp @@ -13,7 +13,7 @@ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget const QString &_setName, const QString &_collectorsNumber, QSlider *_cardSizeSlider) - : QWidget(parent) + : QWidget(parent), cardSizeSlider(_cardSizeSlider) { // Set up the layout for the widget layout = new QVBoxLayout(this); @@ -35,7 +35,6 @@ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget collectorsNumber->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); // Store the card size slider and connect its signal to the font size adjustment slot - cardSizeSlider = _cardSizeSlider; connect(cardSizeSlider, &QSlider::valueChanged, this, &SetNameAndCollectorsNumberDisplayWidget::adjustFontSize); // Add labels to the layout diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index c97d32d44..50375c936 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -206,7 +206,7 @@ void ChatView::appendMessage(QString message, defaultFormat = QTextCharFormat(); if (!isUserMessage) { if (messageType == Event_RoomSay::ChatHistory) { - defaultFormat.setForeground(Qt::gray); // FIXME : hardcoded color + defaultFormat.setForeground(Qt::gray); //! \todo hardcoded color defaultFormat.setFontWeight(QFont::Light); defaultFormat.setFontItalic(true); 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 } } else { - defaultFormat.setForeground(Qt::darkGreen); // FIXME : hardcoded color + defaultFormat.setForeground(Qt::darkGreen); //! \todo hardcoded color defaultFormat.setFontWeight(QFont::Bold); } } cursor.setCharFormat(defaultFormat); bool mentionEnabled = SettingsCache::instance().getChatMention(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) highlightedWords = SettingsCache::instance().getHighlightWords().split(' ', Qt::SkipEmptyParts); -#else - highlightedWords = SettingsCache::instance().getHighlightWords().split(' ', QString::SkipEmptyParts); -#endif // parse the message while (message.size()) { diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h index 00fcc4f5e..6cf8370ed 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h @@ -28,9 +28,6 @@ class UserListProxy; class UserMessagePosition { 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); int relativePosition; QTextBlock block; diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp index cdac71b28..05d363fee 100644 --- a/cockatrice/src/interface/widgets/server/games_model.cpp +++ b/cockatrice/src/interface/widgets/server/games_model.cpp @@ -421,10 +421,8 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const { #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) 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 - static const QDate epochDate = QDateTime::fromTime_t(0, Qt::UTC).date(); + static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, Qt::UTC).date(); #endif auto *model = qobject_cast(sourceModel()); if (!model) diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp index 55fa0b95c..29bcc1ab1 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -12,6 +12,7 @@ #include "../../../client/settings/cache_settings.h" #include "../client/network/interfaces/deck_stats_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/pixel_map_generator.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); - historyManager = new DeckListHistoryManager(this); + deckStateManager = new DeckStateManager(this); databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this); deckDockWidget = new DeckEditorDeckDockWidget(this); @@ -64,14 +65,8 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta }); // Connect deck signals to this tab - connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged); - connect(deckDockWidget, &DeckEditorDeckDockWidget::deckModified, this, &AbstractTabDeckEditor::onDeckModified); - 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(deckStateManager, &DeckStateManager::isModifiedChanged, this, &AbstractTabDeckEditor::onDeckModified); + connect(deckDockWidget, &DeckEditorDeckDockWidget::selectedCardChanged, this, &AbstractTabDeckEditor::updateCard); // Connect database display signals to this tab connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this, @@ -101,13 +96,12 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta void AbstractTabDeckEditor::updateCard(const ExactCard &card) { cardInfoDockWidget->updateCard(card); - printingSelectorDockWidget->printingSelector->setCard(card.getCardPtr(), DECK_ZONE_MAIN); + printingSelectorDockWidget->printingSelector->setCard(card.getCardPtr()); } /** @brief Placeholder: called when the deck changes. */ void AbstractTabDeckEditor::onDeckChanged() { - historyManager->clear(); } /** @@ -115,24 +109,8 @@ void AbstractTabDeckEditor::onDeckChanged() */ void AbstractTabDeckEditor::onDeckModified() { - setModified(!isBlankNewDeck()); - deckMenu->setSaveStatus(!isBlankNewDeck()); -} - -/** - * @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(); + deckMenu->setSaveStatus(!deckStateManager->isBlankNewDeck()); + emit tabTextChanged(this, getTabText()); } /** @@ -140,23 +118,9 @@ void AbstractTabDeckEditor::onDeckHistoryClearRequested() * @param card Card to add. * @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) - 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); + deckStateManager->addCard(card, zoneName); 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. */ 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. */ void AbstractTabDeckEditor::actDecrementCardFromSideboard(const ExactCard &card) { - emit 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); + deckStateManager->decrementCard(card, DECK_ZONE_SIDE); } /** * @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); - if (!deck->getLastLoadInfo().fileName.isEmpty()) { - SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastLoadInfo().fileName); + if (!deck.lastLoadInfo.fileName.isEmpty()) { + SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck.lastLoadInfo.fileName); } } /** * @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); - CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList()->getCardRefList())); - setModified(false); + deckStateManager->replaceDeck(_deck); + CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(_deck.deckList.getCardRefList())); aDeckDockVisible->setChecked(true); 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. */ void AbstractTabDeckEditor::actNewDeck() { @@ -277,7 +191,7 @@ void AbstractTabDeckEditor::actNewDeck() return; if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(nullptr); + emit openDeckEditor(LoadedDeck()); return; } @@ -287,9 +201,8 @@ void AbstractTabDeckEditor::actNewDeck() /** @brief Clears the current deck and resets modified flag. */ void AbstractTabDeckEditor::cleanDeckAndResetModified() { + deckStateManager->clearDeck(); deckMenu->setSaveStatus(false); - deckDockWidget->cleanDeck(); - setModified(false); } /** @@ -300,13 +213,13 @@ void AbstractTabDeckEditor::cleanDeckAndResetModified() AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank) { if (SettingsCache::instance().getOpenDeckInNewTab()) { - if (openInSameTabIfBlank && isBlankNewDeck()) + if (openInSameTabIfBlank && deckStateManager->isBlankNewDeck()) return SAME_TAB; else return NEW_TAB; } - if (!modified) + if (!deckStateManager->isModified()) return SAME_TAB; tabSupervisor->setCurrentWidget(this); @@ -357,7 +270,6 @@ void AbstractTabDeckEditor::actLoadDeck() QString fileName = dialog.selectedFiles().at(0); openDeckFromFile(fileName, deckOpenLocation); - deckDockWidget->updateBannerCardComboBox(); } /** @@ -382,17 +294,15 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo { DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); - auto *l = new DeckLoader(this); - if (l->loadFromFile(fileName, fmt, true)) { + auto l = DeckLoader(this); + if (l.loadFromFile(fileName, fmt, true)) { if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(l); - l->deleteLater(); + emit openDeckEditor(l.getDeck()); } else { deckMenu->setSaveStatus(false); - openDeck(l); + openDeck(l.getDeck()); } } else { - l->deleteLater(); QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName)); } deckMenu->setSaveStatus(true); @@ -405,16 +315,16 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo */ bool AbstractTabDeckEditor::actSaveDeck() { - DeckLoader *const deck = getDeckLoader(); - if (deck->getLastLoadInfo().remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) { - QString deckString = deck->getDeckList()->writeToString_Native(); + const auto loadedDeck = deckStateManager->toLoadedDeck(); + if (loadedDeck.lastLoadInfo.remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) { + QString deckString = loadedDeck.deckList.writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck")); return false; } Command_DeckUpload cmd; - cmd.set_deck_id(static_cast(deck->getLastLoadInfo().remoteDeckId)); + cmd.set_deck_id(static_cast(loadedDeck.lastLoadInfo.remoteDeckId)); cmd.set_deck_list(deckString.toStdString()); PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); @@ -422,10 +332,14 @@ bool AbstractTabDeckEditor::actSaveDeck() tabSupervisor->getClient()->sendCommand(pend); return true; - } else if (deck->getLastLoadInfo().fileName.isEmpty()) + } + if (loadedDeck.lastLoadInfo.fileName.isEmpty()) 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; } @@ -441,12 +355,14 @@ bool AbstractTabDeckEditor::actSaveDeck() */ bool AbstractTabDeckEditor::actSaveDeckAs() { + LoadedDeck loadedDeck = deckStateManager->toLoadedDeck(); + QFileDialog dialog(this, tr("Save deck")); dialog.setDirectory(SettingsCache::instance().getDeckPath()); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("cod"); dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); - dialog.selectFile(getDeckList()->getName().trimmed()); + dialog.selectFile(loadedDeck.deckList.getName().trimmed()); if (!dialog.exec()) return false; @@ -454,14 +370,18 @@ bool AbstractTabDeckEditor::actSaveDeckAs() QString fileName = dialog.selectedFiles().at(0); 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( this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); return false; } - setModified(false); + deckStateManager->setLastLoadInfo({.fileName = fileName, .fileFormat = fmt}); + + deckStateManager->setModified(false); SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName); return true; } @@ -475,7 +395,7 @@ void AbstractTabDeckEditor::saveDeckRemoteFinished(const Response &response) if (response.response_code() != Response::RespOk) QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.")); else - setModified(false); + deckStateManager->setModified(false); } /** @@ -493,10 +413,10 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() return; if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(dlg.getDeckList()); + emit openDeckEditor({.deckList = dlg.getDeckList()}); } else { - setDeck(dlg.getDeckList()); - setModified(true); + setDeck({.deckList = dlg.getDeckList()}); + deckStateManager->setModified(true); } deckMenu->setSaveStatus(true); @@ -508,12 +428,13 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() */ void AbstractTabDeckEditor::editDeckInClipboard(bool annotated) { - DlgEditDeckInClipboard dlg(getDeckLoader(), annotated, this); + LoadedDeck loadedDeck = deckStateManager->toLoadedDeck(); + DlgEditDeckInClipboard dlg(loadedDeck.deckList, annotated, this); if (!dlg.exec()) return; - setDeck(dlg.getDeckList()); - setModified(true); + setDeck({dlg.getDeckList(), loadedDeck.lastLoadInfo}); + deckStateManager->setModified(true); deckMenu->setSaveStatus(true); } @@ -532,25 +453,25 @@ void AbstractTabDeckEditor::actEditDeckInClipboardRaw() /** @brief Saves deck to clipboard with set info and annotation. */ void AbstractTabDeckEditor::actSaveDeckToClipboard() { - DeckLoader::saveToClipboard(getDeckList(), true, true); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), true, true); } /** @brief Saves deck to clipboard with annotation, without set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo() { - DeckLoader::saveToClipboard(getDeckList(), true, false); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), true, false); } /** @brief Saves deck to clipboard without annotations, with set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardRaw() { - DeckLoader::saveToClipboard(getDeckList(), false, true); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), false, true); } /** @brief Saves deck to clipboard without annotations or set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo() { - DeckLoader::saveToClipboard(getDeckList(), false, false); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), false, false); } /** @brief Prints the deck using a QPrintPreviewDialog. */ @@ -558,7 +479,7 @@ void AbstractTabDeckEditor::actPrintDeck() { auto *dlg = new QPrintPreviewDialog(this); connect(dlg, &QPrintPreviewDialog::paintRequested, this, - [this](QPrinter *printer) { DeckLoader::printDeckList(printer, getDeckList()); }); + [this](QPrinter *printer) { DeckLoader::printDeckList(printer, deckStateManager->getDeckList()); }); dlg->exec(); } @@ -576,10 +497,10 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() return; if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(dlg.getDeck()); + emit openDeckEditor({.deckList = dlg.getDeck()}); } else { - setDeck(dlg.getDeck()); - setModified(true); + setDeck({.deckList = dlg.getDeck()}); + deckStateManager->setModified(true); } deckMenu->setSaveStatus(true); @@ -591,26 +512,21 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() */ void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) { - if (DeckLoader *const deck = getDeckLoader()) { - QString decklistUrlString = deck->exportDeckToDecklist(getDeckList(), website); - // Check to make sure the string isn't empty. - if (decklistUrlString.isEmpty()) { - // Show an error if the deck is empty, and return. - QMessageBox::critical(this, tr("Error"), tr("There are no cards in your deck to be exported")); - return; - } - - // Encode the string recieved from the model to make sure all characters are encoded. - // first we put it into a qurl object - QUrl decklistUrl = QUrl(decklistUrlString); - // we get the correctly encoded url. - decklistUrlString = decklistUrl.toEncoded(); - // We open the url in the user's default browser - 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.")); + QString decklistUrlString = DeckLoader::exportDeckToDecklist(deckStateManager->getDeckList(), website); + // Check to make sure the string isn't empty. + if (decklistUrlString.isEmpty()) { + // Show an error if the deck is empty, and return. + QMessageBox::critical(this, tr("Error"), tr("There are no cards in your deck to be exported")); + return; } + + // Encode the string recieved from the model to make sure all characters are encoded. + // first we put it into a qurl object + QUrl decklistUrl = QUrl(decklistUrlString); + // we get the correctly encoded url. + decklistUrlString = decklistUrl.toEncoded(); + // We open the url in the user's default browser + QDesktopServices::openUrl(decklistUrlString); } /** @brief Exports deck to www.decklist.org. */ @@ -629,14 +545,14 @@ void AbstractTabDeckEditor::actExportDeckDecklistXyz() void AbstractTabDeckEditor::actAnalyzeDeckDeckstats() { auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this); - interface->analyzeDeck(getDeckList()); + interface->analyzeDeck(deckStateManager->getDeckList()); } /** @brief Analyzes the deck using TappedOut. */ void AbstractTabDeckEditor::actAnalyzeDeckTappedout() { 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. */ @@ -695,7 +611,7 @@ bool AbstractTabDeckEditor::eventFilter(QObject *o, QEvent *e) /** @brief Shows a confirmation dialog before closing. */ bool AbstractTabDeckEditor::confirmClose() { - if (modified) { + if (deckStateManager->isModified()) { tabSupervisor->setCurrentWidget(this); int ret = createSaveConfirmationWindow()->exec(); if (ret == QMessageBox::Save) diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 8544e6c00..69b2b4a1c 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -19,6 +19,7 @@ #include +class DeckStateManager; class CardDatabaseModel; class CardDatabaseDisplayModel; @@ -77,7 +78,6 @@ class QAction; * * - actAddCard(const ExactCard &card) — Adds a card to 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. * - actSaveDeckAs() — Performs a "Save As" action for the deck. * - updateCard(const ExactCard &card) — Updates the currently displayed card info in the dock. @@ -114,34 +114,17 @@ public: virtual void retranslateUi() override = 0; /** @brief Opens a deck in this tab. - * @param deck Pointer to a DeckLoader object. + * @param deck The deck to open */ - void openDeck(DeckLoader *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); + void openDeck(const LoadedDeck &deck); DeckEditorDeckDockWidget *getDeckDockWidget() const { return deckDockWidget; } - DeckListHistoryManager *getHistoryManager() const - { - return historyManager; - } - - DeckListHistoryManager *historyManager; - // UI Elements + DeckStateManager *deckStateManager; DeckEditorMenu *deckMenu; ///< Menu for deck operations DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget; ///< Database dock DeckEditorCardInfoDockWidget *cardInfoDockWidget; ///< Card info dock @@ -156,14 +139,6 @@ public slots: /** @brief Called when the deck is modified. */ 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. * @param card The card to display. */ @@ -198,14 +173,11 @@ public slots: signals: /** @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. */ void deckEditorClosing(AbstractTabDeckEditor *tab); - /** @brief Emitted when a card should be decremented. */ - void decrementCard(const ExactCard &card, QString zoneName); - protected slots: /** @brief Starts a new deck in this tab. */ virtual void actNewDeck(); @@ -286,7 +258,7 @@ private: /** @brief Sets the deck for this tab. * @param _deck The deck object. */ - virtual void setDeck(DeckLoader *_deck); + virtual void setDeck(const LoadedDeck &_deck); /** @brief Helper for editing decks from the clipboard. */ void editDeckInClipboard(bool annotated); @@ -316,14 +288,8 @@ protected: */ 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. */ - void addCardHelper(const ExactCard &card, QString zoneName); - - /** @brief Swaps a card in the deck view. */ - void actSwapCard(const ExactCard &card, const QString &zoneName); + void addCardHelper(const ExactCard &card, const QString &zoneName); /** @brief Opens a deck from a file. */ virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation); @@ -334,8 +300,6 @@ protected: QAction *aResetLayout; QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating; QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating; - - bool modified = false; ///< Whether the deck/tab has unsaved changes }; #endif // TAB_GENERIC_DECK_EDITOR_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h new file mode 100644 index 000000000..ac1b66e14 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h @@ -0,0 +1,227 @@ +#ifndef COCKATRICE_ARCHIDEKT_FORMATS_H +#define COCKATRICE_ARCHIDEKT_FORMATS_H +#include +#include + +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 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 diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp index 909c4d9eb..5b879f8ae 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp @@ -21,16 +21,16 @@ void ArchidektApiResponseCard::fromJson(const QJsonObject &json) edition.fromJson(json.value("edition").toObject()); flavor = json.value("flavor").toString(); - // TODO but not really important - // games = {""}; - // options = {""}; + //! \todo but not really important + //! \todo games = {""}; + //! \todo options = {""}; scryfallImageHash = json.value("scryfallImageHash").toString(); oracleCard = json.value("oracleCard").toObject(); owned = json.value("owned").toInt(); pinnedStatus = json.value("pinnedStatus").toInt(); rarity = json.value("rarity").toString(); - // TODO but not really important - // globalCategories = {""}; + //! \todo but not really important + //! \todo globalCategories = {""}; } void ArchidektApiResponseCard::debugPrint() const diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h index 8cb81cac3..fce437751 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h @@ -39,6 +39,11 @@ public: return name; }; + int getDeckFormat() const + { + return deckFormat; + } + private: int id; QString name; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp index 63d732c9a..3a2468368 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -6,6 +6,7 @@ #include "../../../../cards/card_size_widget.h" #include "../../../../cards/deck_card_zone_display_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 @@ -67,11 +68,12 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi model = new DeckListModel(this); connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); - model->getDeckList()->loadFromStream_Plain(deckStream, false); - model->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); + auto decklist = QSharedPointer(new DeckList); + decklist->loadFromStream_Plain(deckStream, false); + model->setDeckList(decklist); - model->rebuildTree(); + model->forEachCard(CardNodeFunction::ResolveProviderId()); retranslateUi(); } @@ -84,17 +86,19 @@ void ArchidektApiResponseDeckDisplayWidget::retranslateUi() void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString &activeGroupCriteria) { model->setActiveGroupCriteria(DeckListModelGroupCriteria::fromString(activeGroupCriteria)); - model->sort(1, Qt::AscendingOrder); + model->sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); } void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor() { - auto loader = new DeckLoader(this); - loader->getDeckList()->loadFromString_Native(model->getDeckList()->writeToString_Native()); + DeckList deckList(*model->getDeckList()); + 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() @@ -119,7 +123,7 @@ void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListMode QSortFilterProxyModel proxy; proxy.setSourceModel(model); proxy.setSortRole(Qt::EditRole); - proxy.sort(1, Qt::AscendingOrder); + proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); for (int i = 0; i < proxy.rowCount(); ++i) { QModelIndex proxyIndex = proxy.index(i, 0); @@ -132,10 +136,12 @@ void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListMode continue; } + QString zoneName = + persistent.sibling(persistent.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + DeckCardZoneDisplayWidget *zoneDisplayWidget = - new DeckCardZoneDisplayWidget(zoneContainer, model, nullptr, persistent, - model->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), - "maintype", {"name"}, DisplayType::Overlap, 20, 10, cardSizeSlider); + new DeckCardZoneDisplayWidget(zoneContainer, model, nullptr, persistent, zoneName, "maintype", {"name"}, + DisplayType::Overlap, 20, 10, cardSizeSlider); connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, zoneDisplayWidget, &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h index 3d624d293..58aca429e 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h @@ -31,7 +31,7 @@ * * ### Signals * - `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. * * ### Features @@ -52,9 +52,9 @@ signals: /** * @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: /** @@ -75,7 +75,7 @@ public: void retranslateUi(); /** - * @brief Opens the deck in the deck editor via DeckLoader. + * @brief Opens the deck in the deck editor. */ void actOpenInDeckEditor(); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp index 7a838d1ff..2998a03bc 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -16,7 +16,7 @@ #define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg" -QString timeAgo(const QString ×tamp) +static QString timeAgo(const QString ×tamp) { QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp index 5747ce90d..8746093c7 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp @@ -16,10 +16,7 @@ ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsD flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); imageNetworkManager = new QNetworkAccessManager(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) imageNetworkManager->setTransferTimeout(); // Use Qt's default timeout -#endif - imageNetworkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); // Add widgets for deck listings diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp index e6615fa7b..3769cd9a2 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -27,10 +27,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { networkManager = new QNetworkAccessManager(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) networkManager->setTransferTimeout(); // Use Qt's default timeout -#endif - networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp index ceafb719b..a744a9b14 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp @@ -14,10 +14,8 @@ void EdhrecDeckApiResponse::fromJson(const QJsonArray &json) deckList += cardlistValue.toString() + "\n"; } - deckLoader = new DeckLoader(nullptr); - QTextStream stream(&deckList); - deckLoader->getDeckList()->loadFromStream_Plain(stream, true); + deck.loadFromStream_Plain(stream, true); } void EdhrecDeckApiResponse::debugPrint() const diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h index 96205e149..cd4f9d99c 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h @@ -21,7 +21,7 @@ public: // Debug method for logging void debugPrint() const; - DeckLoader *deckLoader; + DeckList deck; }; #endif // EDHREC_DECK_API_RESPONSE_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index 26ade96dd..c01c7fa43 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -19,17 +19,17 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi nameLabel = new QLabel(this); nameLabel->setText(toDisplay.name); nameLabel->setAlignment(Qt::AlignHCenter); + nameLabel->setStyleSheet("font-size: 20px; font-weight: bold"); inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay); synergyDisplayWidget = new EdhrecApiResponseCardSynergyDisplayWidget(this, toDisplay); - layout->addWidget(nameLabel); - layout->addWidget(cardPictureWidget); - backgroundPlateWidget = new BackgroundPlateWidget(this); auto plateLayout = new QVBoxLayout(backgroundPlateWidget); + plateLayout->addWidget(nameLabel); + plateLayout->addWidget(cardPictureWidget); plateLayout->addWidget(inclusionDisplayWidget); plateLayout->addWidget(synergyDisplayWidget); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index b15d559f5..515475f3e 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp @@ -3,6 +3,7 @@ #include "../../../../../cards/card_info_picture_widget.h" #include "../../tab_edhrec_main.h" #include "../card_prices/edhrec_api_response_card_prices_display_widget.h" +#include "edhrec_commander_api_response_bracket_navigation_widget.h" #include @@ -12,9 +13,14 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom QString baseUrl) : QWidget(parent), commanderDetails(_commanderDetails) { - layout = new QVBoxLayout(this); + layout = new QHBoxLayout(this); setLayout(layout); + commanderLayout = new QHBoxLayout(); + commanderDetailsLayout = new QVBoxLayout(); + commanderDetailsLayout->setAlignment(Qt::AlignCenter); + navigationAndPricesLayout = new QVBoxLayout(); + commanderPicture = new CardInfoPictureWidget(this); commanderPicture->setCard(CardDatabaseManager::query()->getCard({commanderDetails.getName()})); @@ -36,20 +42,35 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom 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->setAlignment(Qt::AlignCenter); + label->setStyleSheet("font-size: 16px"); + salt = new QLabel(this); salt->setAlignment(Qt::AlignCenter); + salt->setStyleSheet("font-size: 16px"); cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices()); navigationWidget = new EdhrecCommanderApiResponseNavigationWidget(this, commanderDetails, baseUrl); - layout->addWidget(commanderPicture); - layout->addWidget(label); - layout->addWidget(salt); - layout->addWidget(cardPricesDisplayWidget); - layout->addWidget(navigationWidget); + commanderLayout->addWidget(commanderPicture); + commanderDetailsLayout->addWidget(commanderName); + commanderDetailsLayout->addSpacing(1); + commanderDetailsLayout->addWidget(label); + commanderDetailsLayout->addWidget(salt); + commanderDetailsLayout->addWidget(cardPricesDisplayWidget); + commanderLayout->addLayout(commanderDetailsLayout); + navigationAndPricesLayout->addWidget(navigationWidget); + // navigationAndPricesLayout->addWidget(cardPricesDisplayWidget); + + layout->addLayout(commanderLayout); + layout->addLayout(navigationAndPricesLayout); retranslateUi(); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h index 295e4228b..8e74588e2 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h @@ -29,8 +29,12 @@ public: private: EdhrecCommanderApiResponseCommanderDetails commanderDetails; - QVBoxLayout *layout; + QHBoxLayout *layout; + QHBoxLayout *commanderLayout; + QVBoxLayout *commanderDetailsLayout; + QVBoxLayout *navigationAndPricesLayout; CardInfoPictureWidget *commanderPicture; + QLabel *commanderName; QLabel *label; QLabel *salt; EdhrecApiResponseCardPricesDisplayWidget *cardPricesDisplayWidget; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp new file mode 100644 index 000000000..c3ab23e41 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp @@ -0,0 +1,95 @@ +#include "edhrec_commander_api_response_bracket_navigation_widget.h" + +#include + +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 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 &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"); + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h new file mode 100644 index 000000000..713ef2791 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h @@ -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 +#include +#include +#include +#include + +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 gameChangerButtons; + + void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); +}; + +#endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp new file mode 100644 index 000000000..1845c020d --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp @@ -0,0 +1,97 @@ +#include "edhrec_commander_api_response_budget_navigation_widget.h" + +#include + +EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudgetNavigationWidget( + QWidget *parent, + const QString &baseUrl) + : QWidget(parent) +{ + layout = new QGridLayout(this); + setLayout(layout); + + budgetLabel = new QLabel(this); + + layout->addWidget(budgetLabel, 3, 0, 1, 2); + + for (int i = 0; i < budgetOptions.length(); i++) { + QString option = budgetOptions.at(i); + QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1); + QPushButton *btn = new QPushButton(label, this); + btn->setMinimumHeight(84); + btn->setStyleSheet("font-size: 24px"); + budgetButtons[option] = btn; + layout->addWidget(btn, 4, i); + connect(btn, &QPushButton::clicked, this, [=, this]() { + selectedBudget = option; + updateOptionButtonSelection(budgetButtons, option); + emit requestNavigation(); + }); + } + + updateOptionButtonSelection(budgetButtons, ""); + + retranslateUi(); + applyOptionsFromUrl(baseUrl); +} + +void EdhrecCommanderApiResponseBudgetNavigationWidget::retranslateUi() +{ + budgetLabel->setText(tr("Budget")); +} + +void EdhrecCommanderApiResponseBudgetNavigationWidget::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, budgetOpt; + + // Define valid sets + QSet validGameChangers = {"exhibition", "core", "upgraded", "optimized", "cedh"}; + QSet validBudgets = {"budget", "expensive"}; + + // Check remaining parts after commander + for (int i = 2; i < parts.size(); ++i) { + QString part = parts[i].toLower(); + if (validGameChangers.contains(part)) { + gameChangerOpt = part; + } else if (validBudgets.contains(part)) { + budgetOpt = part; + } + } + + if (!budgetButtons.contains(budgetOpt)) { + budgetOpt.clear(); + } + + selectedBudget = budgetOpt; + + updateOptionButtonSelection(budgetButtons, selectedBudget); +} + +void EdhrecCommanderApiResponseBudgetNavigationWidget::updateOptionButtonSelection( + QMap &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"); + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h new file mode 100644 index 000000000..666edba16 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h @@ -0,0 +1,37 @@ +#ifndef COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H +#define COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H + +#include +#include +#include +#include +#include + +class EdhrecCommanderApiResponseBudgetNavigationWidget : public QWidget +{ + Q_OBJECT +public: + explicit EdhrecCommanderApiResponseBudgetNavigationWidget(QWidget *parent, const QString &baseUrl); + void retranslateUi(); + void applyOptionsFromUrl(const QString &url); + QString getSelectedBudget() const + { + return selectedBudget; + } + +signals: + void requestNavigation(); + +private: + QGridLayout *layout; + QLabel *budgetLabel; + + QStringList budgetOptions = {"", "budget", "expensive"}; + QString selectedBudget; + + QMap budgetButtons; + + void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); +}; + +#endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp index 3f3b9f1ba..c42d2ed90 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp @@ -11,47 +11,28 @@ EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigation layout = new QGridLayout(this); setLayout(layout); - gameChangerLabel = new QLabel(this); - budgetLabel = new QLabel(this); + bracketNavigationWidget = new EdhrecCommanderApiResponseBracketNavigationWidget(this, baseUrl); + + connect(bracketNavigationWidget, &EdhrecCommanderApiResponseBracketNavigationWidget::requestNavigation, this, + &EdhrecCommanderApiResponseNavigationWidget::actRequestCommanderNavigation); + + budgetNavigationWidget = new EdhrecCommanderApiResponseBudgetNavigationWidget(this, baseUrl); + + connect(budgetNavigationWidget, &EdhrecCommanderApiResponseBudgetNavigationWidget::requestNavigation, this, + &EdhrecCommanderApiResponseNavigationWidget::actRequestCommanderNavigation); comboPushButton = new QPushButton(this); + comboPushButton->setMinimumHeight(84); + comboPushButton->setStyleSheet("font-size: 24px"); averageDeckPushButton = new QPushButton(this); + averageDeckPushButton->setMinimumHeight(84); + averageDeckPushButton->setStyleSheet("font-size: 24px"); layout->addWidget(comboPushButton, 0, 0, 1, 1); layout->addWidget(averageDeckPushButton, 0, 1, 1, 1); - 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); - gameChangerButtons[option] = optionButton; - layout->addWidget(optionButton, 2, i); - connect(optionButton, &QPushButton::clicked, this, [=, this]() { - selectedGameChanger = option; - updateOptionButtonSelection(gameChangerButtons, option); - actRequestCommanderNavigation(); - }); - } - - layout->addWidget(budgetLabel, 3, 0, 1, 2); - - for (int i = 0; i < budgetOptions.length(); i++) { - QString option = budgetOptions.at(i); - QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1); - QPushButton *btn = new QPushButton(label, this); - budgetButtons[option] = btn; - layout->addWidget(btn, 4, i); - connect(btn, &QPushButton::clicked, this, [=, this]() { - selectedBudget = option; - updateOptionButtonSelection(budgetButtons, option); - actRequestCommanderNavigation(); - }); - } - - updateOptionButtonSelection(gameChangerButtons, ""); - updateOptionButtonSelection(budgetButtons, ""); + layout->addWidget(bracketNavigationWidget, 1, 0, 1, 2); + layout->addWidget(budgetNavigationWidget, 2, 0, 1, 2); QWidget *currentParent = parentWidget(); TabEdhRecMain *parentTab = nullptr; @@ -73,87 +54,21 @@ EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigation } retranslateUi(); - applyOptionsFromUrl(baseUrl); } void EdhrecCommanderApiResponseNavigationWidget::retranslateUi() { comboPushButton->setText(tr("Combos")); averageDeckPushButton->setText(tr("Average Deck")); - gameChangerLabel->setText(tr("Game Changers")); - budgetLabel->setText(tr("Budget")); -} - -void EdhrecCommanderApiResponseNavigationWidget::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" -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - QStringList parts = cleanedUrl.split('/', Qt::SkipEmptyParts); -#else - QStringList parts = cleanedUrl.split('/', QString::SkipEmptyParts); -#endif - - if (parts.size() < 2) { - return; - } - - QString commanderName = parts[1]; - QString gameChangerOpt, budgetOpt; - - // Define valid sets - QSet validGameChangers = {"core", "upgraded", "optimized"}; - QSet validBudgets = {"budget", "expensive"}; - - // Check remaining parts after commander - for (int i = 2; i < parts.size(); ++i) { - QString part = parts[i].toLower(); - if (validGameChangers.contains(part)) { - gameChangerOpt = part; - } else if (validBudgets.contains(part)) { - budgetOpt = part; - } - } - - // Validate and apply - if (!gameChangerButtons.contains(gameChangerOpt)) { - gameChangerOpt.clear(); - } - if (!budgetButtons.contains(budgetOpt)) { - budgetOpt.clear(); - } - - selectedGameChanger = gameChangerOpt; - selectedBudget = budgetOpt; - - updateOptionButtonSelection(gameChangerButtons, selectedGameChanger); - updateOptionButtonSelection(budgetButtons, selectedBudget); -} - -void EdhrecCommanderApiResponseNavigationWidget::updateOptionButtonSelection(QMap &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;" : ""); - } } QString EdhrecCommanderApiResponseNavigationWidget::addNavigationOptionsToUrl(QString baseUrl) { - if (!selectedGameChanger.isEmpty()) { - baseUrl += "/" + selectedGameChanger; + if (!bracketNavigationWidget->getSelectedGameChanger().isEmpty()) { + baseUrl += "/" + bracketNavigationWidget->getSelectedGameChanger(); } - if (!selectedBudget.isEmpty()) { - baseUrl += "/" + selectedBudget; + if (!budgetNavigationWidget->getSelectedBudget().isEmpty()) { + baseUrl += "/" + budgetNavigationWidget->getSelectedBudget(); } return baseUrl; } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h index b97e095f7..10dfa8223 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h @@ -8,6 +8,8 @@ #define EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H #include "edhrec_api_response_commander_details_display_widget.h" +#include "edhrec_commander_api_response_bracket_navigation_widget.h" +#include "edhrec_commander_api_response_budget_navigation_widget.h" #include #include @@ -23,7 +25,6 @@ public: const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails, QString baseUrl); void retranslateUi(); - void applyOptionsFromUrl(const QString &url); public slots: void actRequestCommanderNavigation(); @@ -35,24 +36,14 @@ signals: private: QGridLayout *layout; - QLabel *gameChangerLabel; - QLabel *budgetLabel; - - QStringList gameChangerOptions = {"", "core", "upgraded", "optimized"}; - QStringList budgetOptions = {"", "budget", "expensive"}; - - QString selectedGameChanger; - QString selectedBudget; - - QMap gameChangerButtons; - QMap budgetButtons; + EdhrecCommanderApiResponseBracketNavigationWidget *bracketNavigationWidget; + EdhrecCommanderApiResponseBudgetNavigationWidget *budgetNavigationWidget; QPushButton *comboPushButton; QPushButton *averageDeckPushButton; EdhrecCommanderApiResponseCommanderDetails commanderDetails; - void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); QString addNavigationOptionsToUrl(QString baseUrl); QString buildComboUrl() const; }; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp index 93dbd6bc5..10389f4eb 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp @@ -15,10 +15,7 @@ TabEdhRec::TabEdhRec(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { networkManager = new QNetworkAccessManager(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) networkManager->setTransferTimeout(); // Use Qt's default timeout -#endif - networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, &QNetworkAccessManager::finished, this, &TabEdhRec::processApiJson); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp index 92163997c..f861d8afd 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp @@ -37,10 +37,7 @@ static bool canBeCommander(const CardInfoPtr &cardInfo) TabEdhRecMain::TabEdhRecMain(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { networkManager = new QNetworkAccessManager(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) networkManager->setTransferTimeout(); // Use Qt's default timeout -#endif - networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); @@ -363,7 +360,7 @@ void TabEdhRecMain::processAverageDeckResponse(QJsonObject reply) { EdhrecAverageDeckApiResponse deckData; deckData.fromJson(reply); - tabSupervisor->openDeckInNewTab(deckData.deck.deckLoader); + tabSupervisor->openDeckInNewTab({deckData.deck.deck, {}}); } void TabEdhRecMain::prettyPrintJson(const QJsonValue &value, int indentLevel) diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 4fa12259c..286252e13 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -1,6 +1,7 @@ #include "tab_deck_editor.h" #include "../../../client/settings/cache_settings.h" +#include "../deck_editor/deck_state_manager.h" #include "../filters/filter_builder.h" #include "../interface/pixel_map_generator.h" #include "../interface/widgets/cards/card_info_frame_widget.h" @@ -114,8 +115,8 @@ void TabDeckEditor::createMenus() */ QString TabDeckEditor::getTabText() const { - QString result = tr("Deck: %1").arg(deckDockWidget->getSimpleDeckName()); - if (modified) + QString result = tr("Deck: %1").arg(deckStateManager->getSimpleDeckName()); + if (deckStateManager->isModified()) result.prepend("* "); return result; } @@ -158,8 +159,7 @@ void TabDeckEditor::refreshShortcuts() */ void TabDeckEditor::showPrintingSelector() { - printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr(), - DECK_ZONE_MAIN); + printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); printingSelectorDockWidget->printingSelector->updateDisplay(); aPrintingSelectorDockVisible->setChecked(true); printingSelectorDockWidget->setVisible(true); diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h index 9e00099a0..b3ffb8d90 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h @@ -12,7 +12,6 @@ class CardDatabaseDisplayModel; class DeckListModel; class QLabel; -class DeckLoader; /** * @class TabDeckEditor diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index a54c16258..73b3fc0ac 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -245,7 +245,7 @@ void TabDeckStorage::actOpenLocalDeck() if (!deckLoader->loadFromFile(filePath, DeckFileFormat::Cockatrice, true)) continue; - emit openDeckEditor(deckLoader); + emit openDeckEditor(deckLoader->getDeck()); } } @@ -307,13 +307,15 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa QFile deckFile(filePath); QFileInfo deckFileInfo(deckFile); - DeckLoader deck(this); - if (!deck.loadFromFile(filePath, DeckFileFormat::Cockatrice)) { + DeckLoader deckLoader(this); + if (!deckLoader.loadFromFile(filePath, DeckFileFormat::Cockatrice)) { QMessageBox::critical(this, tr("Error"), tr("Invalid deck file")); return; } - if (deck.getDeckList()->getName().isEmpty()) { + DeckList deck = deckLoader.getDeck().deckList; + + if (deck.getName().isEmpty()) { bool ok; QString deckName = getTextWithMax(this, tr("Enter deck name"), tr("This decklist does not have a name.\nPlease enter a name:"), @@ -322,12 +324,12 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa return; if (deckName.isEmpty()) deckName = tr("Unnamed deck"); - deck.getDeckList()->setName(deckName); + deck.setName(deckName); } else { - deck.getDeckList()->setName(deck.getDeckList()->getName().left(MAX_NAME_LENGTH)); + deck.setName(deck.getName().left(MAX_NAME_LENGTH)); } - QString deckString = deck.getDeckList()->writeToString_Native(); + QString deckString = deck.writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { QMessageBox::critical(this, tr("Error"), tr("Invalid deck file")); return; @@ -436,7 +438,7 @@ void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandCont if (!loader.loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id())) return; - emit openDeckEditor(&loader); + emit openDeckEditor(loader.getDeck()); } void TabDeckStorage::actDownload() @@ -492,8 +494,12 @@ void TabDeckStorage::downloadFinished(const Response &r, const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); QString filePath = extraData.toString(); - DeckLoader deck(this, new DeckList(QString::fromStdString(resp.deck()))); - deck.saveToFile(filePath, DeckFileFormat::Cockatrice); + DeckList deckList = DeckList(QString::fromStdString(resp.deck())); + + DeckLoader deckLoader(this); + deckLoader.setDeck({deckList, {}}); + + deckLoader.saveToFile(filePath, DeckFileFormat::Cockatrice); } void TabDeckStorage::actNewFolder() diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h index 0226785e3..f8c0497f7 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h @@ -13,6 +13,7 @@ #include +struct LoadedDeck; class ServerInfo_User; class AbstractClient; class QTreeView; @@ -23,7 +24,6 @@ class QTreeWidgetItem; class QGroupBox; class CommandContainer; class Response; -class DeckLoader; class TabDeckStorage : public Tab { @@ -87,7 +87,7 @@ public: return tr("Deck Storage"); } signals: - void openDeckEditor(DeckLoader *deckLoader); + void openDeckEditor(const LoadedDeck &deck); }; #endif diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 2f83ba2e6..5b6c8d52a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -749,11 +749,10 @@ void TabGame::loadDeckForLocalPlayer(Player *localPlayer, int playerId, ServerIn { TabbedDeckViewContainer *deckViewContainer = deckViewContainers.value(playerId); if (playerInfo.has_deck_list()) { - DeckLoader newDeck(this, new DeckList(QString::fromStdString(playerInfo.deck_list()))); - CardPictureLoader::cacheCardPixmaps( - CardDatabaseManager::query()->getCards(newDeck.getDeckList()->getCardRefList())); - deckViewContainer->playerDeckView->setDeck(newDeck); - localPlayer->setDeck(newDeck); + DeckList deckList = DeckList(QString::fromStdString(playerInfo.deck_list())); + CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(deckList.getCardRefList())); + deckViewContainer->playerDeckView->setDeck(deckList); + localPlayer->setDeck(deckList); } } diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h index b6fe5b51c..27374ef2a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -45,7 +45,6 @@ class ReplayTimelineWidget; class CardZone; class AbstractCardItem; class CardItem; -class DeckLoader; class QVBoxLayout; class QHBoxLayout; class GameReplay; @@ -119,7 +118,7 @@ signals: void containerProcessingStarted(const GameEventContext &context); void containerProcessingDone(); void openMessageDialog(const QString &userName, bool focus); - void openDeckEditor(DeckLoader *deck); + void openDeckEditor(const LoadedDeck &deck); void notIdle(); void phaseChanged(int phase); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index df9387a6c..b10d615ff 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -133,10 +133,10 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget * // create tabs menu actions aTabDeckEditor = new QAction(this); - connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(nullptr); }); + connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(LoadedDeck()); }); aTabVisualDeckEditor = new QAction(this); - connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(nullptr); }); + connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(LoadedDeck()); }); aTabEdhRec = new QAction(this); connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); }); @@ -846,9 +846,9 @@ void TabSupervisor::talkLeft(TabMessage *tab) /** * Creates a new deck editor tab and loads the deck into it. * Creates either a classic or visual deck editor tab depending on settings - * @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance. + * @param deckToOpen The deck to open in the tab. */ -void TabSupervisor::openDeckInNewTab(DeckLoader *deckToOpen) +void TabSupervisor::openDeckInNewTab(const LoadedDeck &deckToOpen) { int type = SettingsCache::instance().getDefaultDeckEditorType(); switch (type) { @@ -868,13 +868,12 @@ void TabSupervisor::openDeckInNewTab(DeckLoader *deckToOpen) /** * Creates a new deck editor tab - * @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance. + * @param deckToOpen The deck to open in the tab. */ -TabDeckEditor *TabSupervisor::addDeckEditorTab(DeckLoader *deckToOpen) +TabDeckEditor *TabSupervisor::addDeckEditorTab(const LoadedDeck &deckToOpen) { auto *tab = new TabDeckEditor(this); - if (deckToOpen) - tab->openDeck(deckToOpen); + tab->openDeck(deckToOpen); connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed); connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addDeckEditorTab); myAddTab(tab); @@ -883,11 +882,10 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(DeckLoader *deckToOpen) return tab; } -TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(DeckLoader *deckToOpen) +TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(const LoadedDeck &deckToOpen) { auto *tab = new TabDeckEditorVisual(this); - if (deckToOpen) - tab->openDeck(deckToOpen); + tab->openDeck(deckToOpen); connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed); connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addVisualDeckEditorTab); myAddTab(tab); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 948ac6d7e..0c4428f83 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -168,9 +168,9 @@ signals: void showWindowIfHidden(); public slots: - void openDeckInNewTab(DeckLoader *deckToOpen); - TabDeckEditor *addDeckEditorTab(DeckLoader *deckToOpen); - TabDeckEditorVisual *addVisualDeckEditorTab(DeckLoader *deckToOpen); + void openDeckInNewTab(const LoadedDeck &deckToOpen); + TabDeckEditor *addDeckEditorTab(const LoadedDeck &deckToOpen); + TabDeckEditorVisual *addVisualDeckEditorTab(const LoadedDeck &deckToOpen); TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab(); TabEdhRecMain *addEdhrecMainTab(); TabArchidekt *addArchidektTab(); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 6dd55d545..f3d573d27 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -1,6 +1,7 @@ #include "tab_deck_editor_visual.h" #include "../../../../client/settings/cache_settings.h" +#include "../../deck_editor/deck_state_manager.h" #include "../../filters/filter_builder.h" #include "../../interface/pixel_map_generator.h" #include "../../interface/widgets/cards/card_info_frame_widget.h" @@ -61,7 +62,7 @@ void TabDeckEditorVisual::createCentralFrame() centralFrame = new QVBoxLayout; centralWidget->setLayout(centralFrame); - tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckDockWidget->deckModel, + tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckStateManager->getModel(), databaseDisplayDockWidget->databaseModel, databaseDisplayDockWidget->databaseDisplayModel); @@ -85,7 +86,7 @@ void TabDeckEditorVisual::onDeckChanged() AbstractTabDeckEditor::onDeckModified(); tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel(); tabContainer->deckAnalytics->refreshDisplays(); - tabContainer->sampleHandWidget->setDeckModel(deckDockWidget->deckModel); + tabContainer->sampleHandWidget->setDeckModel(deckStateManager->getModel()); } /** @brief Creates menus for deck editing and view options, including dock actions. */ @@ -149,8 +150,8 @@ void TabDeckEditorVisual::createMenus() /** @brief Returns the tab text, prepending a mark if the deck has unsaved changes. */ QString TabDeckEditorVisual::getTabText() const { - QString result = tr("Visual Deck: %1").arg(deckDockWidget->getSimpleDeckName()); - if (modified) + QString result = tr("Visual Deck: %1").arg(deckStateManager->getSimpleDeckName()); + if (deckStateManager->isModified()) result.prepend("* "); return result; } @@ -166,9 +167,9 @@ void TabDeckEditorVisual::changeModelIndexAndCardInfo(const ExactCard &activeCar void TabDeckEditorVisual::changeModelIndexToCard(const ExactCard &activeCard) { QString cardName = activeCard.getName(); - QModelIndex index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_MAIN); + QModelIndex index = deckStateManager->getModel()->findCard(cardName, DECK_ZONE_MAIN); if (!index.isValid()) { - index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_SIDE); + index = deckStateManager->getModel()->findCard(cardName, DECK_ZONE_SIDE); } if (!deckDockWidget->getSelectionModel()->hasSelection()) { deckDockWidget->getSelectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); @@ -182,7 +183,7 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, auto card = instance->getCard(); // Get the model index for the card - QModelIndex idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); + QModelIndex idx = deckStateManager->getModel()->findCard(card.getName(), zoneName); if (!idx.isValid()) { return; } @@ -191,8 +192,8 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, // Double click = swap if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton) { - actSwapCard(card, zoneName); - idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); + deckStateManager->swapCardAtIndex(idx); + idx = deckStateManager->getModel()->findCard(card.getName(), zoneName); sel->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect); return; } @@ -266,8 +267,7 @@ bool TabDeckEditorVisual::actSaveDeckAs() /** @brief Shows the printing selector dock and updates it with the current card. */ void TabDeckEditorVisual::showPrintingSelector() { - printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr(), - DECK_ZONE_MAIN); + printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); printingSelectorDockWidget->printingSelector->updateDisplay(); aPrintingSelectorDockVisible->setChecked(true); printingSelectorDockWidget->setVisible(true); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp index 69c80bb35..9570702ed 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -24,11 +24,11 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath) { - auto deckLoader = new DeckLoader(this); - if (!deckLoader->loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true)) { + auto deckLoader = DeckLoader(this); + if (!deckLoader.loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true)) { QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath)); return; } - emit openDeckEditor(deckLoader); + emit openDeckEditor(deckLoader.getDeck()); } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h index a56ce4c24..ccf752e67 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h @@ -9,9 +9,9 @@ #include "../tab.h" +struct LoadedDeck; class AbstractClient; class CommandContainer; -class DeckLoader; class DeckPreviewWidget; class QFileSystemModel; class QGroupBox; @@ -39,7 +39,7 @@ public slots: void actOpenLocalDeck(const QString &filePath); signals: - void openDeckEditor(DeckLoader *deckLoader); + void openDeckEditor(const LoadedDeck &deck); private: VisualDeckStorageWidget *visualDeckStorageWidget; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index 9bc506056..5098696dd 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -2,6 +2,7 @@ #include "../../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" +#include "../deck_editor/deck_state_manager.h" #include @@ -60,18 +61,14 @@ void VisualDatabaseDisplayNameFilterWidget::retranslateUi() void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() { - DeckListModel *deckListModel = deckEditor->deckDockWidget->deckModel; + DeckListModel *deckListModel = deckEditor->deckStateManager->getModel(); if (!deckListModel) return; - DeckList *decklist = deckListModel->getDeckList(); - if (!decklist) - return; - QList cardsInDeck = decklist->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - createNameFilter(currentCard->getName()); + QList cardNames = deckListModel->getCardNames(); + for (auto cardName : cardNames) { + createNameFilter(cardName); } updateFilterModel(); @@ -83,7 +80,7 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromClipboard() if (!dlg.exec()) return; - QStringList cardsInClipboard = dlg.getDeckList()->getDeckList()->getCardList(); + QStringList cardsInClipboard = dlg.getDeckList().getCardList(); for (QString cardName : cardsInClipboard) { createNameFilter(cardName); } diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index 778706f9a..0badb76ff 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -76,25 +76,11 @@ void VisualDeckEditorSampleHandWidget::updateDisplay() QList VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGet) { - QList mainDeckCards; QList randomCards; if (!deckListModel) return randomCards; - DeckList *decklist = deckListModel->getDeckList(); - if (!decklist) - return randomCards; - QList cardsInDeck = decklist->getCardNodes({DECK_ZONE_MAIN}); - - // Collect all cards in the main deck, allowing duplicates based on their count - for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef()); - if (card) { - mainDeckCards.append(card); - } - } - } + QList mainDeckCards = deckListModel->getCardsForZone(DECK_ZONE_MAIN); if (mainDeckCards.isEmpty()) return randomCards; diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp index 76cfe8c8e..7b70e2b6d 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp @@ -264,9 +264,10 @@ void VisualDeckEditorWidget::onCardRemoval(const QModelIndex &parent, int first, void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex persistent) { + QString zoneName = + persistent.sibling(persistent.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( - zoneContainer, deckListModel, selectionModel, persistent, - deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), + zoneContainer, deckListModel, selectionModel, persistent, zoneName, displayOptionsWidget->getActiveGroupCriteria(), displayOptionsWidget->getActiveSortCriteria(), displayOptionsWidget->getDisplayType(), 20, 10, cardSizeWidget); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); @@ -290,7 +291,7 @@ void VisualDeckEditorWidget::constructZoneWidgetsFromDeckListModel() QSortFilterProxyModel proxy; proxy.setSourceModel(deckListModel); proxy.setSortRole(Qt::EditRole); - proxy.sort(1, Qt::AscendingOrder); + proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); for (int i = 0; i < proxy.rowCount(); ++i) { QModelIndex proxyIndex = proxy.index(i, 0); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index d3858a135..d57cda1c6 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -80,7 +80,7 @@ static QStringList findAllKnownTags() auto loader = DeckLoader(nullptr); for (const QString &file : allFiles) { loader.loadFromFile(file, DeckFileFormat::getFormatFromName(file), false); - QStringList tags = loader.getDeckList()->getTags(); + QStringList tags = loader.getDeck().deckList.getTags(); knownTags.append(tags); knownTags.removeDuplicates(); } @@ -125,7 +125,7 @@ static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath) static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget) { deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); - deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName; + deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getDeck().lastLoadInfo.fileName; deckPreviewWidget->refreshBannerCardText(); } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index 68243100c..4c782c989 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -74,16 +74,16 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) if (!deckLoadSuccess) { return; } - auto bannerCard = deckLoader->getDeckList()->getBannerCard().name.isEmpty() + auto bannerCard = deckLoader->getDeck().deckList.getBannerCard().name.isEmpty() ? ExactCard() - : CardDatabaseManager::query()->getCard(deckLoader->getDeckList()->getBannerCard()); + : CardDatabaseManager::query()->getCard(deckLoader->getDeck().deckList.getBannerCard()); bannerCardDisplayWidget->setCard(bannerCard); bannerCardDisplayWidget->setFontSize(24); - setFilePath(deckLoader->getLastLoadInfo().fileName); + setFilePath(deckLoader->getDeck().lastLoadInfo.fileName); colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()->getTags()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeck().deckList.getTags()); connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, &DeckPreviewWidget::setTags); bannerCardLabel = new QLabel(this); @@ -91,7 +91,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) bannerCardComboBox = new QComboBox(this); bannerCardComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); bannerCardComboBox->setObjectName("bannerCardComboBox"); - bannerCardComboBox->setCurrentText(deckLoader->getDeckList()->getBannerCard().name); + bannerCardComboBox->setCurrentText(deckLoader->getDeck().deckList.getBannerCard().name); bannerCardComboBox->installEventFilter(new NoScrollFilter()); connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeckPreviewWidget::setBannerCard); @@ -153,7 +153,7 @@ void DeckPreviewWidget::updateTagsVisibility(bool visible) QString DeckPreviewWidget::getColorIdentity() { - QStringList cardList = deckLoader->getDeckList()->getCardList(); + QStringList cardList = deckLoader->getDeck().deckList.getCardList(); if (cardList.isEmpty()) { return {}; } @@ -187,8 +187,8 @@ QString DeckPreviewWidget::getColorIdentity() */ QString DeckPreviewWidget::getDisplayName() const { - return deckLoader->getDeckList()->getName().isEmpty() ? QFileInfo(deckLoader->getLastLoadInfo().fileName).fileName() - : deckLoader->getDeckList()->getName(); + QString deckName = deckLoader->getDeck().deckList.getName(); + return !deckName.isEmpty() ? deckName : QFileInfo(deckLoader->getDeck().lastLoadInfo.fileName).fileName(); } void DeckPreviewWidget::setFilePath(const QString &_filePath) @@ -235,7 +235,7 @@ void DeckPreviewWidget::updateBannerCardComboBox() // Prepare the new items with deduplication QSet> bannerCardSet; - QList cardsInDeck = deckLoader->getDeckList()->getCardNodes(); + QList cardsInDeck = deckLoader->getDeck().deckList.getCardNodes(); for (auto currentCard : cardsInDeck) { for (int k = 0; k < currentCard->getNumber(); ++k) { @@ -269,7 +269,7 @@ void DeckPreviewWidget::updateBannerCardComboBox() bannerCardComboBox->setCurrentIndex(restoredIndex); } else { // Add a placeholder "-" and set it as the current selection - int bannerIndex = bannerCardComboBox->findText(deckLoader->getDeckList()->getBannerCard().name); + int bannerIndex = bannerCardComboBox->findText(deckLoader->getDeck().deckList.getBannerCard().name); if (bannerIndex != -1) { bannerCardComboBox->setCurrentIndex(bannerIndex); } else { @@ -287,7 +287,7 @@ void DeckPreviewWidget::setBannerCard(int /* changedIndex */) { auto [name, id] = bannerCardComboBox->currentData().value>(); CardRef cardRef = {name, id}; - deckLoader->getDeckList()->setBannerCard(cardRef); + deckLoader->getDeck().deckList.setBannerCard(cardRef); deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef)); } @@ -310,7 +310,7 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC void DeckPreviewWidget::setTags(const QStringList &tags) { - deckLoader->getDeckList()->setTags(tags); + deckLoader->getDeck().deckList.setTags(tags); deckLoader->saveToFile(filePath, DeckFileFormat::Cockatrice); } @@ -320,7 +320,7 @@ QMenu *DeckPreviewWidget::createRightClickMenu() menu->setAttribute(Qt::WA_DeleteOnClose); connect(menu->addAction(tr("Open in deck editor")), &QAction::triggered, this, - [this] { emit openDeckEditor(deckLoader); }); + [this] { emit openDeckEditor(deckLoader->getDeck()); }); connect(menu->addAction(tr("Edit Tags")), &QAction::triggered, deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::openTagEditDlg); @@ -334,13 +334,13 @@ QMenu *DeckPreviewWidget::createRightClickMenu() auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard")); connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, true); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeck().deckList, true, true); }); connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, false); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeck().deckList, true, false); }); connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, true); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeck().deckList, false, true); }); connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, false); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeck().deckList, false, false); }); menu->addSeparator(); @@ -376,7 +376,7 @@ void DeckPreviewWidget::addSetBannerCardMenu(QMenu *menu) void DeckPreviewWidget::actRenameDeck() { // read input - const QString oldName = deckLoader->getDeckList()->getName(); + const QString oldName = deckLoader->getDeck().deckList.getName(); bool ok; QString newName = QInputDialog::getText(this, "Rename deck", tr("New name:"), QLineEdit::Normal, oldName, &ok); @@ -385,7 +385,7 @@ void DeckPreviewWidget::actRenameDeck() } // write change - deckLoader->getDeckList()->setName(newName); + deckLoader->getDeck().deckList.setName(newName); deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); // update VDS @@ -416,9 +416,7 @@ void DeckPreviewWidget::actRenameFile() return; } - LoadedDeck::LoadInfo lastLoadInfo = deckLoader->getLastLoadInfo(); - lastLoadInfo.fileName = newFilePath; - deckLoader->setLastLoadInfo(lastLoadInfo); + deckLoader->getDeck().lastLoadInfo.fileName = newFilePath; // update VDS setFilePath(newFilePath); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index 6e6fdb9af..e5975db71 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -51,7 +51,7 @@ public: signals: void deckLoadRequested(const QString &filePath); - void openDeckEditor(DeckLoader *deck); + void openDeckEditor(const LoadedDeck &deck); public slots: void setFilePath(const QString &filePath); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp index 883682749..fdb44dff4 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp @@ -211,7 +211,7 @@ QStringList VisualDeckStorageFolderDisplayWidget::gatherAllTagsFromFlowWidget() // Iterate through all DeckPreviewWidgets for (DeckPreviewWidget *display : flowWidget->findChildren()) { // Get tags from each DeckPreviewWidget - QStringList tags = display->deckLoader->getDeckList()->getTags(); + QStringList tags = display->deckLoader->getDeck().deckList.getTags(); // Add tags to the list while avoiding duplicates allTags.append(tags); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp index cbc78ba50..0eb45cdcc 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp @@ -95,14 +95,17 @@ QList VisualDeckStorageSortWidget::filterFiles(QListdeckLoader->getDeckList()->getName() < widget2->deckLoader->getDeckList()->getName(); + return widget1->deckLoader->getDeck().deckList.getName() < + widget2->deckLoader->getDeck().deckList.getName(); case Alphabetical: return QString::localeAwareCompare(info1.fileName(), info2.fileName()) <= 0; case ByLastModified: return info1.lastModified() > info2.lastModified(); case ByLastLoaded: { - QDateTime time1 = QDateTime::fromString(widget1->deckLoader->getDeckList()->getLastLoadedTimestamp()); - QDateTime time2 = QDateTime::fromString(widget2->deckLoader->getDeckList()->getLastLoadedTimestamp()); + QDateTime time1 = + QDateTime::fromString(widget1->deckLoader->getDeck().deckList.getLastLoadedTimestamp()); + QDateTime time2 = + QDateTime::fromString(widget2->deckLoader->getDeck().deckList.getLastLoadedTimestamp()); return time1 > time2; } } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp index 1686de054..28fd7a5ca 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp @@ -57,7 +57,7 @@ void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QListdeckLoader->getDeckList()->getTags(); + QStringList deckTags = deckPreview->deckLoader->getDeck().deckList.getTags(); bool hasAllSelected = std::all_of(selectedTags.begin(), selectedTags.end(), [&deckTags](const QString &tag) { return deckTags.contains(tag); }); @@ -153,7 +153,7 @@ QSet VisualDeckStorageTagFilterWidget::gatherAllTags() const for (DeckPreviewWidget *widget : deckWidgets) { if (widget->checkVisibility()) { - for (const QString &tag : widget->deckLoader->getDeckList()->getTags()) { + for (const QString &tag : widget->deckLoader->getDeck().deckList.getTags()) { allTags.insert(tag); } } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h index b205cb67e..b13c51700 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -53,7 +53,7 @@ public slots: signals: void bannerCardsRefreshed(); void deckLoadRequested(const QString &filePath); - void openDeckEditor(DeckLoader *deck); + void openDeckEditor(const LoadedDeck &deck); private: QVBoxLayout *layout; diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 4c9922c80..2e135d170 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -499,11 +499,7 @@ QString MainWindow::extractInvalidUsernameMessage(QString &in) if (words.startsWith("\n")) { out += tr("no unacceptable language as specified by these server rules:", "note that the following lines will not be translated"); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) for (QString &line : words.split("\n", Qt::SkipEmptyParts)) { -#else - for (QString &line : words.split("\n", QString::SkipEmptyParts)) { -#endif out += "
  • " + line + "
  • "; } } else { @@ -942,9 +938,25 @@ void MainWindow::startupConfigCheck() const auto reloadOk0 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); }); } - qCInfo(WindowMainStartupShortcutsLog) << "[MainWindow] Migrating shortcuts after update detected."; + qCInfo(WindowMainStartupShortcutsLog) << "Migrating shortcuts after update detected."; SettingsCache::instance().shortcuts().migrateShortcuts(); + if (SettingsCache::instance().getCheckUpdatesOnStartup()) { + if (QString(VERSION_STRING).contains("custom", Qt::CaseInsensitive)) { + qCInfo(WindowMainStartupShortcutsLog) << "Update has changed to custom version, disabling auto update"; + SettingsCache::instance().setCheckUpdatesOnStartup(Qt::Unchecked); + } else { + int channel = 0; + if (QString(VERSION_STRING).contains("beta", Qt::CaseInsensitive)) { + channel = 1; + } + if (SettingsCache::instance().getUpdateReleaseChannelIndex() != channel) { + qCInfo(WindowMainStartupShortcutsLog) << "Update has changed beta state, updating release channel."; + SettingsCache::instance().setUpdateReleaseChannelIndex(channel); + } + } + } + SettingsCache::instance().setClientVersion(VERSION_STRING); } else { // previous config from this version found diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 29dacee74..7092a3fd7 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -260,10 +260,8 @@ int main(int argc, char *argv[]) qCInfo(MainLog) << "MainWindow constructor finished"; ui.setWindowIcon(QPixmap("theme:cockatrice")); -#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) // set name of the app desktop file; used by wayland to load the window icon QGuiApplication::setDesktopFileName("cockatrice"); -#endif SettingsCache::instance().setClientID(generateClientID()); diff --git a/dbconverter/CMakeLists.txt b/dbconverter/CMakeLists.txt deleted file mode 100644 index b2de9dcb8..000000000 --- a/dbconverter/CMakeLists.txt +++ /dev/null @@ -1,133 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(Dbconverter VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") - -# ------------------------ -# Sources -# ------------------------ -set(dbconverter_SOURCES src/main.cpp src/mocks.cpp ${VERSION_STRING_CPP}) - -# ------------------------ -# Qt configuration -# ------------------------ -set(QT_DONT_USE_QTGUI TRUE) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - -# ------------------------ -# Build executable -# ------------------------ -add_executable(dbconverter MACOSX_BUNDLE ${dbconverter_SOURCES}) - -# ------------------------ -# Link libraries -# ------------------------ -target_link_libraries( - dbconverter - PRIVATE libcockatrice_card - PRIVATE libcockatrice_interfaces - PRIVATE libcockatrice_settings - PRIVATE ${DB_CONVERTER_QT_MODULES} -) - -# ------------------------ -# Install rules -# ------------------------ -if(UNIX) - if(APPLE) - set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME}") - set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.cockatrice.${PROJECT_NAME}") - set(MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_NAME}-${PROJECT_VERSION}") - set(MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME}) - set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}) - set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}) - - install(TARGETS dbconverter BUNDLE DESTINATION ./) - else() - # Linux - install(TARGETS dbconverter RUNTIME DESTINATION bin/) - endif() -elseif(WIN32) - install(TARGETS dbconverter RUNTIME DESTINATION ./) -endif() - -# ------------------------ -# Qt plugin handling -# ------------------------ -if(APPLE) - set(plugin_dest_dir dbconverter.app/Contents/Plugins) - set(qtconf_dest_dir dbconverter.app/Contents/Resources) - - install( - DIRECTORY "${QT_PLUGINS_DIR}/" - DESTINATION ${plugin_dest_dir} - COMPONENT Runtime - FILES_MATCHING - PATTERN "*.dSYM" EXCLUDE - PATTERN "*_debug.dylib" EXCLUDE - PATTERN "platforms/*.dylib" - ) - - install( - CODE " - file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] -Plugins = Plugins -Translations = Resources/translations\") - " - COMPONENT Runtime - ) - - install( - CODE " - file(GLOB_RECURSE QTPLUGINS - \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dylib\") - set(BU_CHMOD_BUNDLE_ITEMS ON) - include(BundleUtilities) - fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/dbconverter.app\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR};${MYSQLCLIENT_LIBRARY_DIR}\") - " - COMPONENT Runtime - ) -endif() - -if(WIN32) - set(plugin_dest_dir Plugins) - set(qtconf_dest_dir .) - - install( - DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE}/" - DESTINATION ./ - FILES_MATCHING - PATTERN "*.dll" - ) - - install( - DIRECTORY "${QT_PLUGINS_DIR}/" - DESTINATION ${plugin_dest_dir} - COMPONENT Runtime - FILES_MATCHING - PATTERN "platforms/qdirect2d.dll" - PATTERN "platforms/qminimal.dll" - PATTERN "platforms/qoffscreen.dll" - PATTERN "platforms/qwindows.dll" - ) - - install( - CODE " - file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] -Plugins = Plugins -Translations = Resources/translations\") - " - COMPONENT Runtime - ) - - install( - CODE " - file(GLOB_RECURSE QTPLUGINS - \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dll\") - set(BU_CHMOD_BUNDLE_ITEMS ON) - include(BundleUtilities) - fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/dbconverter.exe\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\") - " - COMPONENT Runtime - ) -endif() diff --git a/dbconverter/src/main.cpp b/dbconverter/src/main.cpp deleted file mode 100644 index 67748fa45..000000000 --- a/dbconverter/src/main.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2019 by Fabio Bas * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#include "main.h" - -#include "mocks.h" -#include "version_string.h" - -#include -#include -#include -#include -#include - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - app.setOrganizationName("Cockatrice"); - app.setApplicationName("Dbconverter"); - app.setApplicationVersion(VERSION_STRING); - - QCommandLineParser parser; - parser.addPositionalArgument("olddb", "Read existing card database from "); - parser.addPositionalArgument("newdb", "Write new card database to "); - parser.addHelpOption(); - parser.addVersionOption(); - parser.process(app); - - QString oldDbPath; - QString newDbPath; - QStringList args = parser.positionalArguments(); - if (args.count() == 2) { - oldDbPath = QFileInfo(args.at(0)).absoluteFilePath(); - newDbPath = QFileInfo(args.at(1)).absoluteFilePath(); - } else { - qCritical() << "Usage: dbconverter "; - parser.showHelp(1); - exit(0); - } - - CardDatabaseConverter *db = new CardDatabaseConverter; - - qInfo() << "---------------------------------------------"; - qInfo() << "Loading cards from" << oldDbPath; - db->loadCardDatabase(oldDbPath); - qInfo() << "---------------------------------------------"; - qInfo() << "Saving cards to" << newDbPath; - db->saveCardDatabase(newDbPath); - qInfo() << "---------------------------------------------"; -} diff --git a/dbconverter/src/main.h b/dbconverter/src/main.h deleted file mode 100644 index d6e734316..000000000 --- a/dbconverter/src/main.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef MAIN_H -#define MAIN_H - -#include -#include -#include - -static const QList kConstructedCounts = {{4, "legal"}, {0, "banned"}}; - -static const QList kSingletonCounts = {{1, "legal"}, {0, "banned"}}; - -class CardDatabaseConverter : public CardDatabase -{ -public: - explicit CardDatabaseConverter() - { - // Replace querier with one that ignores SettingsCache - delete querier; - querier = new CardDatabaseQuerier(this, this, new NoopCardPreferenceProvider()); - }; - - LoadStatus loadCardDatabase(const QString &path) - { - return loader->loadCardDatabase(path); - } - - bool saveCardDatabase(const QString &fileName) - { - CockatriceXml4Parser parser(new NoopCardPreferenceProvider()); - - return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName); - } - - FormatRulesNameMap createDefaultMagicFormats() - { - // Predefined common exceptions - CardCondition superTypeIsBasic; - superTypeIsBasic.field = "type"; - superTypeIsBasic.matchType = "contains"; - superTypeIsBasic.value = "Basic Land"; - - ExceptionRule basicLands; - basicLands.conditions.append(superTypeIsBasic); - - CardCondition anyNumberAllowed; - anyNumberAllowed.field = "text"; - anyNumberAllowed.matchType = "contains"; - anyNumberAllowed.value = "A deck can have any number of"; - - ExceptionRule mayContainAnyNumber; - mayContainAnyNumber.conditions.append(anyNumberAllowed); - - // Map to store default rules - FormatRulesNameMap defaultFormatRulesNameMap; - - // ----------------- Helper lambda to create format ----------------- - auto makeFormat = [&](const QString &name, int minDeck = 60, int maxDeck = -1, int maxSideboardSize = 15, - const QList &allowedCounts = kConstructedCounts) -> FormatRulesPtr { - FormatRulesPtr f(new FormatRules); - f->formatName = name; - f->allowedCounts = allowedCounts; - f->minDeckSize = minDeck; - f->maxDeckSize = maxDeck; - f->maxSideboardSize = maxSideboardSize; - f->exceptions.append(basicLands); - f->exceptions.append(mayContainAnyNumber); - defaultFormatRulesNameMap.insert(name.toLower(), f); - return f; - }; - - // ----------------- Standard formats ----------------- - makeFormat("Standard"); - makeFormat("Modern"); - makeFormat("Legacy"); - makeFormat("Pioneer"); - makeFormat("Historic"); - makeFormat("Timeless"); - makeFormat("Future"); - makeFormat("OldSchool"); - makeFormat("Premodern"); - makeFormat("Pauper"); - makeFormat("Penny"); - - // ----------------- Singleton formats ----------------- - makeFormat("Commander", 100, 100, 15, kSingletonCounts); - makeFormat("Duel", 100, 100, 15, kSingletonCounts); - makeFormat("Brawl", 60, 60, 15, kSingletonCounts); - makeFormat("StandardBrawl", 60, 60, 15, kSingletonCounts); - makeFormat("Oathbreaker", 60, 60, 15, kSingletonCounts); - makeFormat("PauperCommander", 100, 100, 15, kSingletonCounts); - makeFormat("Predh", 100, 100, 15, kSingletonCounts); - - // ----------------- Restricted formats ----------------- - makeFormat("Vintage", 60, -1, 15, {{4, "legal"}, {1, "restricted"}, {0, "banned"}}); - - return defaultFormatRulesNameMap; - } -}; - -#endif diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp deleted file mode 100644 index b97bd09c7..000000000 --- a/dbconverter/src/mocks.cpp +++ /dev/null @@ -1,451 +0,0 @@ - -#include "mocks.h" - -CardDatabaseSettings::CardDatabaseSettings(const QString &settingPath, QObject *parent) - : SettingsManager(settingPath + "cardDatabase.ini", QString(), QString(), parent) -{ -} -void CardDatabaseSettings::setSortKey(QString /* shortName */, unsigned int /* sortKey */) -{ -} -void CardDatabaseSettings::setEnabled(QString /* shortName */, bool /* enabled */) -{ -} -void CardDatabaseSettings::setIsKnown(QString /* shortName */, bool /* isknown */) -{ -} -unsigned int CardDatabaseSettings::getSortKey(QString /* shortName */) -{ - return 0; -}; -bool CardDatabaseSettings::isEnabled(QString /* shortName */) -{ - return true; -}; -bool CardDatabaseSettings::isKnown(QString /* shortName */) -{ - return true; -}; - -QString SettingsCache::getDataPath() -{ - return ""; -} -QString SettingsCache::getSettingsPath() -{ - return ""; -} -void SettingsCache::translateLegacySettings() -{ -} -QString SettingsCache::getSafeConfigPath(QString /* configEntry */, QString defaultPath) const -{ - return defaultPath; -} -QString SettingsCache::getSafeConfigFilePath(QString /* configEntry */, QString defaultPath) const -{ - return defaultPath; -} - -void SettingsCache::setUseTearOffMenus(bool /* _useTearOffMenus */) -{ -} -void SettingsCache::setCardViewInitialRowsMax(int /* _cardViewInitialRowsMax */) -{ -} -void SettingsCache::setCardViewExpandedRowsMax(int /* value */) -{ -} -void SettingsCache::setCloseEmptyCardView(const QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setFocusCardViewSearchBar(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setKnownMissingFeatures(const QString & /* _knownMissingFeatures */) -{ -} -void SettingsCache::setCardInfoViewMode(const int /* _viewMode */) -{ -} -void SettingsCache::setHighlightWords(const QString & /* _highlightWords */) -{ -} -void SettingsCache::setMasterVolume(int /* _masterVolume */) -{ -} -void SettingsCache::setLeftJustified(const QT_STATE_CHANGED_T /* _leftJustified */) -{ -} -void SettingsCache::setCardScaling(const QT_STATE_CHANGED_T /* _scaleCards */) -{ -} -void SettingsCache::setStackCardOverlapPercent(const int /* _verticalCardOverlapPercent */) -{ -} -void SettingsCache::setShowMessagePopups(const QT_STATE_CHANGED_T /* _showMessagePopups */) -{ -} -void SettingsCache::setShowMentionPopups(const QT_STATE_CHANGED_T /* _showMentionPopus */) -{ -} -void SettingsCache::setRoomHistory(const QT_STATE_CHANGED_T /* _roomHistory */) -{ -} -void SettingsCache::setLang(const QString & /* _lang */) -{ -} -void SettingsCache::setShowTipsOnStartup(bool /* _showTipsOnStartup */) -{ -} -void SettingsCache::setSeenTips(const QList & /* _seenTips */) -{ -} -void SettingsCache::setDeckPath(const QString & /* _deckPath */) -{ -} -void SettingsCache::setFiltersPath(const QString & /*_filtersPath */) -{ -} -void SettingsCache::setReplaysPath(const QString & /* _replaysPath */) -{ -} -void SettingsCache::setThemesPath(const QString & /* _themesPath */) -{ -} -void SettingsCache::setPicsPath(const QString & /* _picsPath */) -{ -} -void SettingsCache::setCardDatabasePath(const QString & /* _cardDatabasePath */) -{ -} -void SettingsCache::setCustomCardDatabasePath(const QString & /* _customCardDatabasePath */) -{ -} -void SettingsCache::setSpoilerDatabasePath(const QString & /* _spoilerDatabasePath */) -{ -} -void SettingsCache::setTokenDatabasePath(const QString & /* _tokenDatabasePath */) -{ -} -void SettingsCache::setThemeName(const QString & /* _themeName */) -{ -} -void SettingsCache::setHomeTabBackgroundSource(const QString & /* _backgroundSource */) -{ -} -void SettingsCache::setHomeTabBackgroundShuffleFrequency(int /* frequency */) -{ -} -void SettingsCache::setTabVisualDeckStorageOpen(bool /*value*/) -{ -} -void SettingsCache::setTabServerOpen(bool /*value*/) -{ -} -void SettingsCache::setTabAccountOpen(bool /*value*/) -{ -} -void SettingsCache::setTabDeckStorageOpen(bool /*value*/) -{ -} -void SettingsCache::setTabReplaysOpen(bool /*value*/) -{ -} -void SettingsCache::setTabAdminOpen(bool /*value*/) -{ -} -void SettingsCache::setTabLogOpen(bool /*value*/) -{ -} -void SettingsCache::setPicDownload(QT_STATE_CHANGED_T /* _picDownload */) -{ -} -void SettingsCache::setShowStatusBar(bool /* value */) -{ -} -void SettingsCache::setNotificationsEnabled(QT_STATE_CHANGED_T /* _notificationsEnabled */) -{ -} -void SettingsCache::setSpectatorNotificationsEnabled(QT_STATE_CHANGED_T /* _spectatorNotificationsEnabled */) -{ -} -void SettingsCache::setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T /* _buddyConnectNotificationsEnabled */) -{ -} -void SettingsCache::setDoubleClickToPlay(QT_STATE_CHANGED_T /* _doubleClickToPlay */) -{ -} -void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T /* _clickPlaysAllSelected */) -{ -} -void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T /* _playToStack */) -{ -} -void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T /* _doNotDeleteArrowsInSubPhases */) -{ -} -void SettingsCache::setStartingHandSize(int /* _startingHandSize */) -{ -} -void SettingsCache::setAnnotateTokens(QT_STATE_CHANGED_T /* _annotateTokens */) -{ -} -void SettingsCache::setTabGameSplitterSizes(const QByteArray & /* _tabGameSplitterSizes */) -{ -} -void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T /* _showShortcuts */) -{ -} -void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T /* _displayCardNames */) -{ -} -void SettingsCache::setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T /* _displayCardNames */) -{ -} -void SettingsCache::setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T /* _bumpSetsWithCardsInDeckToTop */) -{ -} -void SettingsCache::setPrintingSelectorSortOrder(int /* _printingSelectorSortOrder */) -{ -} -void SettingsCache::setPrintingSelectorCardSize(int /* _printingSelectorCardSize */) -{ -} -void SettingsCache::setIncludeRebalancedCards(bool /* _includeRebalancedCards */) -{ -} -void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */) -{ -} -void SettingsCache::setDeckEditorBannerCardComboBoxVisible( - QT_STATE_CHANGED_T /* _deckEditorBannerCardComboBoxVisible */) -{ -} -void SettingsCache::setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T /* _deckEditorTagsWidgetVisible */) -{ -} -void SettingsCache::setVisualDeckStorageSortingOrder(int /* _visualDeckStorageSortingOrder */) -{ -} -void SettingsCache::setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T /* _showTags */) -{ -} -void SettingsCache::setVisualDeckStorageDefaultTagsList(QStringList /* _defaultTagsList */) -{ -} -void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T /* _showBannerCardComboBox */) -{ -} -void SettingsCache::setVisualDeckStorageShowTagsOnDeckPreviews(QT_STATE_CHANGED_T /* _showTags */) -{ -} -void SettingsCache::setVisualDeckStorageCardSize(int /* _visualDeckStorageCardSize */) -{ -} -void SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities( - QT_STATE_CHANGED_T /* _visualDeckStorageDrawUnusedColorIdentities */) -{ -} -void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( - int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) -{ -} -void SettingsCache::setVisualDeckStorageTooltipType(int /* value */) -{ -} -void SettingsCache::setVisualDeckStoragePromptForConversion(bool /* _visualDeckStoragePromptForConversion */) -{ -} -void SettingsCache::setVisualDeckStorageAlwaysConvert(bool /* _visualDeckStorageAlwaysConvert */) -{ -} -void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setDefaultDeckEditorType(int /* value */) -{ -} -void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T /* _enabled */) -{ -} -void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int /* _amount */) -{ -} -void SettingsCache::setVisualDeckEditorSampleHandSize(int /* _amount */) -{ -} -void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) -{ -} -void SettingsCache::setInvertVerticalCoordinate(QT_STATE_CHANGED_T /* _invertVerticalCoordinate */) -{ -} -void SettingsCache::setMinPlayersForMultiColumnLayout(int /* _minPlayersForMultiColumnLayout */) -{ -} -void SettingsCache::setTapAnimation(QT_STATE_CHANGED_T /* _tapAnimation */) -{ -} -void SettingsCache::setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T /* _autoRotateSidewaysLayoutCards */) -{ -} -void SettingsCache::setOpenDeckInNewTab(QT_STATE_CHANGED_T /* _openDeckInNewTab */) -{ -} -void SettingsCache::setRewindBufferingMs(int /* _rewindBufferingMs */) -{ -} -void SettingsCache::setChatMention(QT_STATE_CHANGED_T /* _chatMention */) -{ -} -void SettingsCache::setChatMentionCompleter(const QT_STATE_CHANGED_T /* _enableMentionCompleter */) -{ -} -void SettingsCache::setChatMentionForeground(QT_STATE_CHANGED_T /* _chatMentionForeground */) -{ -} -void SettingsCache::setChatHighlightForeground(QT_STATE_CHANGED_T /* _chatHighlightForeground */) -{ -} -void SettingsCache::setChatMentionColor(const QString & /* _chatMentionColor */) -{ -} -void SettingsCache::setChatHighlightColor(const QString & /* _chatHighlightColor */) -{ -} -void SettingsCache::setZoneViewGroupByIndex(int /* _zoneViewGroupByIndex */) -{ -} -void SettingsCache::setZoneViewSortByIndex(int /* _zoneViewSortByIndex */) -{ -} -void SettingsCache::setZoneViewPileView(QT_STATE_CHANGED_T /* _zoneViewPileView */) -{ -} -void SettingsCache::setSoundEnabled(QT_STATE_CHANGED_T /* _soundEnabled */) -{ -} -void SettingsCache::setSoundThemeName(const QString & /* _soundThemeName */) -{ -} -void SettingsCache::setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T /* _ignoreUnregisteredUsers */) -{ -} -void SettingsCache::setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T /* _ignoreUnregisteredUserMessages */) -{ -} -void SettingsCache::setMainWindowGeometry(const QByteArray & /* _mainWindowGeometry */) -{ -} -void SettingsCache::setTokenDialogGeometry(const QByteArray & /* _tokenDialogGeometry */) -{ -} -void SettingsCache::setSetsDialogGeometry(const QByteArray & /* _setsDialogGeometry */) -{ -} -void SettingsCache::setPixmapCacheSize(const int /* _pixmapCacheSize */) -{ -} -void SettingsCache::setNetworkCacheSizeInMB(const int /* _networkCacheSize */) -{ -} -void SettingsCache::setNetworkRedirectCacheTtl(const int /* _redirectCacheTtl */) -{ -} -void SettingsCache::setClientID(const QString & /* _clientID */) -{ -} -void SettingsCache::setClientVersion(const QString & /* _clientVersion */) -{ -} -QStringList SettingsCache::getCountries() const -{ - static QStringList countries = QStringList() << "us"; - return countries; -} -void SettingsCache::setGameDescription(const QString /* _gameDescription */) -{ -} -void SettingsCache::setMaxPlayers(const int /* _maxPlayers */) -{ -} -void SettingsCache::setGameTypes(const QString /* _gameTypes */) -{ -} -void SettingsCache::setOnlyBuddies(const bool /* _onlyBuddies */) -{ -} -void SettingsCache::setOnlyRegistered(const bool /* _onlyRegistered */) -{ -} -void SettingsCache::setSpectatorsAllowed(const bool /* _spectatorsAllowed */) -{ -} -void SettingsCache::setSpectatorsNeedPassword(const bool /* _spectatorsNeedPassword */) -{ -} -void SettingsCache::setSpectatorsCanTalk(const bool /* _spectatorsCanTalk */) -{ -} -void SettingsCache::setSpectatorsCanSeeEverything(const bool /* _spectatorsCanSeeEverything */) -{ -} -void SettingsCache::setCreateGameAsSpectator(const bool /* _createGameAsSpectator */) -{ -} -void SettingsCache::setDefaultStartingLifeTotal(const int /* _startingLifeTotal */) -{ -} -void SettingsCache::setShareDecklistsOnLoad(const bool /* _shareDecklistsOnLoad */) -{ -} -void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */) -{ -} -void SettingsCache::setCheckUpdatesOnStartup(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setStartupCardUpdateCheckPromptForUpdate(bool /* value */) -{ -} -void SettingsCache::setStartupCardUpdateCheckAlwaysUpdate(bool /* value */) -{ -} -void SettingsCache::setCardUpdateCheckInterval(int /* value */) -{ -} -void SettingsCache::setLastCardUpdateCheck(QDate /* value */) -{ -} -void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T /* _notifyaboutupdate */) -{ -} -void SettingsCache::setNotifyAboutNewVersion(QT_STATE_CHANGED_T /* _notifyaboutnewversion */) -{ -} -void SettingsCache::setDownloadSpoilerStatus(bool /* _spoilerStatus */) -{ -} -void SettingsCache::setUpdateReleaseChannelIndex(int /* value */) -{ -} -void SettingsCache::setMaxFontSize(int /* _max */) -{ -} -void SettingsCache::setRoundCardCorners(bool /* _roundCardCorners */) -{ -} - -void CardPictureLoader::clearPixmapCache(CardInfoPtr /* card */) -{ -} diff --git a/dbconverter/src/mocks.h b/dbconverter/src/mocks.h deleted file mode 100644 index 929092bd7..000000000 --- a/dbconverter/src/mocks.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Beware of this preprocessor hack used to redefine the settingCache class - * instead of including it and all of its dependencies. - * Always set header guards of mocked objects before including any headers - * with mocked objects. - */ - -#include -#include - -#define PICTURELOADER_H - -#include "../../cockatrice/src/client/settings/cache_settings.h" - -#include -#include - -extern SettingsCache *settingsCache; - -class CardPictureLoader -{ -public: - static void clearPixmapCache(CardInfoPtr card); -}; diff --git a/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md b/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md index 6c5e3512b..7b4bdafc3 100644 --- a/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md +++ b/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md @@ -1 +1,16 @@ -@page card_database_schema_and_parsing Card Database Schema and Parsing \ No newline at end of file +@page card_database_schema_and_parsing Card Database Schema and Parsing + +# Card Database Schemas + +Cockatrice uses `XML files` to store information of available cards to be used in the app (`cards.xml`). +The token file follows the schema of the card database (`tokens.xml`), too. + +Saved decks (`.xml`) use a different schema. + +- [XSD Schema for `Card Databases`](https://github.com/Cockatrice/Cockatrice/blob/master/doc/carddatabase_v4/cards.xsd) v4 +- [XSD Schema for `Decks`](https://github.com/Cockatrice/Cockatrice/blob/master/doc/deck.xsd) v1 + + +# Card Database Parsing + +TODO diff --git a/doc/doxygen/extra-pages/index.md b/doc/doxygen/extra-pages/index.md index 66113117f..c370a6fc2 100644 --- a/doc/doxygen/extra-pages/index.md +++ b/doc/doxygen/extra-pages/index.md @@ -5,4 +5,6 @@ This is the **main landing page** of the Cockatrice documentation. - Go to the @subpage user_reference page -- Or check out the @subpage developer_reference \ No newline at end of file +- Review the @subpage developer_reference + +Or check out the [Cockatrice Webpage](https://cockatrice.github.io/). diff --git a/doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md b/doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md index bfe578afd..153320c3d 100644 --- a/doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md +++ b/doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md @@ -8,10 +8,8 @@ by selecting "Deck Editor" or "Visual Deck Editor" under the "Tabs" application # Further References -See @ref editing_decks for information on how to modify the attributes and contents of a deck in the Deck Editor +- See @ref editing_decks for information on how to modify the attributes and contents of a deck in the Deck Editor widgets. - -See @ref exporting_decks for information on how to store and persist your deck either in-client or to external +- See @ref exporting_decks for information on how to store and persist your deck either in-client or to external services. - -See @ref importing_decks for information on how to import existing decks either in-client or from external services \ No newline at end of file +- See @ref importing_decks for information on how to import existing decks either in-client or from external services diff --git a/doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks.md b/doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks.md index 030811306..37b613461 100644 --- a/doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks.md +++ b/doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks.md @@ -1,7 +1,6 @@ @page editing_decks Editing Decks -@subpage editing_decks_classic + - @subpage editing_decks_classic + - @subpage editing_decks_visual -@subpage editing_decks_visual - -@subpage editing_decks_printings \ No newline at end of file + - @subpage editing_decks_printings diff --git a/doc/doxygen/extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md index 8a5c88c37..33104991a 100644 --- a/doc/doxygen/extra-pages/user_documentation/index.md +++ b/doc/doxygen/extra-pages/user_documentation/index.md @@ -1,13 +1,9 @@ @page user_reference User Reference -@subpage search_syntax_help - -@subpage deck_search_syntax_help - -@subpage creating_decks - -@subpage importing_decks - -@subpage editing_decks - -@subpage exporting_decks \ No newline at end of file +- @subpage search_syntax_help +- @subpage deck_search_syntax_help + +- @subpage creating_decks +- @subpage importing_decks +- @subpage editing_decks +- @subpage exporting_decks diff --git a/format.sh b/format.sh index 51fccbdcf..f8c183dfc 100755 --- a/format.sh +++ b/format.sh @@ -14,7 +14,6 @@ cd "${BASH_SOURCE%/*}/" || exit 2 # could not find path, this could happen with # defaults include=("cockatrice/src" \ -"dbconverter/src" \ "libcockatrice_card" \ "libcockatrice_deck_list" \ "libcockatrice_network" \ diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp index 3054a10cf..ae73ef2db 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info.cpp @@ -75,7 +75,17 @@ QString CardInfo::getCorrectedName() const return result.remove(rmrx).replace(spacerx, space); } -void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo _info) +bool CardInfo::isLegalInFormat(const QString &format) const +{ + if (format.isEmpty()) { + return true; + } + + QString formatLegality = getProperty("format-" + format); + return formatLegality == "legal" || formatLegality == "restricted"; +} + +void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo &_info) { if (!_set->contains(smartThis)) { _set->append(smartThis); @@ -156,7 +166,7 @@ QString CardInfo::simplifyName(const QString &name) return simpleName; } -const QChar CardInfo::getColorChar() const +QChar CardInfo::getColorChar() const { QString colors = getColors(); switch (colors.size()) { @@ -178,7 +188,7 @@ void CardInfo::resetReverseRelatedCards2Me() } // Back-compatibility methods. Remove ASAP -const QString CardInfo::getCardType() const +QString CardInfo::getCardType() const { return getProperty(Mtg::CardType); } @@ -186,11 +196,11 @@ void CardInfo::setCardType(const QString &value) { setProperty(Mtg::CardType, value); } -const QString CardInfo::getCmc() const +QString CardInfo::getCmc() const { return getProperty(Mtg::ConvertedManaCost); } -const QString CardInfo::getColors() const +QString CardInfo::getColors() const { return getProperty(Mtg::Colors); } @@ -198,19 +208,19 @@ void CardInfo::setColors(const QString &value) { setProperty(Mtg::Colors, value); } -const QString CardInfo::getLoyalty() const +QString CardInfo::getLoyalty() const { return getProperty(Mtg::Loyalty); } -const QString CardInfo::getMainCardType() const +QString CardInfo::getMainCardType() const { return getProperty(Mtg::MainCardType); } -const QString CardInfo::getManaCost() const +QString CardInfo::getManaCost() const { return getProperty(Mtg::ManaCost); } -const QString CardInfo::getPowTough() const +QString CardInfo::getPowTough() const { return getProperty(Mtg::PowTough); } diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index 00e8fec37..b81cf51dc 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -267,18 +267,18 @@ public: } //@} - [[nodiscard]] const QChar getColorChar() const; + [[nodiscard]] QChar getColorChar() const; /** @name Legacy/Convenience Property Accessors */ //@{ - [[nodiscard]] const QString getCardType() const; + [[nodiscard]] QString getCardType() const; void setCardType(const QString &value); - [[nodiscard]] const QString getCmc() const; - [[nodiscard]] const QString getColors() const; + [[nodiscard]] QString getCmc() const; + [[nodiscard]] QString getColors() const; void setColors(const QString &value); - [[nodiscard]] const QString getLoyalty() const; - [[nodiscard]] const QString getMainCardType() const; - [[nodiscard]] const QString getManaCost() const; - [[nodiscard]] const QString getPowTough() const; + [[nodiscard]] QString getLoyalty() const; + [[nodiscard]] QString getMainCardType() const; + [[nodiscard]] QString getManaCost() const; + [[nodiscard]] QString getPowTough() const; void setPowTough(const QString &value); //@} @@ -291,6 +291,15 @@ public: */ [[nodiscard]] QString getCorrectedName() const; + /** + * @brief Checks if the card is legal in the given format. + * A card is considered legal in a format if its properties map contains an entry for "format-", with value + * "legal" or "restricted". + * @param format The format's name. If empty, will always return true. + * @return Whether the card is legal in the given format. + */ + [[nodiscard]] bool isLegalInFormat(const QString &format) const; + /** * @brief Adds a printing to a specific set. * @@ -299,7 +308,7 @@ public: * @param _set The set to which the card should be added. * @param _info Optional printing information. */ - void addToSet(const CardSetPtr &_set, PrintingInfo _info = PrintingInfo()); + void addToSet(const CardSetPtr &_set, const PrintingInfo &_info = PrintingInfo()); /** * @brief Combines legality properties from a provided map. diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.cpp b/libcockatrice_card/libcockatrice/card/database/card_database.cpp index de84ad814..5c4b408b3 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database.cpp @@ -21,7 +21,7 @@ CardDatabase::CardDatabase(QObject *parent, qRegisterMetaType("CardSetPtr"); // create loader and wire it up - loader = new CardDatabaseLoader(this, this, pathProvider, prefs); + loader = new CardDatabaseLoader(this, this, pathProvider, prefs, setPriorityController); // re-emit loader signals (so other code doesn't need to know about internals) connect(loader, &CardDatabaseLoader::loadingFinished, this, &CardDatabase::cardDatabaseLoadingFinished); connect(loader, &CardDatabaseLoader::loadingFailed, this, &CardDatabase::cardDatabaseLoadingFailed); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp index 91bb4c741..716477a59 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp @@ -12,12 +12,13 @@ CardDatabaseLoader::CardDatabaseLoader(QObject *parent, CardDatabase *db, ICardDatabasePathProvider *_pathProvider, - ICardPreferenceProvider *_preferenceProvider) + ICardPreferenceProvider *_preferenceProvider, + ICardSetPriorityController *_priorityController) : QObject(parent), database(db), pathProvider(_pathProvider) { // instantiate available parsers here and connect them to the database - availableParsers << new CockatriceXml4Parser(_preferenceProvider); - availableParsers << new CockatriceXml3Parser; + availableParsers << new CockatriceXml4Parser(_preferenceProvider, _priorityController); + availableParsers << new CockatriceXml3Parser(_priorityController); for (auto *p : availableParsers) { // connect parser outputs to the database adders diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h index f4e690428..861cc95b0 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h @@ -6,6 +6,7 @@ #include #include #include +#include inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading"); inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure"); @@ -52,7 +53,8 @@ public: explicit CardDatabaseLoader(QObject *parent, CardDatabase *db, ICardDatabasePathProvider *pathProvider, - ICardPreferenceProvider *preferenceProvider); + ICardPreferenceProvider *preferenceProvider, + ICardSetPriorityController *_priorityController); /** @brief Destructor cleans up allocated parsers. */ ~CardDatabaseLoader() override; diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp index b2a675b99..26e515a2d 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp @@ -328,11 +328,7 @@ QMap CardDatabaseQuerier::getAllSubCardTypesWithCount() const QStringList parts = type.split(" — "); if (parts.size() > 1) { // Ensure there are subtypes -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts); -#else - QStringList subtypes = parts[1].split(" ", QString::SkipEmptyParts); -#endif for (const QString &subtype : subtypes) { typeCounts[subtype]++; diff --git a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.cpp b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.cpp index e9686f816..f7e6bbfcf 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.cpp @@ -4,6 +4,10 @@ SetNameMap ICardDatabaseParser::sets; +ICardDatabaseParser::ICardDatabaseParser(ICardSetPriorityController *_cardSetPriorityController) + : cardSetPriorityController(_cardSetPriorityController) +{ +} void ICardDatabaseParser::clearSetlist() { sets.clear(); @@ -19,7 +23,7 @@ CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName, return sets.value(setName); } - CardSetPtr newSet = CardSet::newInstance(new NoopCardSetPriorityController(), setName); + CardSetPtr newSet = CardSet::newInstance(cardSetPriorityController, setName); newSet->setLongName(longName); newSet->setSetType(setType); newSet->setReleaseDate(releaseDate); diff --git a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h index 93d46a3cc..a8eceab5a 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h @@ -20,6 +20,7 @@ class ICardDatabaseParser : public QObject { Q_OBJECT public: + ICardDatabaseParser(ICardSetPriorityController *cardSetPriorityController); ~ICardDatabaseParser() override = default; /** @@ -59,6 +60,7 @@ public: protected: /** @brief Cached global list of sets shared between all parsers. */ static SetNameMap sets; + ICardSetPriorityController *cardSetPriorityController; /** * @brief Internal helper to add a set to the global set cache. diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp index 9df396c74..ba27d63c4 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp @@ -14,6 +14,11 @@ #define COCKATRICE_XML3_SCHEMALOCATION \ "https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v3/cards.xsd" +CockatriceXml3Parser::CockatriceXml3Parser(ICardSetPriorityController *_cardSetPriorityController) + : ICardDatabaseParser(_cardSetPriorityController) +{ +} + bool CockatriceXml3Parser::getCanParseFile(const QString &fileName, QIODevice &device) { qCInfo(CockatriceXml3Log) << "Trying to parse: " << fileName; diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h index c3a261739..a01b705aa 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h @@ -26,7 +26,7 @@ class CockatriceXml3Parser : public ICardDatabaseParser { Q_OBJECT public: - CockatriceXml3Parser() = default; + CockatriceXml3Parser(ICardSetPriorityController *cardSetPriorityController); ~CockatriceXml3Parser() override = default; /** diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp index 0d46aad0f..cc0220526 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp @@ -14,8 +14,9 @@ #define COCKATRICE_XML4_SCHEMALOCATION \ "https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v4/cards.xsd" -CockatriceXml4Parser::CockatriceXml4Parser(ICardPreferenceProvider *_cardPreferenceProvider) - : cardPreferenceProvider(_cardPreferenceProvider) +CockatriceXml4Parser::CockatriceXml4Parser(ICardPreferenceProvider *_cardPreferenceProvider, + ICardSetPriorityController *_cardSetPriorityController) + : ICardDatabaseParser(_cardSetPriorityController), cardPreferenceProvider(_cardPreferenceProvider) { } diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h index 5b47e9090..189e79535 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h @@ -29,7 +29,8 @@ class CockatriceXml4Parser : public ICardDatabaseParser { Q_OBJECT public: - explicit CockatriceXml4Parser(ICardPreferenceProvider *cardPreferenceProvider); + explicit CockatriceXml4Parser(ICardPreferenceProvider *cardPreferenceProvider, + ICardSetPriorityController *cardSetPriorityController); ~CockatriceXml4Parser() override = default; /** diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index 5dffff3f7..a7dd01702 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -27,6 +27,8 @@ add_library( libcockatrice/deck_list/tree/inner_deck_list_node.cpp libcockatrice/deck_list/deck_list.cpp libcockatrice/deck_list/deck_list_history_manager.cpp + libcockatrice/deck_list/deck_list_node_tree.cpp + libcockatrice/deck_list/deck_list_node_tree.h ) add_dependencies(libcockatrice_deck_list libcockatrice_protocol) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 09326e619..b453a57ef 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -83,26 +83,23 @@ bool DeckList::Metadata::isEmpty() const } DeckList::DeckList() -{ - root = new InnerDecklistNode; -} - -DeckList::DeckList(const DeckList &other) - : metadata(other.metadata), sideboardPlans(other.sideboardPlans), root(new InnerDecklistNode(other.getRoot())), - cachedDeckHash(other.cachedDeckHash) { } DeckList::DeckList(const QString &nativeString) { - root = new InnerDecklistNode; loadFromString_Native(nativeString); } +DeckList::DeckList(const Metadata &metadata, + const DecklistNodeTree &tree, + const QMap &sideboardPlans) + : metadata(metadata), sideboardPlans(sideboardPlans), tree(tree) +{ +} + DeckList::~DeckList() { - delete root; - QMapIterator i(sideboardPlans); while (i.hasNext()) delete i.next().value(); @@ -152,8 +149,7 @@ bool DeckList::readElement(QXmlStreamReader *xml) } } } else if (childName == "zone") { - InnerDecklistNode *newZone = getZoneObjFromName(xml->attributes().value("name").toString()); - newZone->readElement(xml); + tree.readZoneElement(xml); } else if (childName == "sideboard_plan") { SideboardPlan *newSideboardPlan = new SideboardPlan; if (newSideboardPlan->readElement(xml)) { @@ -168,7 +164,7 @@ bool DeckList::readElement(QXmlStreamReader *xml) return true; } -void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metadata) +static void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metadata) { xml->writeTextElement("lastLoadedTimestamp", metadata.lastLoadedTimestamp); xml->writeTextElement("deckname", metadata.name); @@ -195,9 +191,7 @@ void DeckList::write(QXmlStreamWriter *xml) const writeMetadata(xml, metadata); // Write zones - for (int i = 0; i < root->size(); i++) { - root->at(i)->writeElement(xml); - } + tree.write(xml); // Write sideboard plans QMapIterator i(sideboardPlans); @@ -456,25 +450,13 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) QString zoneName = sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; // make new entry in decklist - new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), -1, setCode, collectorNumber); + tree.addCard(cardName, amount, zoneName, -1, setCode, collectorNumber); } refreshDeckHash(); return true; } -InnerDecklistNode *DeckList::getZoneObjFromName(const QString &zoneName) -{ - for (int i = 0; i < root->size(); i++) { - auto *node = dynamic_cast(root->at(i)); - if (node->getName() == zoneName) { - return node; - } - } - - return new InnerDecklistNode(zoneName, root); -} - bool DeckList::loadFromFile_Plain(QIODevice *device) { QTextStream in(device); @@ -519,7 +501,7 @@ QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappe */ void DeckList::cleanList(bool preserveMetadata) { - root->clearTree(); + tree.clear(); if (!preserveMetadata) { metadata = {}; } @@ -528,7 +510,7 @@ void DeckList::cleanList(bool preserveMetadata) QStringList DeckList::getCardList() const { - auto nodes = getCardNodes(); + auto nodes = tree.getCardNodes(); QStringList result; std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), [](auto node) { return node->getName(); }); @@ -538,7 +520,7 @@ QStringList DeckList::getCardList() const QList DeckList::getCardRefList() const { - auto nodes = getCardNodes(); + auto nodes = tree.getCardNodes(); QList result; std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), @@ -547,42 +529,19 @@ QList DeckList::getCardRefList() const return result; } -QList DeckList::getCardNodes(const QStringList &restrictToZones) const +QList DeckList::getCardNodes(const QSet &restrictToZones) const { - QList result; - - auto zoneNodes = getZoneNodes(); - for (auto *zoneNode : zoneNodes) { - if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) { - continue; - } - for (auto *cardNode : *zoneNode) { - auto *cardCardNode = dynamic_cast(cardNode); - if (cardCardNode != nullptr) { - result.append(cardCardNode); - } - } - } - - return result; + return tree.getCardNodes(restrictToZones); } QList DeckList::getZoneNodes() const { - QList zones; - for (auto *node : *root) { - InnerDecklistNode *currentZone = dynamic_cast(node); - if (!currentZone) - continue; - zones.append(currentZone); - } - - return zones; + return tree.getZoneNodes(); } int DeckList::getSideboardSize() const { - auto cards = getCardNodes({DECK_ZONE_SIDE}); + auto cards = tree.getCardNodes({DECK_ZONE_SIDE}); int size = 0; for (auto card : cards) { @@ -594,93 +553,18 @@ int DeckList::getSideboardSize() const DecklistCardNode *DeckList::addCard(const QString &cardName, const QString &zoneName, - const int position, + int position, const QString &cardSetName, const QString &cardSetCollectorNumber, const QString &cardProviderId, - const bool formatLegal) + bool formatLegal) { - auto *zoneNode = dynamic_cast(root->findChild(zoneName)); - if (zoneNode == nullptr) { - zoneNode = new InnerDecklistNode(zoneName, root); - } - - auto *node = new DecklistCardNode(cardName, 1, zoneNode, position, cardSetName, cardSetCollectorNumber, - cardProviderId, formatLegal); + auto node = + tree.addCard(cardName, 1, zoneName, position, cardSetName, cardSetCollectorNumber, cardProviderId, formatLegal); refreshDeckHash(); - return node; } -bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode) -{ - if (node == root) { - return true; - } - - bool updateHash = false; - if (rootNode == nullptr) { - rootNode = root; - updateHash = true; - } - - int index = rootNode->indexOf(node); - if (index != -1) { - delete rootNode->takeAt(index); - - if (rootNode->empty()) { - deleteNode(rootNode, rootNode->getParent()); - } - - if (updateHash) { - refreshDeckHash(); - } - - return true; - } - - for (int i = 0; i < rootNode->size(); i++) { - auto *inner = dynamic_cast(rootNode->at(i)); - if (inner) { - if (deleteNode(node, inner)) { - if (updateHash) { - refreshDeckHash(); - } - - return true; - } - } - } - - return false; -} - -static QString computeDeckHash(const DeckList &deckList) -{ - auto mainDeckNodes = deckList.getCardNodes({DECK_ZONE_MAIN}); - auto sideDeckNodes = deckList.getCardNodes({DECK_ZONE_SIDE}); - - static auto nodesToCardList = [](const QList &nodes, const QString &prefix = {}) { - QStringList result; - for (auto node : nodes) { - for (int i = 0; i < node->getNumber(); ++i) { - result.append(prefix + node->getName().toLower()); - } - } - return result; - }; - - QStringList cardList = nodesToCardList(mainDeckNodes) + nodesToCardList(sideDeckNodes, "SB:"); - - cardList.sort(); - QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); - quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) + - (((quint64)(unsigned char)deckHashArray[1]) << 24) + - (((quint64)(unsigned char)deckHashArray[2] << 16)) + - (((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4]; - return QString::number(number, 32).rightJustified(8, '0'); -} - /** * Gets the deck hash. * The hash is computed on the first call to this method, and is cached until the decklist is modified. @@ -693,7 +577,7 @@ QString DeckList::getDeckHash() const return cachedDeckHash; } - cachedDeckHash = computeDeckHash(*this); + cachedDeckHash = tree.computeDeckHash(); return cachedDeckHash; } @@ -710,15 +594,7 @@ void DeckList::refreshDeckHash() */ void DeckList::forEachCard(const std::function &func) const { - // Support for this is only possible if the internal structure - // doesn't get more complicated. - for (int i = 0; i < root->size(); i++) { - InnerDecklistNode *node = dynamic_cast(root->at(i)); - for (int j = 0; j < node->size(); j++) { - DecklistCardNode *card = dynamic_cast(node->at(j)); - func(node, card); - } - } + tree.forEachCard(func); } DeckListMemento DeckList::createMemento(const QString &reason) const diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index e4206ebc6..e8c57be67 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -11,6 +11,7 @@ #define DECKLIST_H #include "deck_list_memento.h" +#include "deck_list_node_tree.h" #include "tree/inner_deck_list_node.h" #include @@ -107,14 +108,14 @@ public: * - Provide hashing for deck identity (deck hash). * * ### Ownership: - * - Owns the root `InnerDecklistNode` tree. + * - Owns the `DecklistNodeTree`. * - Owns `SideboardPlan` instances stored in `sideboardPlans`. * * ### Example workflow: * ``` * DeckList deck; * deck.setName("Mono Red Aggro"); - * deck.addCard("Lightning Bolt", "main", -1); + * deck.addCard("Lightning Bolt", "main"); * deck.addTag("Aggro"); * deck.saveToFile_Native(device); * ``` @@ -140,7 +141,7 @@ public: private: Metadata metadata; ///< Deck metadata that is stored in the deck file QMap sideboardPlans; ///< Named sideboard plans. - InnerDecklistNode *root; ///< Root of the deck tree (zones + cards). + DecklistNodeTree tree; ///< The deck tree (zones + cards). /** * @brief Cached deck hash, recalculated lazily. @@ -148,9 +149,6 @@ private: */ mutable QString cachedDeckHash; - // Helpers for traversing the tree - InnerDecklistNode *getZoneObjFromName(const QString &zoneName); - public: /// @name Metadata setters ///@{ @@ -190,16 +188,28 @@ public: /// @brief Construct an empty deck. explicit DeckList(); - /// @brief Copy constructor (deep copies the node tree) - DeckList(const DeckList &other); /// @brief Construct from a serialized native-format string. explicit DeckList(const QString &nativeString); + /// @brief Construct from components + DeckList(const Metadata &metadata, + const DecklistNodeTree &tree, + const QMap &sideboardPlans = {}); virtual ~DeckList(); + /** + * @brief Gets a pointer to the underlying node tree. + * Note: DO NOT call this method unless the object needs to have access to the underlying model. + * For now, only the DeckListModel should be calling this. + */ + DecklistNodeTree *getTree() + { + return &tree; + } + /// @name Metadata getters /// The individual metadata getters still exist for backwards compatibility. - /// TODO: Figure out when we can remove them. ///@{ + //! \todo Figure out when we can remove them. const Metadata &getMetadata() const { return metadata; @@ -270,25 +280,21 @@ public: void cleanList(bool preserveMetadata = false); bool isEmpty() const { - return root->isEmpty() && metadata.isEmpty() && sideboardPlans.isEmpty(); + return tree.isEmpty() && metadata.isEmpty() && sideboardPlans.isEmpty(); } QStringList getCardList() const; QList getCardRefList() const; - QList getCardNodes(const QStringList &restrictToZones = QStringList()) const; + QList getCardNodes(const QSet &restrictToZones = {}) const; QList getZoneNodes() const; int getSideboardSize() const; - InnerDecklistNode *getRoot() const - { - return root; - } + DecklistCardNode *addCard(const QString &cardName, const QString &zoneName, - int position, + int position = -1, const QString &cardSetName = QString(), const QString &cardSetCollectorNumber = QString(), const QString &cardProviderId = QString(), const bool formatLegal = true); - bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); ///@} /// @name Deck identity diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp new file mode 100644 index 000000000..8f651a061 --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp @@ -0,0 +1,186 @@ +#include "deck_list_node_tree.h" + +#include "tree/deck_list_card_node.h" + +#include +#include + +DecklistNodeTree::DecklistNodeTree() : root(new InnerDecklistNode()) +{ +} + +DecklistNodeTree::DecklistNodeTree(const DecklistNodeTree &other) : root(new InnerDecklistNode(other.root)) +{ +} + +DecklistNodeTree &DecklistNodeTree::operator=(const DecklistNodeTree &other) +{ + if (this != &other) { + delete root; + root = new InnerDecklistNode(other.root); + } + return *this; +} + +DecklistNodeTree::~DecklistNodeTree() +{ + delete root; +} + +bool DecklistNodeTree::isEmpty() const +{ + return root->isEmpty(); +} + +void DecklistNodeTree::clear() +{ + root->clearTree(); +} + +QList DecklistNodeTree::getCardNodes(const QSet &restrictToZones) const +{ + QList result; + + for (auto *zoneNode : getZoneNodes()) { + if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) { + continue; + } + for (auto *cardNode : *zoneNode) { + auto *cardCardNode = dynamic_cast(cardNode); + if (cardCardNode) { + result.append(cardCardNode); + } + } + } + + return result; +} + +QList DecklistNodeTree::getZoneNodes() const +{ + QList zones; + for (auto *node : *root) { + InnerDecklistNode *currentZone = dynamic_cast(node); + if (!currentZone) + continue; + zones.append(currentZone); + } + + return zones; +} + +QString DecklistNodeTree::computeDeckHash() const +{ + auto mainDeckNodes = getCardNodes({DECK_ZONE_MAIN}); + auto sideDeckNodes = getCardNodes({DECK_ZONE_SIDE}); + + static auto nodesToCardList = [](const QList &nodes, const QString &prefix = {}) { + QStringList result; + for (auto node : nodes) { + for (int i = 0; i < node->getNumber(); ++i) { + result.append(prefix + node->getName().toLower()); + } + } + return result; + }; + + QStringList cardList = nodesToCardList(mainDeckNodes) + nodesToCardList(sideDeckNodes, "SB:"); + + cardList.sort(); + QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); + quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) + + (((quint64)(unsigned char)deckHashArray[1]) << 24) + + (((quint64)(unsigned char)deckHashArray[2] << 16)) + + (((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4]; + return QString::number(number, 32).rightJustified(8, '0'); +} + +void DecklistNodeTree::write(QXmlStreamWriter *xml) const +{ + for (int i = 0; i < root->size(); i++) { + root->at(i)->writeElement(xml); + } +} + +void DecklistNodeTree::readZoneElement(QXmlStreamReader *xml) +{ + QString zoneName = xml->attributes().value("name").toString(); + InnerDecklistNode *newZone = getZoneObjFromName(zoneName); + newZone->readElement(xml); +} + +DecklistCardNode *DecklistNodeTree::addCard(const QString &cardName, + int amount, + const QString &zoneName, + int position, + const QString &cardSetName, + const QString &cardSetCollectorNumber, + const QString &cardProviderId, + const bool formatLegal) +{ + auto *zoneNode = getZoneObjFromName(zoneName); + auto *node = new DecklistCardNode(cardName, amount, zoneNode, position, cardSetName, cardSetCollectorNumber, + cardProviderId, formatLegal); + return node; +} + +bool DecklistNodeTree::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode) +{ + if (node == root) { + return true; + } + + if (rootNode == nullptr) { + rootNode = root; + } + + int index = rootNode->indexOf(node); + if (index != -1) { + delete rootNode->takeAt(index); + + if (rootNode->empty()) { + deleteNode(rootNode, rootNode->getParent()); + } + + return true; + } + + for (int i = 0; i < rootNode->size(); i++) { + auto *inner = dynamic_cast(rootNode->at(i)); + if (inner) { + if (deleteNode(node, inner)) { + return true; + } + } + } + + return false; +} + +void DecklistNodeTree::forEachCard(const std::function &func) const +{ + // Support for this is only possible if the internal structure + // doesn't get more complicated. + for (int i = 0; i < root->size(); i++) { + InnerDecklistNode *node = dynamic_cast(root->at(i)); + for (int j = 0; j < node->size(); j++) { + DecklistCardNode *card = dynamic_cast(node->at(j)); + func(node, card); + } + } +} + +/** + * Gets the InnerDecklistNode that is the root node for the given zone, creating a new node if it doesn't exist. + */ +InnerDecklistNode *DecklistNodeTree::getZoneObjFromName(const QString &zoneName) const +{ + for (int i = 0; i < root->size(); i++) { + auto *node = dynamic_cast(root->at(i)); + if (node->getName() == zoneName) { + return node; + } + } + + return new InnerDecklistNode(zoneName, root); +} diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h new file mode 100644 index 000000000..5cfd4944d --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h @@ -0,0 +1,87 @@ +#ifndef COCKATRICE_DECKLIST_NODE_TREE_H +#define COCKATRICE_DECKLIST_NODE_TREE_H + +#include "libcockatrice/utility/card_ref.h" +#include "tree/deck_list_card_node.h" +#include "tree/inner_deck_list_node.h" + +#include + +class DecklistNodeTree +{ + InnerDecklistNode *root; ///< Root of the deck tree (zones + cards). + +public: + /// @brief Constructs an empty DecklistNodeTree + explicit DecklistNodeTree(); + /// @brief Copy constructor. Deep copies the tree + explicit DecklistNodeTree(const DecklistNodeTree &other); + /// @brief Copy-assignment operator. Deep copies the tree + DecklistNodeTree &operator=(const DecklistNodeTree &other); + + virtual ~DecklistNodeTree(); + + /** + * @brief Gets a pointer to the underlying root node. + * Note: DO NOT call this method unless the object needs to have access to the underlying model. + * For now, only the DeckListModel should be calling this. + */ + InnerDecklistNode *getRoot() const + { + return root; + } + + bool isEmpty() const; + + /** + * @brief Deletes all nodes except the root. + */ + void clear(); + + /** + * Gets all card nodes in the tree + * @param restrictToZones Only get the nodes in these zones + * @return A QList containing all the card nodes in the zone. + */ + QList getCardNodes(const QSet &restrictToZones = {}) const; + + QList getZoneNodes() const; + + /** + * @brief Computes the deck hash + */ + QString computeDeckHash() const; + + /** + *@brief Writes the contents of the deck to xml + */ + void write(QXmlStreamWriter *xml) const; + + /** + * @brief Reads a "zone" section of the xml to this tree + */ + void readZoneElement(QXmlStreamReader *xml); + + DecklistCardNode *addCard(const QString &cardName, + int amount, + const QString &zoneName, + int position, + const QString &cardSetName = QString(), + const QString &cardSetCollectorNumber = QString(), + const QString &cardProviderId = QString(), + const bool formatLegal = true); + bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); + + /** + * @brief Apply a function to every card in the deck tree. This can modify the cards. + * + * @param func Function taking (zone node, card node). + */ + void forEachCard(const std::function &func) const; + +private: + // Helpers for traversing the tree + InnerDecklistNode *getZoneObjFromName(const QString &zoneName) const; +}; + +#endif // COCKATRICE_DECKLIST_NODE_TREE_H diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h index a4f3184cc..46a5897f7 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h @@ -1,6 +1,8 @@ #ifndef COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H #define COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H +#include + class ICardSetPriorityController { public: diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h index b2410781c..949ab5a91 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h @@ -6,25 +6,25 @@ class NoopCardSetPriorityController : public ICardSetPriorityController { public: - void setSortKey(QString /* shortName */, unsigned int /* sortKey */) + void setSortKey(QString /* shortName */, unsigned int /* sortKey */) override { } - void setEnabled(QString /* shortName */, bool /* enabled */) + void setEnabled(QString /* shortName */, bool /* enabled */) override { } - void setIsKnown(QString /* shortName */, bool /* isknown */) + void setIsKnown(QString /* shortName */, bool /* isknown */) override { } - unsigned int getSortKey(QString /* shortName */) + unsigned int getSortKey(QString /* shortName */) override { return 0; } - bool isEnabled(QString /* shortName */) + bool isEnabled(QString /* shortName */) override { return true; } - bool isKnown(QString /* shortName */) + bool isKnown(QString /* shortName */) override { return true; } diff --git a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp index 2af815246..b678e8276 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp @@ -283,11 +283,7 @@ bool SetsDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex &source auto nameIndex = sourceModel()->index(sourceRow, SetsModel::LongNameCol, sourceParent); auto shortNameIndex = sourceModel()->index(sourceRow, SetsModel::ShortNameCol, sourceParent); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) const auto filter = filterRegularExpression(); -#else - const auto filter = filterRegExp(); -#endif return (sourceModel()->data(typeIndex).toString().contains(filter) || sourceModel()->data(nameIndex).toString().contains(filter) || diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp index 05ee480a5..ece3bc2f8 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -5,26 +5,33 @@ DeckListModel::DeckListModel(QObject *parent) : QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder) { - // This class will leak the decklist object. We cannot safely delete it in the dtor because the deckList field is a - // non-owning pointer and another deckList might have been assigned to it. - // `DeckListModel::cleanList` also leaks for the same reason. - // TODO: fix the leak - deckList = new DeckList; + deckList = QSharedPointer(new DeckList()); root = new InnerDecklistNode; } +DeckListModel::DeckListModel(QObject *parent, const QSharedPointer &deckList) : DeckListModel(parent) +{ + setDeckList(deckList); +} + DeckListModel::~DeckListModel() { delete root; } -QString DeckListModel::getGroupCriteriaForCard(CardInfoPtr info) const +/** + * @brief Extract the value from the card that is used for the group criteria. + * @param info Pointer to card information. + * @param criteria The group criteria + * @return String representing the value of the criteria. + */ +static QString extractGroupCriteriaValue(const CardInfoPtr &info, DeckListModelGroupCriteria::Type criteria) { if (!info) { return "unknown"; } - switch (activeGroupCriteria) { + switch (criteria) { case DeckListModelGroupCriteria::MAIN_TYPE: return info->getMainCardType(); case DeckListModelGroupCriteria::MANA_COST: @@ -41,7 +48,7 @@ void DeckListModel::rebuildTree() beginResetModel(); root->clearTree(); - InnerDecklistNode *listRoot = deckList->getRoot(); + InnerDecklistNode *listRoot = deckList->getTree()->getRoot(); for (int i = 0; i < listRoot->size(); i++) { auto *currentZone = dynamic_cast(listRoot->at(i)); @@ -56,7 +63,7 @@ void DeckListModel::rebuildTree() } CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); - QString groupCriteria = getGroupCriteriaForCard(info); + QString groupCriteria = extractGroupCriteriaValue(info, activeGroupCriteria); auto *groupNode = dynamic_cast(node->findChild(groupCriteria)); @@ -178,22 +185,6 @@ QVariant DeckListModel::data(const QModelIndex &index, int role) const } } -void DeckListModel::emitBackgroundUpdates(const QModelIndex &parent) -{ - int rows = rowCount(parent); - if (rows == 0) - return; - - QModelIndex topLeft = index(0, 0, parent); - QModelIndex bottomRight = index(rows - 1, columnCount() - 1, parent); - emit dataChanged(topLeft, bottomRight, {Qt::BackgroundRole}); - - for (int r = 0; r < rows; ++r) { - QModelIndex child = index(r, 0, parent); - emitBackgroundUpdates(child); - } -} - QVariant DeckListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) { @@ -252,6 +243,22 @@ Qt::ItemFlags DeckListModel::flags(const QModelIndex &index) const return result; } +void DeckListModel::emitBackgroundUpdates(const QModelIndex &parent) +{ + int rows = rowCount(parent); + if (rows == 0) + return; + + QModelIndex topLeft = index(0, 0, parent); + QModelIndex bottomRight = index(rows - 1, columnCount() - 1, parent); + emit dataChanged(topLeft, bottomRight, {Qt::BackgroundRole}); + + for (int r = 0; r < rows; ++r) { + QModelIndex child = index(r, 0, parent); + emitBackgroundUpdates(child); + } +} + void DeckListModel::emitRecursiveUpdates(const QModelIndex &index) { if (!index.isValid()) { @@ -294,7 +301,6 @@ bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, con deckList->refreshDeckHash(); emit deckHashChanged(); - emit dataChanged(index, index); return true; } @@ -313,7 +319,7 @@ bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent) for (int i = 0; i < count; i++) { AbstractDecklistNode *toDelete = node->takeAt(row); if (auto *temp = dynamic_cast(toDelete)) { - deckList->deleteNode(temp->getDataNode()); + deckList->getTree()->deleteNode(temp->getDataNode()); } delete toDelete; } @@ -354,7 +360,7 @@ DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName, return nullptr; } - QString groupCriteria = getGroupCriteriaForCard(info); + QString groupCriteria = extractGroupCriteriaValue(info, activeGroupCriteria); InnerDecklistNode *groupNode = dynamic_cast(zoneNode->findChild(groupCriteria)); if (!groupNode) { return nullptr; @@ -407,7 +413,7 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam CardInfoPtr cardInfo = card.getCardPtr(); PrintingInfo printingInfo = card.getPrinting(); - QString groupCriteria = getGroupCriteriaForCard(cardInfo); + QString groupCriteria = extractGroupCriteriaValue(cardInfo, activeGroupCriteria); InnerDecklistNode *groupNode = createNodeIfNeeded(groupCriteria, zoneNode); const QModelIndex parentIndex = nodeToIndex(groupNode); @@ -421,7 +427,7 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName, printingInfo.getProperty("num"), - printingInfo.getProperty("uuid"), isCardLegalForCurrentFormat(cardInfo)); + printingInfo.getProperty("uuid"), cardInfo->isLegalInFormat(deckList->getGameFormat())); beginInsertRows(parentIndex, insertRow, insertRow); cardNode = new DecklistModelCardNode(decklistCard, groupNode, insertRow); @@ -436,10 +442,44 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam } sort(lastKnownColumn, lastKnownOrder); emitRecursiveUpdates(parentIndex); - return nodeToIndex(cardNode); + auto index = nodeToIndex(cardNode); + + emit cardAddedAt(index); + + return index; } -int DeckListModel::findSortedInsertRow(InnerDecklistNode *parent, CardInfoPtr cardInfo) const +bool DeckListModel::offsetCountAtIndex(const QModelIndex &idx, int offset) +{ + if (!idx.isValid()) { + return false; + } + + auto *node = static_cast(idx.internalPointer()); + auto *card = dynamic_cast(node); + + if (!card) { + return false; + } + + const QModelIndex numberIndex = idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT); + const int count = numberIndex.data(Qt::EditRole).toInt(); + const int newCount = count + offset; + + if (newCount <= 0) { + removeRow(idx.row(), idx.parent()); + } else { + setData(numberIndex, newCount, Qt::EditRole); + } + + if (offset > 0) { + emit cardAddedAt(idx); + } + + return true; +} + +int DeckListModel::findSortedInsertRow(const InnerDecklistNode *parent, const CardInfoPtr &cardInfo) const { if (!cardInfo) { return parent->size(); // fallback: append at end @@ -547,56 +587,74 @@ void DeckListModel::setActiveFormat(const QString &_format) void DeckListModel::cleanList() { - setDeckList(new DeckList); + setDeckList(QSharedPointer(new DeckList())); } /** * @param _deck The deck. */ -void DeckListModel::setDeckList(DeckList *_deck) +void DeckListModel::setDeckList(const QSharedPointer &_deck) { if (deckList != _deck) { deckList = _deck; } rebuildTree(); + emit deckReplaced(); +} + +void DeckListModel::forEachCard(const std::function &func) +{ + deckList->forEachCard(func); +} + +static QList cardNodesToExactCards(QList nodes) +{ + QList cards; + for (auto node : nodes) { + ExactCard card = CardDatabaseManager::query()->getCard(node->toCardRef()); + if (card) { + for (int k = 0; k < node->getNumber(); ++k) { + cards.append(card); + } + } else { + qDebug() << "Card not found in database!"; + } + } + + return cards; } QList DeckListModel::getCards() const { auto nodes = deckList->getCardNodes(); - - QList cards; - for (auto node : nodes) { - ExactCard card = CardDatabaseManager::query()->getCard(node->toCardRef()); - if (card) { - for (int k = 0; k < node->getNumber(); ++k) { - cards.append(card); - } - } else { - qDebug() << "Card not found in database!"; - } - } - - return cards; + return cardNodesToExactCards(nodes); } QList DeckListModel::getCardsForZone(const QString &zoneName) const { auto nodes = deckList->getCardNodes({zoneName}); + return cardNodesToExactCards(nodes); +} - QList cards; - for (auto node : nodes) { - ExactCard card = CardDatabaseManager::query()->getCard(node->toCardRef()); - if (card) { - for (int k = 0; k < node->getNumber(); ++k) { - cards.append(card); - } - } else { - qDebug() << "Card not found in database!"; - } - } +QList DeckListModel::getCardNames() const +{ + auto nodes = deckList->getCardNodes(); - return cards; + QList names; + std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(names), [](auto node) { return node->getName(); }); + + return names; +} + +QList DeckListModel::getCardRefs() const +{ + auto nodes = deckList->getCardNodes(); + + QList cardRefs; + std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(cardRefs), + [](auto node) { return node->toCardRef(); }); + + return cardRefs; } QList DeckListModel::getZones() const @@ -610,19 +668,7 @@ QList DeckListModel::getZones() const return zones; } -bool DeckListModel::isCardLegalForCurrentFormat(const CardInfoPtr cardInfo) -{ - if (!deckList->getGameFormat().isEmpty()) { - if (cardInfo->getProperties().contains("format-" + deckList->getGameFormat())) { - QString formatLegality = cardInfo->getProperty("format-" + deckList->getGameFormat()); - return formatLegality == "legal" || formatLegality == "restricted"; - } - return false; - } - return true; -} - -int maxAllowedForLegality(const FormatRules &format, const QString &legality) +static int maxAllowedForLegality(const FormatRules &format, const QString &legality) { for (const AllowedCount &c : format.allowedCounts) { if (c.label == legality) { @@ -632,26 +678,29 @@ int maxAllowedForLegality(const FormatRules &format, const QString &legality) return -1; // unknown legality → treat as illegal } - -bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardInfo, int quantity) +static bool isCardQuantityLegalForFormat(const QString &format, const CardInfo &cardInfo, int quantity) { - auto formatRules = CardDatabaseManager::query()->getFormat(deckList->getGameFormat()); + if (format.isEmpty()) { + return true; + } + + auto formatRules = CardDatabaseManager::query()->getFormat(format); if (!formatRules) { return true; } // Exceptions always win - if (cardHasAnyException(*cardInfo, *formatRules)) { + if (cardHasAnyException(cardInfo, *formatRules)) { return true; } - const QString legalityProp = "format-" + deckList->getGameFormat(); - if (!cardInfo->getProperties().contains(legalityProp)) { + const QString legalityProp = "format-" + format; + if (!cardInfo.getProperties().contains(legalityProp)) { return false; } - const QString legality = cardInfo->getProperty(legalityProp); + const QString legality = cardInfo.getProperty(legalityProp); int maxAllowed = maxAllowedForLegality(*formatRules, legality); @@ -668,7 +717,7 @@ bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardIn void DeckListModel::refreshCardFormatLegalities() { - InnerDecklistNode *listRoot = deckList->getRoot(); + InnerDecklistNode *listRoot = deckList->getTree()->getRoot(); for (int i = 0; i < listRoot->size(); i++) { auto *currentZone = static_cast(listRoot->at(i)); @@ -685,10 +734,11 @@ void DeckListModel::refreshCardFormatLegalities() continue; } - bool legal = isCardLegalForCurrentFormat(exactCard.getCardPtr()); + QString format = deckList->getGameFormat(); + bool legal = exactCard.getInfo().isLegalInFormat(format); if (legal) { - legal = isCardQuantityLegalForCurrentFormat(exactCard.getCardPtr(), currentCard->getNumber()); + legal = isCardQuantityLegalForFormat(format, exactCard.getInfo(), currentCard->getNumber()); } currentCard->setFormatLegality(legal); diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h index c03497bd5..e6f10c072 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -202,7 +202,7 @@ public: * affects its hash. * * Slots: - * - rebuildTree(): rebuilds the model structure from the underlying DeckLoader. + * - rebuildTree(): rebuilds the model structure from the underlying node tree. */ class DeckListModel : public QAbstractItemModel { @@ -210,14 +210,19 @@ class DeckListModel : public QAbstractItemModel public slots: /** - * @brief Rebuilds the model tree from the underlying DeckLoader. + * @brief Rebuilds the model tree from the underlying node tree. * * This updates all indices and ensures the model reflects the current * state of the deck. */ void rebuildTree(); -public slots: + /** + * @brief Sets the criteria used to group cards in the model. + * @param newCriteria The new grouping criteria. + */ + void setActiveGroupCriteria(DeckListModelGroupCriteria::Type newCriteria); + void setActiveFormat(const QString &_format); signals: @@ -226,8 +231,21 @@ signals: */ void deckHashChanged(); + /** + * @brief Emitted whenever a card is added to the deck, regardless of whether it's an entirely new card or an + * existing card that got incremented. + * @param index The index of the card that got added. + */ + void cardAddedAt(const QModelIndex &index); + + /** + * @brief Emitted whenever the deck in the model has been replaced with a new one + */ + void deckReplaced(); + public: explicit DeckListModel(QObject *parent = nullptr); + explicit DeckListModel(QObject *parent, const QSharedPointer &deckList); ~DeckListModel() override; /** @@ -239,24 +257,19 @@ public: return nodeToIndex(root); } - /** - * @brief Returns the value of the grouping category for a card based on the current criteria. - * @param info Pointer to card information. - * @return String representing the value of the current grouping criteria for the card. - */ - [[nodiscard]] QString getGroupCriteriaForCard(CardInfoPtr info) const; - - // Qt model overrides + /// @name Qt model overrides + ///@{ [[nodiscard]] int rowCount(const QModelIndex &parent) const override; [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; - void emitBackgroundUpdates(const QModelIndex &parent); [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override; [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent) const override; [[nodiscard]] QModelIndex parent(const QModelIndex &index) const override; [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool removeRows(int row, int count, const QModelIndex &parent) override; + void sort(int column, Qt::SortOrder order) override; + ///@} /** * @brief Finds a card by name, zone, and optional identifiers. @@ -290,41 +303,58 @@ public: QModelIndex addCard(const ExactCard &card, const QString &zoneName); /** - * @brief Determines the sorted insertion row for a card. - * @param parent The parent node where the card will be inserted. - * @param cardInfo The card info to insert. - * @return Row index where the card should be inserted to maintain sort order. + * @brief Changes the `amount` field in the card node at the index by the amount. + * Removes the node if it causes the amount to fall to 0 or below. + * @param idx The index of a card node. No-ops if the index is invalid or not a card node. + * @param offset The amount to change the amount field by. + * @return Whether the operation was successful */ - int findSortedInsertRow(InnerDecklistNode *parent, CardInfoPtr cardInfo) const; - - void sort(int column, Qt::SortOrder order) override; + bool offsetCountAtIndex(const QModelIndex &idx, int offset); /** * @brief Removes all cards and resets the model. */ void cleanList(); - [[nodiscard]] DeckList *getDeckList() const + + [[nodiscard]] QSharedPointer getDeckList() const { return deckList; } - void setDeckList(DeckList *_deck); - - [[nodiscard]] QList getCards() const; - [[nodiscard]] QList getCardsForZone(const QString &zoneName) const; - [[nodiscard]] QList getZones() const; - bool isCardLegalForCurrentFormat(CardInfoPtr cardInfo); - bool isCardQuantityLegalForCurrentFormat(CardInfoPtr cardInfo, int quantity); - void refreshCardFormatLegalities(); + void setDeckList(const QSharedPointer &_deck); /** - * @brief Sets the criteria used to group cards in the model. - * @param newCriteria The new grouping criteria. + * @brief Apply a function to every card in the deck tree. + * + * @param func Function taking (zone node, card node). */ - void setActiveGroupCriteria(DeckListModelGroupCriteria::Type newCriteria); + void forEachCard(const std::function &func); + + /** + * @brief Creates a list consisting of the entries of the model mapped into ExactCards (with each entry looked up + * in the card database). + * If a card node has number > 1, it will be added that many times to the list. + * If an entry's card is not found in the card database, that entry will be left out of the list. + * @return An ordered list of ExactCards + */ + [[nodiscard]] QList getCards() const; + [[nodiscard]] QList getCardsForZone(const QString &zoneName) const; + + /** + * @brief Gets a deduplicated list of all card names that appear in the model + */ + [[nodiscard]] QList getCardNames() const; + /** + * @brief Gets a deduplicated list of all CardRefs that appear in the model + */ + [[nodiscard]] QList getCardRefs() const; + /** + * @brief Gets a list of all zone names that appear in the model + */ + [[nodiscard]] QList getZones() const; private: - DeckList *deckList; /**< Pointer to the deck loader providing the underlying data. */ - InnerDecklistNode *root; /**< Root node of the model tree. */ + QSharedPointer deckList; /**< Pointer to the decklist providing the underlying data. */ + InnerDecklistNode *root; /**< Root node of the model tree. */ DeckListModelGroupCriteria::Type activeGroupCriteria = DeckListModelGroupCriteria::MAIN_TYPE; int lastKnownColumn; /**< Last column used for sorting. */ Qt::SortOrder lastKnownOrder; /**< Last known sort order. */ @@ -335,7 +365,28 @@ private: const QString &zoneName, const QString &providerId = "", const QString &cardNumber = "") const; + + /** + * @brief Determines the sorted insertion row for a card. + * @param parent The parent node where the card will be inserted. + * @param cardInfo The card info to insert. + * @return Row index where the card should be inserted to maintain sort order. + */ + int findSortedInsertRow(const InnerDecklistNode *parent, const CardInfoPtr &cardInfo) const; + + /** + * @brief Recursively emits the dataChanged signal with role as Qt::BackgroundRole for all indices that are children + * of the given node. This is used to update the background color when changing formats. + * @param parent The parent node + */ + void emitBackgroundUpdates(const QModelIndex &parent); + + /** + * @brief Recursively emits the dataChanged signal for the given node and all parent nodes. + * @param index The parent node + */ void emitRecursiveUpdates(const QModelIndex &index); + void sortHelper(InnerDecklistNode *node, Qt::SortOrder order); template T getNode(const QModelIndex &index) const @@ -344,6 +395,8 @@ private: return dynamic_cast(root); return dynamic_cast(static_cast(index.internalPointer())); } + + void refreshCardFormatLegalities(); }; #endif diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp index 35fd4d9f8..0ec159737 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp @@ -11,8 +11,8 @@ bool DeckListSortFilterProxyModel::lessThan(const QModelIndex &left, const QMode bool rightIsCard = src->data(right, Qt::UserRole + 1).toBool(); if (!leftIsCard || !rightIsCard) { - QString lName = src->data(left.siblingAtColumn(1), Qt::EditRole).toString(); - QString rName = src->data(right.siblingAtColumn(1), Qt::EditRole).toString(); + QString lName = src->data(left.siblingAtColumn(DeckListModelColumns::CARD_NAME), Qt::EditRole).toString(); + QString rName = src->data(right.siblingAtColumn(DeckListModelColumns::CARD_NAME), Qt::EditRole).toString(); return lName.localeAwareCompare(rName) < 0; } diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp index c0167a875..4b5d3f1b8 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp @@ -42,11 +42,7 @@ RemoteClient::RemoteClient(QObject *parent, INetworkSettingsProvider *_networkSe connect(socket, &QTcpSocket::connected, this, &RemoteClient::slotConnected); connect(socket, &QTcpSocket::readyRead, this, &RemoteClient::readData); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) connect(socket, &QTcpSocket::errorOccurred, this, &RemoteClient::slotSocketError); -#else - connect(socket, qOverload(&QTcpSocket::error), this, &RemoteClient::slotSocketError); -#endif websocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this); connect(websocket, &QWebSocket::binaryMessageReceived, this, &RemoteClient::websocketMessageReceived); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp index 68dcadd35..f2a35e548 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp @@ -63,11 +63,7 @@ void Server_CardZone::shuffle(int start, int end) for (int i = end; i > start; i--) { int j = rng->rand(start, i); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) cards.swapItemsAt(j, i); -#else - cards.swap(j, i); -#endif } playersWithWritePermission.clear(); } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp index 5ec7b3c5d..5cd6c8bf8 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -73,11 +73,7 @@ Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, spectatorsSeeEverything(_spectatorsSeeEverything), startingLifeTotal(_startingLifeTotal), shareDecklistsOnLoad(_shareDecklistsOnLoad), inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false), turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr), -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) gameMutex() -#else - gameMutex(QMutex::Recursive) -#endif { currentReplay = new GameReplay; currentReplay->set_replay_id(room->getServer()->getDatabaseInterface()->getNextReplayId()); @@ -501,7 +497,7 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface, allPlayersEver.insert(playerName); // if the original creator of the game joins, give them host status back - // FIXME: transferring host to spectators has side effects + //! \todo transferring host to spectators has side effects if (newParticipant->getUserInfo()->name() == creatorInfo->name()) { hostId = newParticipant->getPlayerId(); sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId)); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h index 033542fad..64374019c 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h @@ -90,11 +90,7 @@ private slots: void doStartGameIfReady(bool forceStartGame = false); public: -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) mutable QRecursiveMutex gameMutex; -#else - mutable QMutex gameMutex; -#endif Server_Game(const ServerInfo_User &_creatorInfo, int _gameId, const QString &_description, diff --git a/libcockatrice_utility/libcockatrice/utility/peglib.h b/libcockatrice_utility/libcockatrice/utility/peglib.h index 459fa5a27..6a5b87b2d 100644 --- a/libcockatrice_utility/libcockatrice/utility/peglib.h +++ b/libcockatrice_utility/libcockatrice/utility/peglib.h @@ -441,8 +441,8 @@ private: size_t id; }; - // TODO: Use unordered_map when heterogeneous lookup is supported in C++20 - // std::unordered_map dic_; + //! \todo Use unordered_map when heterogeneous lookup is supported in C++20 + //! \todo std::unordered_map dic_; std::map> dic_; bool ignore_case_; @@ -3068,7 +3068,7 @@ inline size_t Recovery::parse_core(const char *s, size_t n, c.cut_stack.back() = true; if (c.cut_stack.size() == 1) { - // TODO: Remove unneeded entries in packrat memoise table + //! \todo Remove unneeded entries in packrat memoise table } } diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 813df49ae..4b089846f 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -565,7 +565,7 @@ int OracleImporter::startImport() bool OracleImporter::saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion) { - CockatriceXml4Parser parser(new NoopCardPreferenceProvider()); + CockatriceXml4Parser parser(new NoopCardPreferenceProvider(), new NoopCardSetPriorityController()); return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName, sourceUrl, sourceVersion); } diff --git a/oracle/src/zip/unzip.cpp b/oracle/src/zip/unzip.cpp index 1e5910051..8e52ece18 100755 --- a/oracle/src/zip/unzip.cpp +++ b/oracle/src/zip/unzip.cpp @@ -245,7 +245,6 @@ UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) \internal Parses a local header record and makes some consistency check with the information stored in the Central Directory record for this entry that has been previously parsed. - \todo Optional consistency check (as a ExtractionOptions flag) local file header signature 4 bytes (0x04034b50) version needed to extract 2 bytes @@ -262,6 +261,7 @@ UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) file name (variable size) extra field (variable size) */ +//! \todo Optional consistency check (as a ExtractionOptions flag) UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, const ZipEntryP& entry) { Q_ASSERT(device); diff --git a/servatrice/src/isl_interface.cpp b/servatrice/src/isl_interface.cpp index 2269ff314..bcf71a98a 100644 --- a/servatrice/src/isl_interface.cpp +++ b/servatrice/src/isl_interface.cpp @@ -112,11 +112,7 @@ void IslInterface::initServer() socket->startServerEncryption(); if (!socket->waitForEncrypted(5000)) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) QList sslErrors(socket->sslHandshakeErrors()); -#else - QList sslErrors(socket->sslErrors()); -#endif if (sslErrors.isEmpty()) qDebug() << "[ISL] SSL handshake timeout, terminating connection"; else @@ -193,11 +189,7 @@ void IslInterface::initClient() return; } if (!socket->waitForEncrypted(5000)) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) QList sslErrors(socket->sslHandshakeErrors()); -#else - QList sslErrors(socket->sslErrors()); -#endif if (sslErrors.isEmpty()) qDebug() << "[ISL] SSL handshake timeout, terminating connection"; else diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index ea3d783bd..410bf4ed9 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -254,13 +254,8 @@ bool Servatrice::initServer() qDebug() << "Accept registered users only:" << getRegOnlyServerEnabled(); qDebug() << "Registration enabled:" << getRegistrationEnabled(); if (getRegistrationEnabled()) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList emailBlackListFilters = getEmailBlackList().split(",", Qt::SkipEmptyParts); QStringList emailWhiteListFilters = getEmailWhiteList().split(",", Qt::SkipEmptyParts); -#else - QStringList emailBlackListFilters = getEmailBlackList().split(",", QString::SkipEmptyParts); - QStringList emailWhiteListFilters = getEmailWhiteList().split(",", QString::SkipEmptyParts); -#endif qDebug() << "Email blacklist:" << emailBlackListFilters; qDebug() << "Email whitelist:" << emailWhiteListFilters; qDebug() << "Require email address to register:" << getRequireEmailForRegistrationEnabled(); @@ -564,11 +559,7 @@ void Servatrice::setRequiredFeatures(const QString &featureList) FeatureSet features; serverRequiredFeatureList.clear(); features.initalizeFeatureList(serverRequiredFeatureList); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList listReqFeatures = featureList.split(",", Qt::SkipEmptyParts); -#else - QStringList listReqFeatures = featureList.split(",", QString::SkipEmptyParts); -#endif if (!listReqFeatures.isEmpty()) for (const QString &reqFeature : listReqFeatures) { features.enableRequiredFeature(serverRequiredFeatureList, reqFeature); diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index bad16bec3..fa9b14f31 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -163,11 +163,7 @@ bool Servatrice_DatabaseInterface::usernameIsValid(const QString &user, QString bool allowPunctuationPrefix = settingsCache->value("users/allowpunctuationprefix", false).toBool(); QString allowedPunctuation = settingsCache->value("users/allowedpunctuation", "_").toString(); QString disallowedWordsStr = settingsCache->value("users/disallowedwords", "").toString(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList disallowedWords = disallowedWordsStr.split(",", Qt::SkipEmptyParts); -#else - QStringList disallowedWords = disallowedWordsStr.split(",", QString::SkipEmptyParts); -#endif disallowedWords.removeDuplicates(); QVariant displayDisallowedWords = settingsCache->value("users/displaydisallowedwords"); QString disallowedRegExpStr; diff --git a/servatrice/src/server_logger.cpp b/servatrice/src/server_logger.cpp index 9e40ac7b4..79d8cdfe0 100644 --- a/servatrice/src/server_logger.cpp +++ b/servatrice/src/server_logger.cpp @@ -57,11 +57,7 @@ void ServerLogger::logMessage(const QString &message, void *caller) // filter out all log entries based on values in configuration file bool shouldWeWriteLog = settingsCache->value("server/writelog", 1).toBool(); QString logFilters = settingsCache->value("server/logfilters").toString(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList listlogFilters = logFilters.split(",", Qt::SkipEmptyParts); -#else - QStringList listlogFilters = logFilters.split(",", QString::SkipEmptyParts); -#endif bool shouldWeSkipLine = false; if (!shouldWeWriteLog) diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 4b33aad12..619d36d3a 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -972,11 +972,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetWarnList(const Comma Response_WarnList *re = new Response_WarnList; QString officialWarnings = settingsCache->value("server/officialwarnings").toString(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList warningsList = officialWarnings.split(",", Qt::SkipEmptyParts); -#else - QStringList warningsList = officialWarnings.split(",", QString::SkipEmptyParts); -#endif for (const QString &warning : warningsList) { re->add_warning(warning.toStdString()); } @@ -1172,13 +1168,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C const auto parsedEmailParts = EmailParser::parseEmailAddress(nameFromStdString(cmd.email())); const auto emailUser = parsedEmailParts.first; const auto emailDomain = parsedEmailParts.second; -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) const QStringList emailBlackListFilters = emailBlackList.split(",", Qt::SkipEmptyParts); const QStringList emailWhiteListFilters = emailWhiteList.split(",", Qt::SkipEmptyParts); -#else - const QStringList emailBlackListFilters = emailBlackList.split(",", QString::SkipEmptyParts); - const QStringList emailWhiteListFilters = emailWhiteList.split(",", QString::SkipEmptyParts); -#endif bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); if (requireEmailForRegistration && emailUser.isEmpty()) { @@ -1207,7 +1198,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C return Response::RespEmailBlackListed; } - // TODO: Move this method outside of the db interface + //! \todo Move this method outside of the db interface QString errorString; if (!sqlInterface->usernameIsValid(userName, errorString)) { if (servatrice->getEnableRegistrationAudit()) @@ -1330,7 +1321,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C bool AbstractServerSocketInterface::tooManyRegistrationAttempts(const QString &ipAddress) { - // TODO: implement + //! \todo implement Q_UNUSED(ipAddress); return false; } @@ -1930,13 +1921,8 @@ TcpServerSocketInterface::TcpServerSocketInterface(Servatrice *_server, socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); connect(socket, SIGNAL(readyRead()), this, SLOT(readClient())); connect(socket, SIGNAL(disconnected()), this, SLOT(catchSocketDisconnected())); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(catchSocketError(QAbstractSocket::SocketError))); -#else - connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(catchSocketError(QAbstractSocket::SocketError))); -#endif } TcpServerSocketInterface::~TcpServerSocketInterface() @@ -2109,10 +2095,8 @@ void WebsocketServerSocketInterface::initConnection(void *_socket) } socket = (QWebSocket *)_socket; socket->setParent(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) // https://bugreports.qt.io/browse/QTBUG-70693 socket->setMaxAllowedIncomingMessageSize(1500000); // 1.5MB -#endif address = socket->peerAddress(); diff --git a/servatrice/src/settingscache.cpp b/servatrice/src/settingscache.cpp index 14bfaebfb..f6dcd5fc8 100644 --- a/servatrice/src/settingscache.cpp +++ b/servatrice/src/settingscache.cpp @@ -11,12 +11,7 @@ SettingsCache::SettingsCache(const QString &fileName, QSettings::Format format, // first, figure out if we are running in portable mode isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat"); - QStringList disallowedRegExpStr = -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - value("users/disallowedregexp", "").toString().split(",", Qt::SkipEmptyParts); -#else - value("users/disallowedregexp", "").toString().split(",", QString::SkipEmptyParts); -#endif + QStringList disallowedRegExpStr = value("users/disallowedregexp", "").toString().split(",", Qt::SkipEmptyParts); disallowedRegExpStr.removeDuplicates(); for (const QString ®ExpStr : disallowedRegExpStr) { disallowedRegExp.append(QRegularExpression(QString("\\A%1\\z").arg(regExpStr))); diff --git a/servatrice/src/smtp/qxtmailmessage.cpp b/servatrice/src/smtp/qxtmailmessage.cpp index ca003d875..ff376c1a9 100644 --- a/servatrice/src/smtp/qxtmailmessage.cpp +++ b/servatrice/src/smtp/qxtmailmessage.cpp @@ -27,9 +27,8 @@ * \class QxtMailMessage * \inmodule QxtNetwork * \brief The QxtMailMessage class encapsulates an e-mail according to RFC 2822 and related specifications - * TODO: {implicitshared} */ - +//! \todo {implicitshared} #include "qxtmailmessage.h" #include "qxtmail_p.h" diff --git a/servatrice/src/smtp/qxtsmtp.cpp b/servatrice/src/smtp/qxtsmtp.cpp index 951492d7a..6326b101d 100644 --- a/servatrice/src/smtp/qxtsmtp.cpp +++ b/servatrice/src/smtp/qxtsmtp.cpp @@ -334,11 +334,7 @@ void QxtSmtpPrivate::authenticate() state = Authenticated; emit qxt_p().authenticated(); } else { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList auth = extensions["AUTH"].toUpper().split(' ', Qt::SkipEmptyParts); -#else - QStringList auth = extensions["AUTH"].toUpper().split(' ', QString::SkipEmptyParts); -#endif if (auth.contains("CRAM-MD5")) { authCramMD5(); } else if (auth.contains("PLAIN")) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6a5eacf54..d80ccce4f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,16 +1,20 @@ enable_testing() + add_test(NAME dummy_test COMMAND dummy_test) add_test(NAME expression_test COMMAND expression_test) - add_test(NAME test_age_formatting COMMAND test_age_formatting) add_test(NAME password_hash_test COMMAND password_hash_test) +add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test) +set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 5) + # Find GTest add_executable(dummy_test dummy_test.cpp) add_executable(expression_test expression_test.cpp) add_executable(test_age_formatting test_age_formatting.cpp) add_executable(password_hash_test password_hash_test.cpp) +add_executable(deck_hash_performance_test deck_hash_performance_test.cpp) find_package(GTest) @@ -41,6 +45,7 @@ if(NOT GTEST_FOUND) add_dependencies(expression_test gtest) add_dependencies(test_age_formatting gtest) add_dependencies(password_hash_test gtest) + add_dependencies(deck_hash_performance_test gtest) endif() include_directories(${GTEST_INCLUDE_DIRS}) @@ -50,6 +55,10 @@ target_link_libraries(test_age_formatting Threads::Threads ${GTEST_BOTH_LIBRARIE target_link_libraries( password_hash_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) +target_link_libraries( + deck_hash_performance_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} +) add_subdirectory(carddatabase) add_subdirectory(loading_from_clipboard) diff --git a/tests/deck_hash_performance_test.cpp b/tests/deck_hash_performance_test.cpp new file mode 100644 index 000000000..154283e8e --- /dev/null +++ b/tests/deck_hash_performance_test.cpp @@ -0,0 +1,81 @@ +#include "gtest/gtest.h" +#include +#include + +static constexpr int amount = 1e5; +QString repeatDeck; +QString numberDeck; +QString uniquesDeck; +QString uniquesXorDeck; +QString duplicatesDeck; + +TEST(DeckHashTest, RepeatTest) +{ + DeckList decklist(repeatDeck); + for (int i = 0; i < amount; ++i) { + decklist.getDeckHash(); + decklist.refreshDeckHash(); + } + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "5cac19qm") << "The hash does not match!"; +} + +TEST(DeckHashTest, NumberTest) +{ + DeckList decklist(numberDeck); + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "e0m38p19") << "The hash does not match!"; +} + +TEST(DeckHashTest, UniquesTest) +{ + DeckList decklist(uniquesDeck); + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "88prk025") << "The hash does not match!"; +} + +TEST(DeckHashTest, UniquesTestXor) +{ + DeckList decklist(uniquesXorDeck); + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "hkn6q4pf") << "The hash does not match!"; +} + +TEST(DeckHashTest, DuplicatesTest) +{ + DeckList decklist(duplicatesDeck); + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "ekt6tg1h") << "The hash does not match!"; +} + +int main(int argc, char **argv) +{ + const QString deckStart = + R"()"; + const QString deckEnd = R"()"; + + repeatDeck = + deckStart + + R"()" + + deckEnd; + numberDeck = deckStart + QString(R"()").arg(amount) + deckEnd; + + QStringList deckString{deckStart}; + QStringList deckStringXor = deckString; + int len = QString::number(amount).length(); + for (int i = 0; i < amount; ++i) { + // creates already sorted list + deckString << R"()"; + // xor in order to mess with sorting + deckStringXor << R"()"; + } + deckString << deckEnd; + deckStringXor << deckEnd; + uniquesDeck = deckString.join(""); + uniquesXorDeck = deckStringXor.join(""); + + duplicatesDeck = deckStart + QString(R"()").repeated(amount) + deckEnd; + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/loading_from_clipboard/clipboard_testing.cpp b/tests/loading_from_clipboard/clipboard_testing.cpp index 2342bc088..29535c1dc 100644 --- a/tests/loading_from_clipboard/clipboard_testing.cpp +++ b/tests/loading_from_clipboard/clipboard_testing.cpp @@ -3,22 +3,32 @@ #include #include -void testEmpty(const QString &clipboard) +DeckList getDeckList(const QString &clipboard) { - QString cp(clipboard); DeckList deckList; + QString cp(clipboard); QTextStream stream(&cp); // text stream requires local copy deckList.loadFromStream_Plain(stream, false); + return deckList; +} + +void testEmpty(const QString &clipboard) +{ + DeckList deckList = getDeckList(clipboard); ASSERT_TRUE(deckList.getCardList().isEmpty()); } +void testHash(const QString &clipboard, const std::string &hash) +{ + DeckList deckList = getDeckList(clipboard); + + ASSERT_EQ(deckList.getDeckHash().toStdString(), hash); +} + void testDeck(const QString &clipboard, const Result &result) { - QString cp(clipboard); - DeckList deckList; - QTextStream stream(&cp); // text stream requires local copy - deckList.loadFromStream_Plain(stream, false); + DeckList deckList = getDeckList(clipboard); ASSERT_EQ(result.name, deckList.getName().toStdString()); ASSERT_EQ(result.comments, deckList.getComments().toStdString()); diff --git a/tests/loading_from_clipboard/clipboard_testing.h b/tests/loading_from_clipboard/clipboard_testing.h index 5e9cff915..41d8ea794 100644 --- a/tests/loading_from_clipboard/clipboard_testing.h +++ b/tests/loading_from_clipboard/clipboard_testing.h @@ -21,7 +21,7 @@ struct Result }; void testEmpty(const QString &clipboard); - +void testHash(const QString &clipboard, const std::string &hash); void testDeck(const QString &clipboard, const Result &result); #endif // CLIPBOARD_TESTING_H diff --git a/tests/loading_from_clipboard/loading_from_clipboard_test.cpp b/tests/loading_from_clipboard/loading_from_clipboard_test.cpp index 6f9762be9..fcfbb22db 100644 --- a/tests/loading_from_clipboard/loading_from_clipboard_test.cpp +++ b/tests/loading_from_clipboard/loading_from_clipboard_test.cpp @@ -203,6 +203,22 @@ TEST(LoadingFromClipboardTest, emptyMainBoard) testEmpty(clipboard); } +TEST(LoadingFromClipboardTest, emptyHash) +{ + QString clipboard(""); + + testHash(clipboard, "r8sq7riu"); +} + +TEST(LoadingFromClipboardTest, deckHash) +{ + QString clipboard("1 Mountain\n" + "2 Island\n" + "SB: 3 Forest\n"); + + testHash(clipboard, "5cac19qm"); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv);