diff --git a/.ci/compile.sh b/.ci/compile.sh index a7c349fb0..0af79868b 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") @@ -246,7 +246,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 58b246071..a26e82ee5 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -166,7 +166,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 +205,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 +213,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}} @@ -342,6 +342,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 @@ -356,16 +361,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: Install Qt ${{matrix.qt_version}} uses: jurplel/install-qt-action@v4 @@ -403,6 +412,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: @@ -450,7 +468,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}} @@ -458,7 +476,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 3e415fa23..3d01e9f3d 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,8 @@ Latest beta version: - [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 -- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official webpage of the Cockatrice project +- [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 # Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) @@ -54,11 +55,12 @@ 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 @@ -75,10 +77,15 @@ 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). + 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. +#### Repository Activity +![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats") +
-Kudos to our amazing contributors ❤️ +Kudos to all our amazing contributors ❤️
@@ -124,8 +131,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 522300346..b2d387391 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -19,7 +19,10 @@ set(cockatrice_SOURCES src/client/settings/card_counter_settings.cpp src/client/settings/shortcut_treeview.cpp src/client/settings/shortcuts_settings.cpp + src/interface/deck_loader/card_node_function.cpp + src/interface/deck_loader/deck_file_format.cpp src/interface/deck_loader/deck_loader.cpp + src/interface/deck_loader/loaded_deck.cpp src/interface/widgets/dialogs/dlg_connect.cpp src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.cpp src/interface/widgets/dialogs/dlg_create_game.cpp @@ -142,6 +145,7 @@ set(cockatrice_SOURCES src/interface/widgets/cards/deck_card_zone_display_widget.cpp src/interface/widgets/cards/deck_preview_card_picture_widget.cpp src/interface/widgets/deck_analytics/deck_analytics_widget.cpp + src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp src/interface/widgets/deck_analytics/mana_base_widget.cpp src/interface/widgets/deck_analytics/mana_curve_widget.cpp src/interface/widgets/deck_analytics/mana_devotion_widget.cpp @@ -153,8 +157,10 @@ set(cockatrice_SOURCES src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp src/interface/widgets/deck_editor/deck_list_style_proxy.cpp src/interface/widgets/general/background_sources.cpp + src/interface/widgets/general/display/background_plate_widget.cpp src/interface/widgets/general/display/banner_widget.cpp src/interface/widgets/general/display/bar_widget.cpp + src/interface/widgets/general/display/color_bar.cpp src/interface/widgets/general/display/dynamic_font_size_label.cpp src/interface/widgets/general/display/dynamic_font_size_push_button.cpp src/interface/widgets/general/display/labeled_input.cpp @@ -196,12 +202,14 @@ set(cockatrice_SOURCES src/interface/widgets/utility/sequence_edit.cpp src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp + src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_widget.cpp src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp + src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp @@ -220,6 +228,20 @@ set(cockatrice_SOURCES src/interface/window_main.cpp src/main.cpp src/interface/widgets/tabs/abstract_tab_deck_editor.cpp + src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp + src/interface/widgets/tabs/api/archidekt/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 + src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp @@ -265,6 +287,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/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index 083d7234e..20aa206ce 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -1,6 +1,10 @@ [Rules] # The default log level is info *.debug = false +#*.info = true +#*.warning = true +#*.critical = true +#*.fatal = true # Uncomment a rule to see debug level logs for that category, # or set = false to disable logging 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/resources/usericons/pawn_donator_double.svg b/cockatrice/resources/usericons/pawn_donator_double.svg index f0dba08b2..30b353652 100644 --- a/cockatrice/resources/usericons/pawn_donator_double.svg +++ b/cockatrice/resources/usericons/pawn_donator_double.svg @@ -350,11 +350,11 @@ style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z " transform="translate(0,952.36218)" - id="right" /> + id="left" /> + id="left" /> + id="left" /> #include -#include #include #include #include #include #include -#include +#include +#include DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent) : QObject(parent), cardDatabase(_cardDatabase) @@ -43,31 +43,32 @@ 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"); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); 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 f621fff7c..7dc841027 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.h +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.h @@ -7,7 +7,6 @@ #ifndef DECKSTATS_INTERFACE_H #define DECKSTATS_INTERFACE_H -#include #include #include @@ -29,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 d18d811b0..a30a7f531 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp @@ -2,13 +2,13 @@ #include #include -#include #include #include #include #include #include -#include +#include +#include TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent) : QObject(parent), cardDatabase(_cardDatabase) @@ -67,32 +67,33 @@ 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"); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); 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 e4e9498a3..0ea9c8358 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.h +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.h @@ -7,8 +7,6 @@ #ifndef TAPPEDOUT_INTERFACE_H #define TAPPEDOUT_INTERFACE_H -#include -#include #include #include @@ -32,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 8ac05a84b..e19e4f4e4 100644 --- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h +++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h @@ -6,6 +6,8 @@ #ifndef INTERFACE_JSON_DECK_PARSER_H #define INTERFACE_JSON_DECK_PARSER_H + +#include "../../../interface/deck_loader/card_node_function.h" #include "../../../interface/deck_loader/deck_loader.h" #include @@ -16,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); @@ -47,25 +49,25 @@ public: outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; } - loader->getDeckList()->loadFromStream_Plain(outStream, false); - DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList()); + 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); @@ -94,8 +96,8 @@ public: outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; } - loader->getDeckList()->loadFromStream_Plain(outStream, false); - DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList()); + deckList.loadFromStream_Plain(outStream, false); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); QJsonObject commandersObj = obj.value("commanders").toObject(); if (!commandersObj.isEmpty()) { @@ -106,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/network/update/card_spoiler/spoiler_background_updater.cpp b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp index cfd4cbbf8..7194c46e5 100644 --- a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp +++ b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp @@ -4,18 +4,17 @@ #include "../../../../main.h" #include "../../../settings/cache_settings.h" -#include #include #include #include #include #include -#include #include #include #include #include #include +#include #define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled" #define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml" @@ -41,7 +40,9 @@ void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool sav void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults) { auto *nam = new QNetworkAccessManager(this); - QNetworkReply *reply = nam->get(QNetworkRequest(url)); + auto request = QNetworkRequest(url); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + QNetworkReply *reply = nam->get(request); if (saveResults) { // This will write out to the file (used for spoiler.xml) diff --git a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h index 387f93820..a2feb5ccf 100644 --- a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h +++ b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h @@ -22,7 +22,7 @@ public: inline QString getCardUpdaterBinaryName() { return "oracle"; - }; + } QByteArray getHash(const QString fileName); QByteArray getHash(QByteArray data); static bool deleteSpoilerFile(); diff --git a/cockatrice/src/client/network/update/client/update_downloader.cpp b/cockatrice/src/client/network/update/client/update_downloader.cpp index 40186bec4..c0f3e945c 100644 --- a/cockatrice/src/client/network/update/client/update_downloader.cpp +++ b/cockatrice/src/client/network/update/client/update_downloader.cpp @@ -1,6 +1,5 @@ #include "update_downloader.h" -#include #include UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(nullptr) diff --git a/cockatrice/src/client/network/update/client/update_downloader.h b/cockatrice/src/client/network/update/client/update_downloader.h index 4f79f3f6d..d70759038 100644 --- a/cockatrice/src/client/network/update/client/update_downloader.h +++ b/cockatrice/src/client/network/update/client/update_downloader.h @@ -7,9 +7,7 @@ #ifndef COCKATRICE_UPDATEDOWNLOADER_H #define COCKATRICE_UPDATEDOWNLOADER_H -#include #include -#include #include class UpdateDownloader : public QObject diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 1cf04070c..df68ce0c4 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -325,8 +324,8 @@ private: int keepalive; int timeout; void translateLegacySettings(); - QString getSafeConfigPath(QString configEntry, QString defaultPath) const; - QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const; + [[nodiscard]] QString getSafeConfigPath(QString configEntry, QString defaultPath) const; + [[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const; void loadPaths(); bool rememberGameSettings; QList releaseChannels; @@ -338,137 +337,137 @@ public: SettingsCache(); QString getDataPath(); QString getSettingsPath(); - QString getCachePath() const; - QString getNetworkCachePath() const; - const QByteArray &getMainWindowGeometry() const + [[nodiscard]] QString getCachePath() const; + [[nodiscard]] QString getNetworkCachePath() const; + [[nodiscard]] const QByteArray &getMainWindowGeometry() const { return mainWindowGeometry; } - const QByteArray &getTokenDialogGeometry() const + [[nodiscard]] const QByteArray &getTokenDialogGeometry() const { return tokenDialogGeometry; } - const QByteArray &getSetsDialogGeometry() const + [[nodiscard]] const QByteArray &getSetsDialogGeometry() const { return setsDialogGeometry; } - QString getLang() const + [[nodiscard]] QString getLang() const { return lang; } - QString getDeckPath() const + [[nodiscard]] QString getDeckPath() const { return deckPath; } - QString getFiltersPath() const + [[nodiscard]] QString getFiltersPath() const { return filtersPath; } - QString getReplaysPath() const + [[nodiscard]] QString getReplaysPath() const { return replaysPath; } - QString getThemesPath() const + [[nodiscard]] QString getThemesPath() const { return themesPath; } - QString getPicsPath() const + [[nodiscard]] QString getPicsPath() const { return picsPath; } - QString getRedirectCachePath() const + [[nodiscard]] QString getRedirectCachePath() const { return redirectCachePath; } - QString getCustomPicsPath() const + [[nodiscard]] QString getCustomPicsPath() const { return customPicsPath; } - QString getCustomCardDatabasePath() const override + [[nodiscard]] QString getCustomCardDatabasePath() const override { return customCardDatabasePath; } - QString getCardDatabasePath() const override + [[nodiscard]] QString getCardDatabasePath() const override { return cardDatabasePath; } - QString getSpoilerCardDatabasePath() const override + [[nodiscard]] QString getSpoilerCardDatabasePath() const override { return spoilerDatabasePath; } - QString getTokenDatabasePath() const override + [[nodiscard]] QString getTokenDatabasePath() const override { return tokenDatabasePath; } - QString getThemeName() const + [[nodiscard]] QString getThemeName() const { return themeName; } - QString getHomeTabBackgroundSource() const + [[nodiscard]] QString getHomeTabBackgroundSource() const { return homeTabBackgroundSource; } - int getHomeTabBackgroundShuffleFrequency() const + [[nodiscard]] int getHomeTabBackgroundShuffleFrequency() const { return homeTabBackgroundShuffleFrequency; } - bool getTabVisualDeckStorageOpen() const + [[nodiscard]] bool getTabVisualDeckStorageOpen() const { return tabVisualDeckStorageOpen; } - bool getTabServerOpen() const + [[nodiscard]] bool getTabServerOpen() const { return tabServerOpen; } - bool getTabAccountOpen() const + [[nodiscard]] bool getTabAccountOpen() const { return tabAccountOpen; } - bool getTabDeckStorageOpen() const + [[nodiscard]] bool getTabDeckStorageOpen() const { return tabDeckStorageOpen; } - bool getTabReplaysOpen() const + [[nodiscard]] bool getTabReplaysOpen() const { return tabReplaysOpen; } - bool getTabAdminOpen() const + [[nodiscard]] bool getTabAdminOpen() const { return tabAdminOpen; } - bool getTabLogOpen() const + [[nodiscard]] bool getTabLogOpen() const { return tabLogOpen; } - QString getChatMentionColor() const + [[nodiscard]] QString getChatMentionColor() const { return chatMentionColor; } - QString getChatHighlightColor() const + [[nodiscard]] QString getChatHighlightColor() const { return chatHighlightColor; } - bool getPicDownload() const + [[nodiscard]] bool getPicDownload() const { return picDownload; } - bool getShowStatusBar() const + [[nodiscard]] bool getShowStatusBar() const { return showStatusBar; } - bool getNotificationsEnabled() const + [[nodiscard]] bool getNotificationsEnabled() const { return notificationsEnabled; } - bool getSpectatorNotificationsEnabled() const + [[nodiscard]] bool getSpectatorNotificationsEnabled() const { return spectatorNotificationsEnabled; } - bool getBuddyConnectNotificationsEnabled() const + [[nodiscard]] bool getBuddyConnectNotificationsEnabled() const { return buddyConnectNotificationsEnabled; } - bool getCheckUpdatesOnStartup() const + [[nodiscard]] bool getCheckUpdatesOnStartup() const { return checkUpdatesOnStartup; } @@ -480,263 +479,263 @@ public: { return startupCardUpdateCheckAlwaysUpdate; } - int getCardUpdateCheckInterval() const + [[nodiscard]] int getCardUpdateCheckInterval() const { return cardUpdateCheckInterval; } - QDate getLastCardUpdateCheck() const + [[nodiscard]] QDate getLastCardUpdateCheck() const { return lastCardUpdateCheck; } - bool getCardUpdateCheckRequired() const + [[nodiscard]] bool getCardUpdateCheckRequired() const { return getLastCardUpdateCheck().daysTo(QDateTime::currentDateTime().date()) >= getCardUpdateCheckInterval() && getLastCardUpdateCheck() != QDateTime::currentDateTime().date(); } - bool getNotifyAboutUpdates() const override + [[nodiscard]] bool getNotifyAboutUpdates() const override { return notifyAboutUpdates; } - bool getNotifyAboutNewVersion() const + [[nodiscard]] bool getNotifyAboutNewVersion() const { return notifyAboutNewVersion; } - bool getShowTipsOnStartup() const + [[nodiscard]] bool getShowTipsOnStartup() const { return showTipsOnStartup; } - QList getSeenTips() const + [[nodiscard]] QList getSeenTips() const { return seenTips; } - int getUpdateReleaseChannelIndex() const + [[nodiscard]] int getUpdateReleaseChannelIndex() const { return updateReleaseChannel; } - ReleaseChannel *getUpdateReleaseChannel() const + [[nodiscard]] ReleaseChannel *getUpdateReleaseChannel() const { return releaseChannels.at(qMax(0, updateReleaseChannel)); } - QList getUpdateReleaseChannels() const + [[nodiscard]] QList getUpdateReleaseChannels() const { return releaseChannels; } - bool getDoubleClickToPlay() const + [[nodiscard]] bool getDoubleClickToPlay() const { return doubleClickToPlay; } - bool getClickPlaysAllSelected() const + [[nodiscard]] bool getClickPlaysAllSelected() const { return clickPlaysAllSelected; } - bool getPlayToStack() const + [[nodiscard]] bool getPlayToStack() const { return playToStack; } - bool getDoNotDeleteArrowsInSubPhases() const + [[nodiscard]] bool getDoNotDeleteArrowsInSubPhases() const { return doNotDeleteArrowsInSubPhases; } - int getStartingHandSize() const + [[nodiscard]] int getStartingHandSize() const { return startingHandSize; } - bool getAnnotateTokens() const + [[nodiscard]] bool getAnnotateTokens() const { return annotateTokens; } - QByteArray getTabGameSplitterSizes() const + [[nodiscard]] QByteArray getTabGameSplitterSizes() const { return tabGameSplitterSizes; } - bool getShowShortcuts() const + [[nodiscard]] bool getShowShortcuts() const { return showShortcuts; } - bool getDisplayCardNames() const + [[nodiscard]] bool getDisplayCardNames() const { return displayCardNames; } - bool getOverrideAllCardArtWithPersonalPreference() const + [[nodiscard]] bool getOverrideAllCardArtWithPersonalPreference() const { return overrideAllCardArtWithPersonalPreference; } - bool getBumpSetsWithCardsInDeckToTop() const + [[nodiscard]] bool getBumpSetsWithCardsInDeckToTop() const { return bumpSetsWithCardsInDeckToTop; } - int getPrintingSelectorSortOrder() const + [[nodiscard]] int getPrintingSelectorSortOrder() const { return printingSelectorSortOrder; } - int getPrintingSelectorCardSize() const + [[nodiscard]] int getPrintingSelectorCardSize() const { return printingSelectorCardSize; } - bool getIncludeRebalancedCards() const + [[nodiscard]] bool getIncludeRebalancedCards() const { return includeRebalancedCards; } - bool getPrintingSelectorNavigationButtonsVisible() const + [[nodiscard]] bool getPrintingSelectorNavigationButtonsVisible() const { return printingSelectorNavigationButtonsVisible; } - bool getDeckEditorBannerCardComboBoxVisible() const + [[nodiscard]] bool getDeckEditorBannerCardComboBoxVisible() const { return deckEditorBannerCardComboBoxVisible; } - bool getDeckEditorTagsWidgetVisible() const + [[nodiscard]] bool getDeckEditorTagsWidgetVisible() const { return deckEditorTagsWidgetVisible; } - int getVisualDeckStorageSortingOrder() const + [[nodiscard]] int getVisualDeckStorageSortingOrder() const { return visualDeckStorageSortingOrder; } - bool getVisualDeckStorageShowFolders() const + [[nodiscard]] bool getVisualDeckStorageShowFolders() const { return visualDeckStorageShowFolders; } - bool getVisualDeckStorageShowTagFilter() const + [[nodiscard]] bool getVisualDeckStorageShowTagFilter() const { return visualDeckStorageShowTagFilter; } - QStringList getVisualDeckStorageDefaultTagsList() const + [[nodiscard]] QStringList getVisualDeckStorageDefaultTagsList() const { return visualDeckStorageDefaultTagsList; } - bool getVisualDeckStorageSearchFolderNames() const + [[nodiscard]] bool getVisualDeckStorageSearchFolderNames() const { return visualDeckStorageSearchFolderNames; } - bool getVisualDeckStorageShowBannerCardComboBox() const + [[nodiscard]] bool getVisualDeckStorageShowBannerCardComboBox() const { return visualDeckStorageShowBannerCardComboBox; } - bool getVisualDeckStorageShowTagsOnDeckPreviews() const + [[nodiscard]] bool getVisualDeckStorageShowTagsOnDeckPreviews() const { return visualDeckStorageShowTagsOnDeckPreviews; } - int getVisualDeckStorageCardSize() const + [[nodiscard]] int getVisualDeckStorageCardSize() const { return visualDeckStorageCardSize; } - bool getVisualDeckStorageDrawUnusedColorIdentities() const + [[nodiscard]] bool getVisualDeckStorageDrawUnusedColorIdentities() const { return visualDeckStorageDrawUnusedColorIdentities; } - int getVisualDeckStorageUnusedColorIdentitiesOpacity() const + [[nodiscard]] int getVisualDeckStorageUnusedColorIdentitiesOpacity() const { return visualDeckStorageUnusedColorIdentitiesOpacity; } - int getVisualDeckStorageTooltipType() const + [[nodiscard]] int getVisualDeckStorageTooltipType() const { return visualDeckStorageTooltipType; } - bool getVisualDeckStoragePromptForConversion() const + [[nodiscard]] bool getVisualDeckStoragePromptForConversion() const { return visualDeckStoragePromptForConversion; } - bool getVisualDeckStorageAlwaysConvert() const + [[nodiscard]] bool getVisualDeckStorageAlwaysConvert() const { return visualDeckStorageAlwaysConvert; } - bool getVisualDeckStorageInGame() const + [[nodiscard]] bool getVisualDeckStorageInGame() const { return visualDeckStorageInGame; } - bool getVisualDeckStorageSelectionAnimation() const + [[nodiscard]] bool getVisualDeckStorageSelectionAnimation() const { return visualDeckStorageSelectionAnimation; } - int getVisualDeckEditorCardSize() const + [[nodiscard]] int getVisualDeckEditorCardSize() const { return visualDeckEditorCardSize; } - int getVisualDatabaseDisplayCardSize() const + [[nodiscard]] int getVisualDatabaseDisplayCardSize() const { return visualDatabaseDisplayCardSize; } - int getEDHRecCardSize() const + [[nodiscard]] int getEDHRecCardSize() const { return edhrecCardSize; } - int getArchidektPreviewSize() const + [[nodiscard]] int getArchidektPreviewSize() const { return archidektPreviewSize; } - int getDefaultDeckEditorType() const + [[nodiscard]] int getDefaultDeckEditorType() const { return defaultDeckEditorType; } - bool getVisualDatabaseDisplayFilterToMostRecentSetsEnabled() const + [[nodiscard]] bool getVisualDatabaseDisplayFilterToMostRecentSetsEnabled() const { return visualDatabaseDisplayFilterToMostRecentSetsEnabled; } - int getVisualDatabaseDisplayFilterToMostRecentSetsAmount() const + [[nodiscard]] int getVisualDatabaseDisplayFilterToMostRecentSetsAmount() const { return visualDatabaseDisplayFilterToMostRecentSetsAmount; } - int getVisualDeckEditorSampleHandSize() const + [[nodiscard]] int getVisualDeckEditorSampleHandSize() const { return visualDeckEditorSampleHandSize; } - bool getHorizontalHand() const + [[nodiscard]] bool getHorizontalHand() const { return horizontalHand; } - bool getInvertVerticalCoordinate() const + [[nodiscard]] bool getInvertVerticalCoordinate() const { return invertVerticalCoordinate; } - int getMinPlayersForMultiColumnLayout() const + [[nodiscard]] int getMinPlayersForMultiColumnLayout() const { return minPlayersForMultiColumnLayout; } - bool getTapAnimation() const + [[nodiscard]] bool getTapAnimation() const { return tapAnimation; } - bool getAutoRotateSidewaysLayoutCards() const + [[nodiscard]] bool getAutoRotateSidewaysLayoutCards() const { return autoRotateSidewaysLayoutCards; } - bool getOpenDeckInNewTab() const + [[nodiscard]] bool getOpenDeckInNewTab() const { return openDeckInNewTab; } - int getRewindBufferingMs() const + [[nodiscard]] int getRewindBufferingMs() const { return rewindBufferingMs; } - bool getChatMention() const + [[nodiscard]] bool getChatMention() const { return chatMention; } - bool getChatMentionCompleter() const + [[nodiscard]] bool getChatMentionCompleter() const { return chatMentionCompleter; } - bool getChatMentionForeground() const + [[nodiscard]] bool getChatMentionForeground() const { return chatMentionForeground; } - bool getChatHighlightForeground() const + [[nodiscard]] bool getChatHighlightForeground() const { return chatHighlightForeground; } /** * Currently selected index for the `Group by X` QComboBox */ - int getZoneViewGroupByIndex() const + [[nodiscard]] int getZoneViewGroupByIndex() const { return zoneViewGroupByIndex; } /** * Currently selected index for the `Sort by X` QComboBox */ - int getZoneViewSortByIndex() const + [[nodiscard]] int getZoneViewSortByIndex() const { return zoneViewSortByIndex; } @@ -744,136 +743,136 @@ public: Returns if the view should be sorted into pile view. @return zoneViewPileView if the view should be sorted into pile view. */ - bool getZoneViewPileView() const + [[nodiscard]] bool getZoneViewPileView() const { return zoneViewPileView; } - bool getSoundEnabled() const + [[nodiscard]] bool getSoundEnabled() const { return soundEnabled; } - QString getSoundThemeName() const + [[nodiscard]] QString getSoundThemeName() const { return soundThemeName; } - bool getIgnoreUnregisteredUsers() const + [[nodiscard]] bool getIgnoreUnregisteredUsers() const { return ignoreUnregisteredUsers; } - bool getIgnoreUnregisteredUserMessages() const + [[nodiscard]] bool getIgnoreUnregisteredUserMessages() const { return ignoreUnregisteredUserMessages; } - int getPixmapCacheSize() const + [[nodiscard]] int getPixmapCacheSize() const { return pixmapCacheSize; } - int getNetworkCacheSizeInMB() const + [[nodiscard]] int getNetworkCacheSizeInMB() const { return networkCacheSize; } - int getRedirectCacheTtl() const + [[nodiscard]] int getRedirectCacheTtl() const { return redirectCacheTtl; } - bool getScaleCards() const + [[nodiscard]] bool getScaleCards() const { return scaleCards; } - int getStackCardOverlapPercent() const + [[nodiscard]] int getStackCardOverlapPercent() const { return verticalCardOverlapPercent; } - bool getShowMessagePopup() const + [[nodiscard]] bool getShowMessagePopup() const { return showMessagePopups; } - bool getShowMentionPopup() const + [[nodiscard]] bool getShowMentionPopup() const { return showMentionPopups; } - bool getRoomHistory() const + [[nodiscard]] bool getRoomHistory() const { return roomHistory; } - bool getLeftJustified() const + [[nodiscard]] bool getLeftJustified() const { return leftJustified; } - int getMasterVolume() const + [[nodiscard]] int getMasterVolume() const { return masterVolume; } - int getCardInfoViewMode() const + [[nodiscard]] int getCardInfoViewMode() const { return cardInfoViewMode; } - QStringList getCountries() const; - QString getHighlightWords() const + [[nodiscard]] QStringList getCountries() const; + [[nodiscard]] QString getHighlightWords() const { return highlightWords; } - QString getGameDescription() const + [[nodiscard]] QString getGameDescription() const { return gameDescription; } - int getMaxPlayers() const + [[nodiscard]] int getMaxPlayers() const { return maxPlayers; } - QString getGameTypes() const + [[nodiscard]] QString getGameTypes() const { return gameTypes; } - bool getOnlyBuddies() const + [[nodiscard]] bool getOnlyBuddies() const { return onlyBuddies; } - bool getOnlyRegistered() const + [[nodiscard]] bool getOnlyRegistered() const { return onlyRegistered; } - bool getSpectatorsAllowed() const + [[nodiscard]] bool getSpectatorsAllowed() const { return spectatorsAllowed; } - bool getSpectatorsNeedPassword() const + [[nodiscard]] bool getSpectatorsNeedPassword() const { return spectatorsNeedPassword; } - bool getSpectatorsCanTalk() const + [[nodiscard]] bool getSpectatorsCanTalk() const { return spectatorsCanTalk; } - bool getSpectatorsCanSeeEverything() const + [[nodiscard]] bool getSpectatorsCanSeeEverything() const { return spectatorsCanSeeEverything; } - int getDefaultStartingLifeTotal() const + [[nodiscard]] int getDefaultStartingLifeTotal() const { return defaultStartingLifeTotal; } - bool getShareDecklistsOnLoad() const + [[nodiscard]] bool getShareDecklistsOnLoad() const { return shareDecklistsOnLoad; } - bool getCreateGameAsSpectator() const + [[nodiscard]] bool getCreateGameAsSpectator() const { return createGameAsSpectator; } - bool getRememberGameSettings() const + [[nodiscard]] bool getRememberGameSettings() const { return rememberGameSettings; } - int getKeepAlive() const override + [[nodiscard]] int getKeepAlive() const override { return keepalive; } - int getTimeOut() const override + [[nodiscard]] int getTimeOut() const override { return timeout; } - int getMaxFontSize() const + [[nodiscard]] int getMaxFontSize() const { return maxFontSize; } @@ -901,73 +900,73 @@ public: { return useTearOffMenus; } - int getCardViewInitialRowsMax() const + [[nodiscard]] int getCardViewInitialRowsMax() const { return cardViewInitialRowsMax; } - int getCardViewExpandedRowsMax() const + [[nodiscard]] int getCardViewExpandedRowsMax() const { return cardViewExpandedRowsMax; } - bool getCloseEmptyCardView() const + [[nodiscard]] bool getCloseEmptyCardView() const { return closeEmptyCardView; } - bool getFocusCardViewSearchBar() const + [[nodiscard]] bool getFocusCardViewSearchBar() const { return focusCardViewSearchBar; } - ShortcutsSettings &shortcuts() const + [[nodiscard]] ShortcutsSettings &shortcuts() const { return *shortcutsSettings; } - CardDatabaseSettings *cardDatabase() const + [[nodiscard]] CardDatabaseSettings *cardDatabase() const { return cardDatabaseSettings; } - ServersSettings &servers() const + [[nodiscard]] ServersSettings &servers() const { return *serversSettings; } - MessageSettings &messages() const + [[nodiscard]] MessageSettings &messages() const { return *messageSettings; } - GameFiltersSettings &gameFilters() const + [[nodiscard]] GameFiltersSettings &gameFilters() const { return *gameFiltersSettings; } - LayoutsSettings &layouts() const + [[nodiscard]] LayoutsSettings &layouts() const { return *layoutsSettings; } - DownloadSettings &downloads() const + [[nodiscard]] DownloadSettings &downloads() const { return *downloadSettings; } - RecentsSettings &recents() const + [[nodiscard]] RecentsSettings &recents() const { return *recentsSettings; } - CardOverrideSettings &cardOverrides() const + [[nodiscard]] CardOverrideSettings &cardOverrides() const { return *cardOverrideSettings; } - DebugSettings &debug() const + [[nodiscard]] DebugSettings &debug() const { return *debugSettings; } - CardCounterSettings &cardCounters() const; + [[nodiscard]] CardCounterSettings &cardCounters() const; - bool getIsPortableBuild() const + [[nodiscard]] bool getIsPortableBuild() const { return isPortableBuild; } - bool getDownloadSpoilersStatus() const + [[nodiscard]] bool getDownloadSpoilersStatus() const { return mbDownloadSpoilers; } - bool getRoundCardCorners() const + [[nodiscard]] bool getRoundCardCorners() const { return roundCardCorners; } diff --git a/cockatrice/src/client/settings/card_counter_settings.h b/cockatrice/src/client/settings/card_counter_settings.h index f2c1c1d43..b1d467d67 100644 --- a/cockatrice/src/client/settings/card_counter_settings.h +++ b/cockatrice/src/client/settings/card_counter_settings.h @@ -7,7 +7,6 @@ #ifndef CARD_COUNTER_SETTINGS_H #define CARD_COUNTER_SETTINGS_H -#include #include class QSettings; @@ -20,9 +19,9 @@ class CardCounterSettings : public SettingsManager public: CardCounterSettings(const QString &settingsPath, QObject *parent = nullptr); - QColor color(int counterId) const; + [[nodiscard]] QColor color(int counterId) const; - QString displayName(int counterId) const; + [[nodiscard]] QString displayName(int counterId) const; public slots: void setColor(int counterId, const QColor &color); 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/client/settings/shortcut_treeview.h b/cockatrice/src/client/settings/shortcut_treeview.h index 229bf2ed2..8d74a6f1e 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.h +++ b/cockatrice/src/client/settings/shortcut_treeview.h @@ -7,7 +7,6 @@ #ifndef SHORTCUT_TREEVIEW_H #define SHORTCUT_TREEVIEW_H -#include #include #include #include @@ -22,7 +21,7 @@ public: explicit ShortcutFilterProxyModel(QObject *parent = nullptr); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; class ShortcutTreeView : public QTreeView diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 31e90bdc7..736f0c896 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -99,15 +99,15 @@ public: void setSequence(const QList &_sequence) { QList::operator=(_sequence); - }; - QString getName() const + } + [[nodiscard]] QString getName() const { return QApplication::translate("shortcutsTab", name.toUtf8().data()); - }; - QString getGroupName() const + } + [[nodiscard]] QString getGroupName() const { return ShortcutGroup::getGroupName(group); - }; + } private: QString name; @@ -120,24 +120,24 @@ class ShortcutsSettings : public QObject public: explicit ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr); - ShortcutKey getDefaultShortcut(const QString &name) const; - ShortcutKey getShortcut(const QString &name) const; - QKeySequence getSingleShortcut(const QString &name) const; - QString getDefaultShortcutString(const QString &name) const; - QString getShortcutString(const QString &name) const; - QString getShortcutFriendlyName(const QString &shortcutName) const; - QList getAllShortcutKeys() const + [[nodiscard]] ShortcutKey getDefaultShortcut(const QString &name) const; + [[nodiscard]] ShortcutKey getShortcut(const QString &name) const; + [[nodiscard]] QKeySequence getSingleShortcut(const QString &name) const; + [[nodiscard]] QString getDefaultShortcutString(const QString &name) const; + [[nodiscard]] QString getShortcutString(const QString &name) const; + [[nodiscard]] QString getShortcutFriendlyName(const QString &shortcutName) const; + [[nodiscard]] QList getAllShortcutKeys() const { return shortCuts.keys(); - }; + } void setShortcuts(const QString &name, const QList &Sequence); void setShortcuts(const QString &name, const QKeySequence &Sequence); void setShortcuts(const QString &name, const QString &sequences); - bool isKeyAllowed(const QString &name, const QString &sequences) const; - bool isValid(const QString &name, const QString &sequences) const; - QStringList findOverlaps(const QString &name, const QString &sequences) const; + [[nodiscard]] bool isKeyAllowed(const QString &name, const QString &sequences) const; + [[nodiscard]] bool isValid(const QString &name, const QString &sequences) const; + [[nodiscard]] QStringList findOverlaps(const QString &name, const QString &sequences) const; void resetAllShortcuts(); void clearAllShortcuts(); @@ -152,8 +152,8 @@ private: QString settingsFilePath; QHash shortCuts; - QString stringifySequence(const QList &Sequence) const; - QList parseSequenceString(const QString &stringSequence) const; + [[nodiscard]] QString stringifySequence(const QList &Sequence) const; + [[nodiscard]] QList parseSequenceString(const QString &stringSequence) const; const QHash defaultShortCuts = { {"MainWindow/aCheckCardUpdates", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Check for Card Updates..."), diff --git a/cockatrice/src/database/interface/settings_card_preference_provider.h b/cockatrice/src/database/interface/settings_card_preference_provider.h index 4197bb53c..6c31b9d6d 100644 --- a/cockatrice/src/database/interface/settings_card_preference_provider.h +++ b/cockatrice/src/database/interface/settings_card_preference_provider.h @@ -7,15 +7,15 @@ class SettingsCardPreferenceProvider : public ICardPreferenceProvider { public: - QString getCardPreferenceOverride(const QString &cardName) const override + [[nodiscard]] QString getCardPreferenceOverride(const QString &cardName) const override { return SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardName); } - bool getIncludeRebalancedCards() const override + [[nodiscard]] bool getIncludeRebalancedCards() const override { return SettingsCache::instance().getIncludeRebalancedCards(); - }; + } }; #endif // COCKATRICE_SETTINGS_CARD_PREFERENCE_PROVIDER_H diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index c9adc17e9..5cce5d323 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -1,5 +1,6 @@ #include "deck_filter_string.h" +#include #include #include #include @@ -12,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 @@ -21,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 @@ -117,12 +119,13 @@ static void setupParserRules() return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool { int count = 0; - deck->deckLoader->getDeckList()->forEachCard([&](InnerDecklistNode *, const DecklistCardNode *node) { + auto cardNodes = deck->deckLoader->getDeck().deckList.getCardNodes(); + for (auto node : cardNodes) { auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName()); if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) { count += node->getNumber(); } - }); + } return numberMatcher(count); }; }; @@ -136,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); }; }; @@ -155,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/filters/deck_filter_string.h b/cockatrice/src/filters/deck_filter_string.h index 15782b3ad..1b43d770d 100644 --- a/cockatrice/src/filters/deck_filter_string.h +++ b/cockatrice/src/filters/deck_filter_string.h @@ -10,10 +10,8 @@ #include "../interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h" #include -#include #include #include -#include inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string"); @@ -40,7 +38,7 @@ public: return filter(deck, info); } - bool valid() const + [[nodiscard]] bool valid() const { return _error.isEmpty(); } diff --git a/cockatrice/src/filters/filter_tree_model.h b/cockatrice/src/filters/filter_tree_model.h index ce6eb85fc..c6666a838 100644 --- a/cockatrice/src/filters/filter_tree_model.h +++ b/cockatrice/src/filters/filter_tree_model.h @@ -24,8 +24,8 @@ public slots: void addFilter(const CardFilter *f); void removeFilter(const CardFilter *f); void clearFiltersOfType(CardFilter::Attr filterType); - QList getFiltersOfType(CardFilter::Attr filterType) const; - QList allFilters() const; + [[nodiscard]] QList getFiltersOfType(CardFilter::Attr filterType) const; + [[nodiscard]] QList allFilters() const; private slots: void proxyBeginInsertRow(const FilterTreeNode *, int); @@ -34,23 +34,23 @@ private slots: void proxyEndRemoveRow(const FilterTreeNode *, int); private: - FilterTreeNode *indexToNode(const QModelIndex &idx) const; + [[nodiscard]] FilterTreeNode *indexToNode(const QModelIndex &idx) const; QModelIndex nodeIndex(const FilterTreeNode *node, int row, int column) const; public: FilterTreeModel(QObject *parent = nullptr); ~FilterTreeModel() override; - FilterTree *filterTree() const + [[nodiscard]] FilterTree *filterTree() const { return fTree; } - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - QModelIndex parent(const QModelIndex &ind) const override; - QModelIndex index(int row, int column, const QModelIndex &parent) const override; + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] QModelIndex parent(const QModelIndex &ind) const override; + [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent) const override; bool removeRows(int row, int count, const QModelIndex &parent) override; void clear(); }; diff --git a/cockatrice/src/game/board/abstract_counter.cpp b/cockatrice/src/game/board/abstract_counter.cpp index 985c78ad2..08d19ec8a 100644 --- a/cockatrice/src/game/board/abstract_counter.cpp +++ b/cockatrice/src/game/board/abstract_counter.cpp @@ -3,15 +3,14 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "translate_counter_name.h" #include #include -#include #include #include #include -#include #include #include #include diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index d5d0ebfe0..1352b3a05 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -3,6 +3,7 @@ #include "../../client/settings/cache_settings.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "../player/player_target.h" #include "../zones/card_zone.h" #include "card_item.h" diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index e786b5329..34c2f9a98 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -5,7 +5,7 @@ #include "../game_scene.h" #include "../phase.h" #include "../player/player.h" -#include "../zones/card_zone.h" +#include "../player/player_actions.h" #include "../zones/logic/view_zone_logic.h" #include "../zones/table_zone.h" #include "../zones/view_zone.h" diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index 64e9abc46..383545cf3 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item, const QPointF &_hotSpot, @@ -343,10 +343,7 @@ void DeckViewScene::rebuildTree() if (!deck) return; - InnerDecklistNode *listRoot = deck->getRoot(); - for (int i = 0; i < listRoot->size(); i++) { - auto *currentZone = dynamic_cast(listRoot->at(i)); - + for (auto *currentZone : deck->getZoneNodes()) { DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0); if (!container) { container = new DeckViewCardContainer(currentZone->getName()); diff --git a/cockatrice/src/game/deckview/deck_view.h b/cockatrice/src/game/deckview/deck_view.h index 027d0fafd..5abc558bd 100644 --- a/cockatrice/src/game/deckview/deck_view.h +++ b/cockatrice/src/game/deckview/deck_view.h @@ -9,11 +9,8 @@ #include "../board/abstract_card_drag_item.h" -#include #include #include -#include -#include #include class DeckList; diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index da24aa814..c7876a12f 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -8,13 +8,9 @@ #include "../../interface/widgets/dialogs/dlg_load_deck_from_website.h" #include "../../interface/widgets/dialogs/dlg_load_remote_deck.h" #include "../../interface/widgets/tabs/tab_game.h" -#include "../game_scene.h" #include "deck_view.h" #include -#include -#include -#include #include #include #include @@ -263,7 +259,7 @@ void DeckViewContainer::loadLocalDeck() void DeckViewContainer::loadDeckFromFile(const QString &filePath) { - DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(filePath); + DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(filePath); DeckLoader deck(this); bool success = deck.loadFromFile(filePath, fmt, true); @@ -273,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.")); @@ -312,8 +308,8 @@ void DeckViewContainer::loadFromClipboard() return; } - DeckLoader *deck = dlg.getDeckList(); - loadDeckFromDeckLoader(deck); + DeckList deck = dlg.getDeckList(); + loadDeckFromDeckList(deck); } void DeckViewContainer::loadFromWebsite() @@ -324,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(); } @@ -414,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 6594e339d..836c0c16a 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/game/dialogs/dlg_create_token.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -118,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; @@ -224,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/dialogs/dlg_create_token.h b/cockatrice/src/game/dialogs/dlg_create_token.h index 8235aa4cc..9a18f1a57 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.h +++ b/cockatrice/src/game/dialogs/dlg_create_token.h @@ -8,7 +8,6 @@ #define DLG_CREATETOKEN_H #include -#include class QLabel; class QLineEdit; diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp index 1cb47f368..7bfc4da75 100644 --- a/cockatrice/src/game/game_event_handler.cpp +++ b/cockatrice/src/game/game_event_handler.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index 2175f4aa2..77037cd6e 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -8,7 +8,6 @@ #include "zones/view_zone.h" #include "zones/view_zone_widget.h" -#include #include #include #include diff --git a/cockatrice/src/game/game_state.h b/cockatrice/src/game/game_state.h index b5d6a6572..54f6d9276 100644 --- a/cockatrice/src/game/game_state.h +++ b/cockatrice/src/game/game_state.h @@ -7,10 +7,8 @@ #ifndef COCKATRICE_GAME_STATE_H #define COCKATRICE_GAME_STATE_H -#include #include #include -#include class AbstractGame; class ServerInfo_PlayerProperties; diff --git a/cockatrice/src/game/log/message_log_widget.cpp b/cockatrice/src/game/log/message_log_widget.cpp index d4af5d00d..1ff3e32ff 100644 --- a/cockatrice/src/game/log/message_log_widget.cpp +++ b/cockatrice/src/game/log/message_log_widget.cpp @@ -6,12 +6,10 @@ #include "../board/translate_counter_name.h" #include "../phase.h" #include "../player/player.h" -#include "../zones/card_zone.h" #include <../../client/settings/card_counter_settings.h> #include #include -#include #include static const QString TABLE_ZONE_NAME = "table"; diff --git a/cockatrice/src/game/log/message_log_widget.h b/cockatrice/src/game/log/message_log_widget.h index a185cb288..369debd33 100644 --- a/cockatrice/src/game/log/message_log_widget.h +++ b/cockatrice/src/game/log/message_log_widget.h @@ -7,12 +7,9 @@ #ifndef MESSAGELOGWIDGET_H #define MESSAGELOGWIDGET_H -#include "../../client/translation.h" #include "../../interface/widgets/server/chat_view/chat_view.h" #include "../zones/logic/card_zone_logic.h" -#include - class AbstractGame; class CardItem; class GameEventContext; diff --git a/cockatrice/src/game/player/menu/grave_menu.cpp b/cockatrice/src/game/player/menu/grave_menu.cpp index 47d01bc77..d4faca42b 100644 --- a/cockatrice/src/game/player/menu/grave_menu.cpp +++ b/cockatrice/src/game/player/menu/grave_menu.cpp @@ -3,7 +3,6 @@ #include "../../abstract_game.h" #include "../player.h" #include "../player_actions.h" -#include "grave_menu.h" #include #include diff --git a/cockatrice/src/game/player/menu/player_menu.cpp b/cockatrice/src/game/player/menu/player_menu.cpp index 82747d482..3016a727f 100644 --- a/cockatrice/src/game/player/menu/player_menu.cpp +++ b/cockatrice/src/game/player/menu/player_menu.cpp @@ -3,12 +3,11 @@ #include "../../../interface/widgets/tabs/tab_game.h" #include "../../board/card_item.h" #include "../../zones/hand_zone.h" -#include "../card_menu_action_type.h" -#include "../player_actions.h" +#include "../../zones/pile_zone.h" +#include "../../zones/table_zone.h" #include "card_menu.h" #include "hand_menu.h" -#include #include PlayerMenu::PlayerMenu(Player *_player) : player(_player) diff --git a/cockatrice/src/game/player/menu/sideboard_menu.cpp b/cockatrice/src/game/player/menu/sideboard_menu.cpp index bfc40cfd3..beaabfcc6 100644 --- a/cockatrice/src/game/player/menu/sideboard_menu.cpp +++ b/cockatrice/src/game/player/menu/sideboard_menu.cpp @@ -1,6 +1,7 @@ #include "sideboard_menu.h" #include "../player.h" +#include "../player_actions.h" SideboardMenu::SideboardMenu(Player *player, QMenu *playerMenu) : QMenu(playerMenu) { diff --git a/cockatrice/src/game/player/menu/utility_menu.cpp b/cockatrice/src/game/player/menu/utility_menu.cpp index 15128defe..37bdcbbaf 100644 --- a/cockatrice/src/game/player/menu/utility_menu.cpp +++ b/cockatrice/src/game/player/menu/utility_menu.cpp @@ -1,9 +1,13 @@ #include "utility_menu.h" +#include "../../../interface/deck_loader/deck_loader.h" #include "../player.h" #include "../player_actions.h" #include "player_menu.h" +#include +#include + UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player) { PlayerActions *playerActions = player->getPlayerActions(); @@ -56,21 +60,19 @@ void UtilityMenu::populatePredefinedTokensMenu() clear(); setEnabled(false); predefinedTokens.clear(); - DeckLoader *_deck = player->getDeck(); + const DeckList &deckList = player->getDeck(); - if (!_deck) { + if (deckList.isEmpty()) { return; } - InnerDecklistNode *tokenZone = - dynamic_cast(_deck->getDeckList()->getRoot()->findChild(DECK_ZONE_TOKENS)); + auto tokenCardNodes = deckList.getCardNodes({DECK_ZONE_TOKENS}); - if (tokenZone) { - if (!tokenZone->empty()) - setEnabled(true); + if (!tokenCardNodes.isEmpty()) { + setEnabled(true); - for (int i = 0; i < tokenZone->size(); ++i) { - const QString tokenName = tokenZone->at(i)->getName(); + for (int i = 0; i < tokenCardNodes.size(); ++i) { + const QString tokenName = tokenCardNodes[i]->getName(); predefinedTokens.append(tokenName); QAction *a = addAction(tokenName); if (i < 10) { diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index ffb26c26e..0723ae6bc 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -11,7 +11,7 @@ #include "../zones/pile_zone.h" #include "../zones/stack_zone.h" #include "../zones/table_zone.h" -#include "../zones/view_zone.h" +#include "player_actions.h" #include "player_target.h" #include @@ -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 0751abb14..0a03b3abe 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -9,9 +9,12 @@ #include "../../game_graphics/board/abstract_graphics_item.h" #include "../../interface/widgets/menus/tearoff_menu.h" -#include "../dialogs/dlg_create_token.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" +#include "../zones/logic/table_zone_logic.h" #include "menu/player_menu.h" -#include "player_actions.h" #include "player_area.h" #include "player_event_handler.h" #include "player_graphics_item.h" @@ -20,9 +23,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -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(); @@ -113,7 +113,7 @@ public: [[nodiscard]] PlayerActions *getPlayerActions() const { return playerActions; - }; + } [[nodiscard]] PlayerEventHandler *getPlayerEventHandler() const { @@ -123,16 +123,16 @@ public: [[nodiscard]] PlayerInfo *getPlayerInfo() const { return playerInfo; - }; + } [[nodiscard]] PlayerMenu *getPlayerMenu() const { 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 e6efaf854..444eecb0d 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -5,14 +5,15 @@ #include "../board/card_item.h" #include "../dialogs/dlg_move_top_cards_until.h" #include "../dialogs/dlg_roll_dice.h" +#include "../zones/hand_zone.h" #include "../zones/logic/view_zone_logic.h" +#include "../zones/table_zone.h" #include "card_menu_action_type.h" #include #include #include #include -#include #include #include #include @@ -217,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_actions.h b/cockatrice/src/game/player/player_actions.h index 9c775691c..e0f86e4fc 100644 --- a/cockatrice/src/game/player/player_actions.h +++ b/cockatrice/src/game/player/player_actions.h @@ -7,6 +7,7 @@ #ifndef COCKATRICE_PLAYER_ACTIONS_H #define COCKATRICE_PLAYER_ACTIONS_H +#include "../dialogs/dlg_create_token.h" #include "event_processing_options.h" #include "player.h" diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index 972229489..331605918 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -6,6 +6,7 @@ #include "../board/card_list.h" #include "../zones/view_zone.h" #include "player.h" +#include "player_actions.h" #include #include @@ -77,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/player/player_graphics_item.cpp b/cockatrice/src/game/player/player_graphics_item.cpp index 27fbc82c8..30648c926 100644 --- a/cockatrice/src/game/player/player_graphics_item.cpp +++ b/cockatrice/src/game/player/player_graphics_item.cpp @@ -1,7 +1,12 @@ #include "player_graphics_item.h" #include "../../interface/widgets/tabs/tab_game.h" +#include "../board/abstract_card_item.h" #include "../hand_counter.h" +#include "../zones/hand_zone.h" +#include "../zones/pile_zone.h" +#include "../zones/stack_zone.h" +#include "../zones/table_zone.h" PlayerGraphicsItem::PlayerGraphicsItem(Player *_player) : player(_player) { diff --git a/cockatrice/src/game/player/player_info.h b/cockatrice/src/game/player/player_info.h index d60c245e2..2e9b49d6d 100644 --- a/cockatrice/src/game/player/player_info.h +++ b/cockatrice/src/game/player/player_info.h @@ -7,11 +7,6 @@ #ifndef COCKATRICE_PLAYER_INFO_H #define COCKATRICE_PLAYER_INFO_H -#include "../../interface/deck_loader/deck_loader.h" -#include "../zones/hand_zone.h" -#include "../zones/pile_zone.h" -#include "../zones/stack_zone.h" -#include "../zones/table_zone.h" #include "player_target.h" #include diff --git a/cockatrice/src/game/player/player_list_widget.cpp b/cockatrice/src/game/player/player_list_widget.cpp index 7cf7c34a6..9506e0729 100644 --- a/cockatrice/src/game/player/player_list_widget.cpp +++ b/cockatrice/src/game/player/player_list_widget.cpp @@ -4,15 +4,11 @@ #include "../../interface/widgets/server/user/user_context_menu.h" #include "../../interface/widgets/server/user/user_list_manager.h" #include "../../interface/widgets/server/user/user_list_widget.h" -#include "../../interface/widgets/tabs/tab_account.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/tabs/tab_supervisor.h" -#include #include -#include #include -#include #include #include #include diff --git a/cockatrice/src/game/player/player_target.h b/cockatrice/src/game/player/player_target.h index aaa0f0610..b60464e12 100644 --- a/cockatrice/src/game/player/player_target.h +++ b/cockatrice/src/game/player/player_target.h @@ -11,7 +11,6 @@ #include "../board/abstract_counter.h" #include "../board/arrow_target.h" -#include #include class Player; diff --git a/cockatrice/src/game/zones/hand_zone.cpp b/cockatrice/src/game/zones/hand_zone.cpp index 860881f38..cc3b44910 100644 --- a/cockatrice/src/game/zones/hand_zone.cpp +++ b/cockatrice/src/game/zones/hand_zone.cpp @@ -5,6 +5,7 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include #include diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.cpp b/cockatrice/src/game/zones/logic/card_zone_logic.cpp index a5dc64b50..bd32eab3e 100644 --- a/cockatrice/src/game/zones/logic/card_zone_logic.cpp +++ b/cockatrice/src/game/zones/logic/card_zone_logic.cpp @@ -2,7 +2,7 @@ #include "../../board/card_item.h" #include "../../player/player.h" -#include "../pile_zone.h" +#include "../../player/player_actions.h" #include "../view_zone.h" #include "view_zone_logic.h" @@ -10,7 +10,6 @@ #include #include #include -#include /** * @param _player the player that the zone belongs to diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.h b/cockatrice/src/game/zones/logic/card_zone_logic.h index 4d02b7dd1..1aa46effd 100644 --- a/cockatrice/src/game/zones/logic/card_zone_logic.h +++ b/cockatrice/src/game/zones/logic/card_zone_logic.h @@ -52,7 +52,7 @@ public: void rawInsertCard(CardItem *card, int index) { cards.insert(index, card); - }; + } [[nodiscard]] const CardList &getCards() const { diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index cafb29038..2d1390826 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -3,6 +3,7 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "logic/pile_zone_logic.h" #include "view_zone.h" diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp index 0c0501705..820008b27 100644 --- a/cockatrice/src/game/zones/stack_zone.cpp +++ b/cockatrice/src/game/zones/stack_zone.cpp @@ -6,10 +6,10 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "logic/stack_zone_logic.h" #include -#include #include StackZone::StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent) diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index 55aaeaa39..c76514350 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -6,11 +6,11 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "logic/table_zone_logic.h" #include #include -#include #include #include #include diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index d5eea9dfe..4ae25989e 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -3,6 +3,7 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "logic/view_zone_logic.h" #include @@ -10,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index e2f242883..91abb4663 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -6,6 +6,7 @@ #include "../board/card_item.h" #include "../game_scene.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "view_zone.h" #include @@ -16,7 +17,6 @@ #include #include #include -#include #include /** 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.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp index 296355940..06a0476c9 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h index 29bdf26c8..4000fd99a 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h @@ -5,7 +5,6 @@ #include "card_picture_loader_worker.h" #include -#include inline Q_LOGGING_CATEGORY(CardPictureLoaderLog, "card_picture_loader"); inline Q_LOGGING_CATEGORY(CardPictureLoaderCardBackCacheFailLog, "card_picture_loader.card_back_cache_fail"); diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h index 84f16cc38..44163d1db 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h @@ -1,8 +1,6 @@ #ifndef PICTURE_LOADER_LOCAL_H #define PICTURE_LOADER_LOCAL_H -#include -#include #include #include @@ -42,7 +40,7 @@ public: * * Uses a set of name variants and folder paths to attempt to locate the correct image. */ - QImage tryLoad(const ExactCard &toLoad) const; + [[nodiscard]] QImage tryLoad(const ExactCard &toLoad) const; private: QString picsPath; ///< Path to standard card image folder @@ -72,10 +70,10 @@ private: * Uses several filename patterns to match card images, in order from * most-specific to least-specific. */ - QImage tryLoadCardImageFromDisk(const QString &setName, - const QString &correctedCardName, - const QString &collectorNumber, - const QString &providerId) const; + [[nodiscard]] QImage tryLoadCardImageFromDisk(const QString &setName, + const QString &correctedCardName, + const QString &collectorNumber, + const QString &providerId) const; private slots: /** diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h index 2e3233c2d..c9a272b68 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h @@ -1,11 +1,11 @@ #ifndef PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H #define PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H -#include "card_picture_loader_worker_work.h" - #include #include +#include #include +#include /** * @class CardPictureLoaderRequestStatusDisplayWidget diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h index cb469269c..7f1f509db 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h @@ -6,7 +6,6 @@ #include #include -#include #include /** 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 a2da3c953..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 @@ -9,8 +9,8 @@ #include #include #include -#include #include +#include static constexpr int MAX_REQUESTS_PER_SEC = 10; @@ -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 * @@ -87,6 +85,7 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture } QNetworkRequest req(url); + req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); if (!picDownload) { req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); } diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp index e74103419..d5aac645e 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp @@ -7,11 +7,9 @@ #include #include #include -#include #include #include #include -#include // Card back returned by gatherer when card is not found static const QStringList MD5_BLACKLIST = {"db0c48db407a907c16ade38de048a441"}; diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h index b0a555c86..b57e57644 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h @@ -1,7 +1,6 @@ #ifndef PICTURE_TO_LOAD_H #define PICTURE_TO_LOAD_H -#include #include inline Q_LOGGING_CATEGORY(CardPictureToLoadLog, "card_picture_loader.picture_to_load"); diff --git a/cockatrice/src/interface/deck_loader/card_node_function.cpp b/cockatrice/src/interface/deck_loader/card_node_function.cpp new file mode 100644 index 000000000..9441fe428 --- /dev/null +++ b/cockatrice/src/interface/deck_loader/card_node_function.cpp @@ -0,0 +1,40 @@ +#include "card_node_function.h" + +#include +#include +#include + +void CardNodeFunction::SetProviderIdToPreferred::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const +{ + Q_UNUSED(node); + PrintingInfo preferredPrinting = CardDatabaseManager::query()->getPreferredPrinting(card->getName()); + QString providerId = preferredPrinting.getUuid(); + QString setShortName = preferredPrinting.getSet()->getShortName(); + QString collectorNumber = preferredPrinting.getProperty("num"); + + card->setCardProviderId(providerId); + card->setCardCollectorNumber(collectorNumber); + card->setCardSetShortName(setShortName); +} + +void CardNodeFunction::ClearPrintingData::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const +{ + Q_UNUSED(node); + card->setCardSetShortName(nullptr); + card->setCardCollectorNumber(nullptr); + card->setCardProviderId(nullptr); +} + +void CardNodeFunction::ResolveProviderId::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const +{ + Q_UNUSED(node); + // Retrieve the providerId based on setName and collectorNumber + QString providerId = + CardDatabaseManager::getInstance() + ->query() + ->getSpecificPrinting(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber()) + .getUuid(); + + // Set the providerId on the card + card->setCardProviderId(providerId); +} \ No newline at end of file diff --git a/cockatrice/src/interface/deck_loader/card_node_function.h b/cockatrice/src/interface/deck_loader/card_node_function.h new file mode 100644 index 000000000..398dc0c0e --- /dev/null +++ b/cockatrice/src/interface/deck_loader/card_node_function.h @@ -0,0 +1,39 @@ +#ifndef COCKATRICE_DECK_FUNCTION_H +#define COCKATRICE_DECK_FUNCTION_H + +class DecklistCardNode; +class InnerDecklistNode; + +/** + * Functions to be used with DeckList::forEachCard + */ +namespace CardNodeFunction +{ + +/** + * @brief Sets the providerId of the card to the preferred printing. + */ +struct SetProviderIdToPreferred +{ + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const; +}; + +/** + * @brief Clears all fields on the card related to the printing + */ +struct ClearPrintingData +{ + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const; +}; + +/** + * @brief Sets the providerId of the card based on its set name and collector number. + */ +struct ResolveProviderId +{ + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const; +}; + +} // namespace CardNodeFunction + +#endif // COCKATRICE_DECK_FUNCTION_H diff --git a/cockatrice/src/interface/deck_loader/deck_file_format.cpp b/cockatrice/src/interface/deck_loader/deck_file_format.cpp new file mode 100644 index 000000000..3c88cde4e --- /dev/null +++ b/cockatrice/src/interface/deck_loader/deck_file_format.cpp @@ -0,0 +1,9 @@ +#include "deck_file_format.h" + +DeckFileFormat::Format DeckFileFormat::getFormatFromName(const QString &fileName) +{ + if (fileName.endsWith(".cod", Qt::CaseInsensitive)) { + return Cockatrice; + } + return PlainText; +} \ No newline at end of file diff --git a/cockatrice/src/interface/deck_loader/deck_file_format.h b/cockatrice/src/interface/deck_loader/deck_file_format.h new file mode 100644 index 000000000..995de32c0 --- /dev/null +++ b/cockatrice/src/interface/deck_loader/deck_file_format.h @@ -0,0 +1,36 @@ +#ifndef COCKATRICE_DECK_FILE_FORMAT_H +#define COCKATRICE_DECK_FILE_FORMAT_H +#include + +namespace DeckFileFormat +{ + +/** + * The deck file formats that Cockatrice supports. + */ +enum Format +{ + /** + * Plaintext deck files, a format that is intended to be widely supported among different programs. + * This format does not support Cockatrice specific features such as banner cards or tags. + */ + PlainText, + + /** + * This is cockatrice's native deck file format, and supports deck metadata such as banner cards and tags. + * Stored as .cod files. + */ + Cockatrice +}; + +/** + * Determines what deck file format the given filename corresponds to. + * + * @param fileName The filename + * @return The deck format + */ +Format getFormatFromName(const QString &fileName); + +} // namespace DeckFileFormat + +#endif // COCKATRICE_DECK_FILE_FORMAT_H diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index 320302142..45c3ec1ba 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -18,24 +18,18 @@ #include #include #include -#include +#include const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.dek", "*.txt", "*.mwDeck"}; 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()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1) +DeckLoader::DeckLoader(QObject *parent) : QObject(parent) { } -DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) - : QObject(parent), deckList(_deckList), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1) -{ -} - -bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest) +bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -43,18 +37,19 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool user } bool result = false; + DeckList deckList = DeckList(); switch (fmt) { - case PlainTextFormat: - result = deckList->loadFromFile_Plain(&file); + case DeckFileFormat::PlainText: + result = deckList.loadFromFile_Plain(&file); break; - case CockatriceFormat: { - result = deckList->loadFromFile_Native(&file); + case DeckFileFormat::Cockatrice: { + 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); - fmt = PlainTextFormat; + result = deckList.loadFromFile_Plain(&file); + fmt = DeckFileFormat::PlainText; } break; } @@ -64,8 +59,11 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool user } if (result) { - lastFileName = fileName; - lastFileFormat = fmt; + loadedDeck.deckList = deckList; + loadedDeck.lastLoadInfo = { + .fileName = fileName, + .fileFormat = fmt, + }; if (userRequest) { updateLastLoadedTimestamp(fileName, fmt); } @@ -77,7 +75,7 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool user return result; } -bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest) +bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest) { auto *watcher = new QFutureWatcher(this); @@ -86,8 +84,10 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool watcher->deleteLater(); if (result) { - lastFileName = fileName; - lastFileFormat = fmt; + loadedDeck.lastLoadInfo = { + .fileName = fileName, + .fileFormat = fmt, + }; if (userRequest) { updateLastLoadedTimestamp(fileName, fmt); } @@ -104,14 +104,14 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool } switch (fmt) { - case PlainTextFormat: - return deckList->loadFromFile_Plain(&file); - case CockatriceFormat: { + case DeckFileFormat::PlainText: + 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; } @@ -127,18 +127,18 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) { - bool result = deckList->loadFromString_Native(nativeString); + bool result = loadedDeck.deckList.loadFromString_Native(nativeString); if (result) { - lastFileName = QString(); - lastFileFormat = CockatriceFormat; - lastRemoteDeckId = remoteDeckId; + loadedDeck.lastLoadInfo = { + .remoteDeckId = remoteDeckId, + }; emit deckLoaded(); } return result; } -bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) +bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt) { QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { @@ -147,18 +147,20 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) bool result = false; switch (fmt) { - case PlainTextFormat: - result = deckList->saveToFile_Plain(&file); + case DeckFileFormat::PlainText: + result = loadedDeck.deckList.saveToFile_Plain(&file); break; - case CockatriceFormat: - result = deckList->saveToFile_Native(&file); + case DeckFileFormat::Cockatrice: + result = loadedDeck.deckList.saveToFile_Native(&file); qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result; break; } if (result) { - lastFileName = fileName; - lastFileFormat = fmt; + loadedDeck.lastLoadInfo = { + .fileName = fileName, + .fileFormat = fmt, + }; qCInfo(DeckLoaderLog) << "Deck was saved -" << result; } @@ -168,7 +170,7 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) return result; } -bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt) +bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt) { QFileInfo fileInfo(fileName); if (!fileInfo.exists()) { @@ -189,20 +191,22 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat f // Perform file modifications switch (fmt) { - case PlainTextFormat: - result = deckList->saveToFile_Plain(&file); + case DeckFileFormat::PlainText: + result = loadedDeck.deckList.saveToFile_Plain(&file); break; - case CockatriceFormat: - deckList->setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); - result = deckList->saveToFile_Native(&file); + case DeckFileFormat::Cockatrice: + 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) { - lastFileName = fileName; - lastFileFormat = fmt; + loadedDeck.lastLoadInfo = { + .fileName = fileName, + .fileFormat = fmt, + }; // Re-open the file and set the original timestamp if (!file.open(QIODevice::ReadWrite)) { @@ -263,39 +267,35 @@ static QString toDecklistExportString(const DecklistCardNode *card) return cardString; } +/** + * Converts all cards in the list to their decklist export string and joins them into one string + */ +static QString toDecklistExportString(const QList &cardNodes) +{ + QString result; + + for (auto cardNode : cardNodes) { + result += toDecklistExportString(cardNode); + } + + return result; +} + /** * Export deck to decklist function, called to format the deck in a way to be sent to a server * * @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) + "/?"; - // Create two strings to pass to function - QString mainBoardCards, sideBoardCards; - // Set up the function to call - auto formatDeckListForExport = [&mainBoardCards, &sideBoardCards](const auto *node, const auto *card) { - // Get the card name - CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName()); - if (!dbCard || dbCard->getIsToken()) { - // If it's a token, we don't care about the card. - return; - } + // export all cards in zone + QString mainBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_MAIN})); + QString sideBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_SIDE})); - // Check if it's a sideboard card. - if (node->getName() == DECK_ZONE_SIDE) { - sideBoardCards += toDecklistExportString(card); - } else { - // If it's a mainboard card, do the same thing, but for the mainboard card string - mainBoardCards += toDecklistExportString(card); - } - }; - - // call our struct function for each card in the deck - deckList->forEachCard(formatDeckListForExport); // Remove the extra return at the end of the last cards mainBoardCards.chop(3); sideBoardCards.chop(3); @@ -310,113 +310,7 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi return deckString; } -// This struct is here to support the forEachCard function call, defined in decklist. -// It requires a function to be called for each card, and it will set the providerId to the preferred printing. -struct SetProviderIdToPreferred -{ - // Main operator for struct, allowing the foreachcard to work. - SetProviderIdToPreferred() - { - } - - void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const - { - Q_UNUSED(node); - PrintingInfo preferredPrinting = CardDatabaseManager::query()->getPreferredPrinting(card->getName()); - QString providerId = preferredPrinting.getUuid(); - QString setShortName = preferredPrinting.getSet()->getShortName(); - QString collectorNumber = preferredPrinting.getProperty("num"); - - card->setCardProviderId(providerId); - card->setCardCollectorNumber(collectorNumber); - card->setCardSetShortName(setShortName); - } -}; - -/** - * This function iterates through each card in the decklist and sets the providerId - * on each card based on its set name and collector number. - * - * @param deckList The decklist to modify - */ -void DeckLoader::setProviderIdToPreferredPrinting(const DeckList *deckList) -{ - // Set up the struct to call. - SetProviderIdToPreferred setProviderIdToPreferred; - - // Call the forEachCard method for each card in the deck - deckList->forEachCard(setProviderIdToPreferred); -} - -/** - * Sets the providerId on each card in the decklist based on its set name and collector number. - * - * @param deckList The decklist to modify - */ -void DeckLoader::resolveSetNameAndNumberToProviderID(const DeckList *deckList) -{ - auto setProviderId = [](const auto node, const auto card) { - Q_UNUSED(node); - // Retrieve the providerId based on setName and collectorNumber - QString providerId = - CardDatabaseManager::getInstance() - ->query() - ->getSpecificPrinting(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber()) - .getUuid(); - - // Set the providerId on the card - card->setCardProviderId(providerId); - }; - - deckList->forEachCard(setProviderId); -} - -// This struct is here to support the forEachCard function call, defined in decklist. -// It requires a function to be called for each card, and it will set the providerId. -struct ClearSetNameNumberAndProviderId -{ - // Main operator for struct, allowing the foreachcard to work. - ClearSetNameNumberAndProviderId() - { - } - - void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const - { - Q_UNUSED(node); - // Set the providerId on the card - card->setCardSetShortName(nullptr); - card->setCardCollectorNumber(nullptr); - card->setCardProviderId(nullptr); - } -}; - -/** - * Clears the set name and numbers on each card in the decklist. - * - * @param deckList The decklist to modify - */ -void DeckLoader::clearSetNamesAndNumbers(const DeckList *deckList) -{ - auto clearSetNameAndNumber = [](const auto node, auto card) { - Q_UNUSED(node) - // Set the providerId on the card - card->setCardSetShortName(nullptr); - card->setCardCollectorNumber(nullptr); - card->setCardProviderId(nullptr); - }; - - deckList->forEachCard(clearSetNameAndNumber); -} - -DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName) -{ - if (fileName.endsWith(".cod", Qt::CaseInsensitive)) { - return CockatriceFormat; - } - return PlainTextFormat; -} - -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); @@ -426,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) { @@ -435,9 +329,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, } // loop zones - for (int i = 0; i < deckList->getRoot()->size(); i++) { - const auto *zoneNode = dynamic_cast(deckList->getRoot()->at(i)); - + for (auto zoneNode : deckList.getZoneNodes()) { saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber); // end of zone @@ -447,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"; } @@ -542,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); @@ -558,12 +450,12 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) bool result = false; // Perform file modifications based on the detected format - switch (getFormatFromName(fileName)) { - case PlainTextFormat: + 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 CockatriceFormat: + case DeckFileFormat::Cockatrice: qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; result = true; break; @@ -582,37 +474,16 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) } else { qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; } - lastFileName = newFileName; - lastFileFormat = CockatriceFormat; + loadedDeck.lastLoadInfo = { + .fileName = newFileName, + .fileFormat = DeckFileFormat::Cockatrice, + }; } 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, InnerDecklistNode *node) +void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node) { const int totalColumns = 2; @@ -672,7 +543,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node) cursor->movePosition(QTextCursor::End); } -void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList) +void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList) { QTextDocument doc; @@ -688,19 +559,18 @@ 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 (int i = 0; i < deckList->getRoot()->size(); i++) { + for (auto zoneNode : deckList.getZoneNodes()) { cursor.insertHtml("
"); - // cursor.insertHtml("
"); cursor.insertBlock(headerBlockFormat, headerCharFormat); - printDeckListNode(&cursor, dynamic_cast(deckList->getRoot()->at(i))); + printDeckListNode(&cursor, zoneNode); } doc.print(printer); diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index 57305b7de..ec5636995 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -7,14 +7,16 @@ #ifndef DECK_LOADER_H #define DECK_LOADER_H +#include "loaded_deck.h" + #include #include #include #include -inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader") +inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader"); - class DeckLoader : public QObject +class DeckLoader : public QObject { Q_OBJECT signals: @@ -22,12 +24,6 @@ signals: void loadFinished(bool success); public: - enum FileFormat - { - PlainTextFormat, - CockatriceFormat - }; - /** * Supported file extensions for decklist files */ @@ -45,56 +41,29 @@ public: }; private: - DeckList *deckList; - QString lastFileName; - FileFormat lastFileFormat; - int lastRemoteDeckId; + LoadedDeck loadedDeck; public: DeckLoader(QObject *parent); - DeckLoader(QObject *parent, DeckList *_deckList); DeckLoader(const DeckLoader &) = delete; DeckLoader &operator=(const DeckLoader &) = delete; - const QString &getLastFileName() const + [[nodiscard]] bool hasNotBeenLoaded() const { - return lastFileName; - } - void setLastFileName(const QString &_lastFileName) - { - lastFileName = _lastFileName; - } - FileFormat getLastFileFormat() const - { - return lastFileFormat; - } - int getLastRemoteDeckId() const - { - return lastRemoteDeckId; + return loadedDeck.lastLoadInfo.isEmpty(); } - bool hasNotBeenLoaded() const - { - return getLastFileName().isEmpty() && getLastRemoteDeckId() == -1; - } - - static void clearSetNamesAndNumbers(const DeckList *deckList); - static FileFormat getFormatFromName(const QString &fileName); - - bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false); - bool loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest); + bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false); + bool loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest); bool loadFromRemote(const QString &nativeString, int remoteDeckId); - bool saveToFile(const QString &fileName, FileFormat fmt); - bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt); + 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 setProviderIdToPreferredPrinting(const DeckList *deckList); - static void resolveSetNameAndNumberToProviderID(const DeckList *deckList); - - 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); @@ -103,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, InnerDecklistNode *node); - static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList); + static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node); + static void saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList); static void saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode *zoneNode, @@ -125,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.cpp b/cockatrice/src/interface/deck_loader/loaded_deck.cpp new file mode 100644 index 000000000..633545718 --- /dev/null +++ b/cockatrice/src/interface/deck_loader/loaded_deck.cpp @@ -0,0 +1,11 @@ +#include "loaded_deck.h" + +bool LoadedDeck::LoadInfo::isEmpty() const +{ + return fileName.isEmpty() && remoteDeckId == NON_REMOTE_ID; +} + +bool LoadedDeck::isEmpty() const +{ + return deckList.isEmpty() && lastLoadInfo.isEmpty(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/deck_loader/loaded_deck.h b/cockatrice/src/interface/deck_loader/loaded_deck.h new file mode 100644 index 000000000..e07cd0db6 --- /dev/null +++ b/cockatrice/src/interface/deck_loader/loaded_deck.h @@ -0,0 +1,39 @@ +#ifndef COCKATRICE_LOADED_DECK_H +#define COCKATRICE_LOADED_DECK_H + +#include "deck_file_format.h" +#include "libcockatrice/deck_list/deck_list.h" + +#include + +/** + * @brief Represents a deck that was loaded from somewhere. + * Contains the DeckList itself, as well as info about where it was loaded from. + */ +struct LoadedDeck +{ + + /** + * @brief Information about where the deck was loaded from. + * + * For local decks, the remoteDeckId field will always be -1. + * For remote decks, fileName will be empty and fileFormat will always be CockatriceFormat + */ + struct LoadInfo + { + static constexpr int NON_REMOTE_ID = -1; + + QString fileName = ""; + DeckFileFormat::Format fileFormat = DeckFileFormat::Cockatrice; + int remoteDeckId = NON_REMOTE_ID; + + bool isEmpty() const; + }; + + DeckList deckList; ///< The decklist itself + LoadInfo lastLoadInfo = {}; ///< info about where the deck was loaded from + + bool isEmpty() const; +}; + +#endif // COCKATRICE_LOADED_DECK_H diff --git a/cockatrice/src/interface/layouts/flow_layout.h b/cockatrice/src/interface/layouts/flow_layout.h index f4c9e3001..cf109d260 100644 --- a/cockatrice/src/interface/layouts/flow_layout.h +++ b/cockatrice/src/interface/layouts/flow_layout.h @@ -23,9 +23,9 @@ public: ~FlowLayout() override; void insertWidgetAtIndex(QWidget *toInsert, int index); - QSize calculateMinimumSizeHorizontal() const; - QSize calculateSizeHintVertical() const; - QSize calculateMinimumSizeVertical() const; + [[nodiscard]] QSize calculateMinimumSizeHorizontal() const; + [[nodiscard]] QSize calculateSizeHintVertical() const; + [[nodiscard]] QSize calculateMinimumSizeVertical() const; void addItem(QLayoutItem *item) override; [[nodiscard]] int count() const override; [[nodiscard]] QLayoutItem *itemAt(int index) const override; @@ -48,7 +48,7 @@ public: void layoutSingleColumn(const QVector &colItems, int x, int y); [[nodiscard]] QSize sizeHint() const override; [[nodiscard]] QSize minimumSize() const override; - QSize calculateSizeHintHorizontal() const; + [[nodiscard]] QSize calculateSizeHintHorizontal() const; protected: QList items; // List to store layout items 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/additional_info/color_identity_widget.cpp b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp index 20f4b29df..e8beae61e 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.cpp b/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.cpp index e5d1127aa..fcbb69c2e 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h index 2ae76e9c0..8d2c6813f 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h +++ b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h @@ -23,15 +23,15 @@ public: [[nodiscard]] bool isColorActive() const { return isActive; - }; + } [[nodiscard]] QString getSymbol() const { return symbol; - }; + } [[nodiscard]] QChar getSymbolChar() const { return symbol[0]; - }; + } void loadManaIcon(); 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 7734a61d0..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 @@ -3,7 +3,6 @@ #include "../card_info_picture_with_text_overlay_widget.h" #include -#include #include #include #include @@ -33,7 +32,10 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, CardGroupDisplayWidget::updateCardDisplays(); connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition); - connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &CardGroupDisplayWidget::onSelectionChanged); + if (selectionModel) { + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, + &CardGroupDisplayWidget::onSelectionChanged); + } connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); } @@ -89,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()); @@ -112,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); @@ -180,7 +183,9 @@ void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSort void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event) { QWidget::mousePressEvent(event); - selectionModel->clearSelection(); + if (selectionModel) { + selectionModel->clearSelection(); + } } void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h index 4d911edde..73c43abbc 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h @@ -15,7 +15,6 @@ #include #include #include -#include #include class CardGroupDisplayWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp index 5f3a2c2c0..e52242c7b 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp @@ -1,9 +1,6 @@ #include "flat_card_group_display_widget.h" -#include "../card_info_picture_with_text_overlay_widget.h" - #include -#include #include #include #include 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 c08e0ed3c..819bc57d0 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp @@ -1,7 +1,6 @@ #include "card_info_display_widget.h" #include "../../../game/board/card_item.h" -#include "../../../main.h" #include "card_info_picture_widget.h" #include "card_info_text_widget.h" @@ -9,7 +8,6 @@ #include #include #include -#include CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *parent, Qt::WindowFlags flags) : QFrame(parent, flags), aspectRatio((qreal)CARD_HEIGHT / (qreal)CARD_WIDTH) @@ -51,7 +49,7 @@ void CardInfoDisplayWidget::setCard(const ExactCard &card) if (exactCard) connect(exactCard.getCardPtr().data(), &QObject::destroyed, this, &CardInfoDisplayWidget::clear); - text->setCard(exactCard.getCardPtr()); + text->setCard(exactCard); pic->setCard(exactCard); } diff --git a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp index 9fea41076..21bee8f54 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp @@ -10,7 +10,6 @@ #include #include #include -#include CardInfoFrameWidget::CardInfoFrameWidget(QWidget *parent) : QTabWidget(parent), viewTransformationButton(nullptr), cardTextOnly(false) @@ -155,7 +154,7 @@ void CardInfoFrameWidget::setCard(const ExactCard &card) setViewTransformationButtonVisibility(hasTransformation(exactCard.getInfo())); - text->setCard(exactCard.getCardPtr()); + text->setCard(exactCard); pic->setCard(exactCard); } diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp index 9a79bd32f..185bb20d1 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp @@ -5,7 +5,6 @@ #include #include -#include /** * @brief Constructs a CardPictureEnlargedWidget. @@ -34,10 +33,13 @@ CardInfoPictureEnlargedWidget::CardInfoPictureEnlargedWidget(QWidget *parent) : */ void CardInfoPictureEnlargedWidget::loadPixmap(const QSize &size) { + // Handle DPI scaling + qreal dpr = devicePixelRatio(); // Get the actual scaling factor + QSize availableSize = size * dpr; // Convert to physical pixel size if (card) { - CardPictureLoader::getPixmap(enlargedPixmap, card, size); + CardPictureLoader::getPixmap(enlargedPixmap, card, availableSize); } else { - CardPictureLoader::getCardBackPixmap(enlargedPixmap, size); + CardPictureLoader::getCardBackPixmap(enlargedPixmap, availableSize); } pixmapDirty = false; } @@ -78,25 +80,35 @@ void CardInfoPictureEnlargedWidget::paintEvent(QPaintEvent *event) loadPixmap(size()); } - // Scale the size of the pixmap to fit the widget while maintaining the aspect ratio - QSize scaledSize = enlargedPixmap.size().scaled(size().width(), size().height(), Qt::KeepAspectRatio); + qreal dpr = enlargedPixmap.devicePixelRatio(); - // Calculate the position to center the scaled pixmap - QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2}; + QSize logicalPixmapSize(enlargedPixmap.width() / dpr, enlargedPixmap.height() / dpr); - // Define the radius for rounded corners - // Adjust the radius as needed for rounded corners - qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * scaledSize.width() : 0.; + // Scale the pixmap to fit the widget (logical → logical) + QSize scaledLogicalSize = logicalPixmapSize.scaled(size(), Qt::KeepAspectRatio); + + // Convert scaled logical size → physical size for scaled() + QSize scaledPhysicalSize = scaledLogicalSize * dpr; + + // Pixmap scaled in PHYSICAL pixels + QPixmap finalPixmap = enlargedPixmap.scaled(scaledPhysicalSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + finalPixmap.setDevicePixelRatio(dpr); + + // Center inside widget + QPoint topLeft{(width() - scaledLogicalSize.width()) / 2, (height() - scaledLogicalSize.height()) / 2}; + + // Rounded corner radius based on logical width + qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * scaledLogicalSize.width() : 0.0; QStylePainter painter(this); // Fill the background with transparent color to ensure rounded corners are rendered properly painter.fillRect(rect(), Qt::transparent); // Use the transparent background QPainterPath shape; - shape.addRoundedRect(QRect(topLeft, scaledSize), radius, radius); + shape.addRoundedRect(QRect(topLeft, scaledLogicalSize), radius, radius); painter.setClipPath(shape); // Set the clipping path // Draw the pixmap scaled to the calculated size - painter.drawItemPixmap(QRect(topLeft, scaledSize), Qt::AlignCenter, - enlargedPixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + painter.drawPixmap(QRect(topLeft, scaledLogicalSize), finalPixmap); } diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h index ec1de166b..10eb32940 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h @@ -8,7 +8,6 @@ #ifndef CARD_PICTURE_ENLARGED_WIDGET_H #define CARD_PICTURE_ENLARGED_WIDGET_H -#include #include #include 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 b628e6982..c9dbc47ab 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h @@ -10,7 +10,6 @@ #include "card_info_picture_enlarged_widget.h" #include -#include #include #include @@ -64,7 +63,8 @@ protected: { return resizedPixmap; } - void showEnlargedPixmap() const; + void showEnlargedPixmap(); + void destroyEnlargedPixmapWidget(); private: ExactCard exactCard; diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index 4cb61deab..2f0aeccfd 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -3,7 +3,6 @@ #include #include #include -#include /** * @brief Constructs a CardPictureWithTextOverlay widget. diff --git a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp index f11be533a..456e1533a 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -51,8 +50,9 @@ void CardInfoTextWidget::setTexts(const QString &propsText, const QString &textT textLabel->setText(textText); } -void CardInfoTextWidget::setCard(CardInfoPtr card) +void CardInfoTextWidget::setCard(const ExactCard &exactCard) { + auto card = exactCard.getCardPtr(); if (card == nullptr) { setTexts("", ""); return; @@ -62,6 +62,15 @@ void CardInfoTextWidget::setCard(CardInfoPtr card) text += QString("%1%2") .arg(tr("Name:"), card->getName().toHtmlEscaped()); + if (exactCard.getPrinting() != PrintingInfo()) { + QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped(); + QString cardNum = exactCard.getPrinting().getProperty("num").toHtmlEscaped(); + + text += QString("%1%2").arg(tr("Set:"), setShort); + + text += QString("%1%2").arg(tr("Collector Number:"), cardNum); + } + QStringList cardProps = card->getProperties(); for (const QString &key : cardProps) { if (key.contains("-")) diff --git a/cockatrice/src/interface/widgets/cards/card_info_text_widget.h b/cockatrice/src/interface/widgets/cards/card_info_text_widget.h index 8711422b0..95b588882 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.h @@ -7,6 +7,8 @@ #ifndef CARDINFOTEXT_H #define CARDINFOTEXT_H +#include "libcockatrice/card/printing/exact_card.h" + #include #include class QLabel; @@ -32,7 +34,7 @@ public: signals: void linkActivated(const QString &link); public slots: - void setCard(CardInfoPtr card); + void setCard(const ExactCard &card); }; #endif diff --git a/cockatrice/src/interface/widgets/cards/card_size_widget.cpp b/cockatrice/src/interface/widgets/cards/card_size_widget.cpp index 927d9bf5f..14e45743d 100644 --- a/cockatrice/src/interface/widgets/cards/card_size_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_size_widget.cpp @@ -1,6 +1,5 @@ #include "card_size_widget.h" -#include "../../../client/settings/cache_settings.h" #include "../printing_selector/printing_selector.h" #include "../visual_deck_storage/visual_deck_storage_widget.h" 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 5529c35b6..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 @@ -4,7 +4,6 @@ #include "card_group_display_widgets/overlapped_card_group_display_widget.h" #include -#include #include DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, @@ -39,8 +38,10 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, displayCards(); connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &DeckCardZoneDisplayWidget::onCategoryAddition); - connect(selectionModel, &QItemSelectionModel::selectionChanged, this, - &DeckCardZoneDisplayWidget::onSelectionChanged); + if (selectionModel) { + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, + &DeckCardZoneDisplayWidget::onSelectionChanged); + } connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &DeckCardZoneDisplayWidget::onCategoryRemoval); } @@ -81,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, @@ -119,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_card_zone_display_widget.h b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h index ac53cb704..d5603017c 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h @@ -16,7 +16,6 @@ #include #include -#include #include class DeckCardZoneDisplayWidget : public QWidget 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 8f4e1a115..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 @@ -8,7 +8,6 @@ #include #include #include -#include /** * @brief Constructs a CardPictureWithTextOverlay widget. @@ -18,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/cards/deck_preview_card_picture_widget.h b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h index c4339da22..acb67a49a 100644 --- a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h +++ b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h @@ -13,6 +13,7 @@ #include #include #include +#include class DeckPreviewCardPictureWidget final : public CardInfoPictureWithTextOverlayWidget { diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp index 2131700fd..45393bb5d 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp @@ -17,20 +17,19 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckLi container->setLayout(containerLayout); scrollArea->setWidget(container); - manaCurveWidget = new ManaCurveWidget(this, deckListModel); + deckListStatisticsAnalyzer = new DeckListStatisticsAnalyzer(this, deckListModel); + + manaCurveWidget = new ManaCurveWidget(this, deckListStatisticsAnalyzer); containerLayout->addWidget(manaCurveWidget); - manaDevotionWidget = new ManaDevotionWidget(this, deckListModel); + manaDevotionWidget = new ManaDevotionWidget(this, deckListStatisticsAnalyzer); containerLayout->addWidget(manaDevotionWidget); - manaBaseWidget = new ManaBaseWidget(this, deckListModel); + manaBaseWidget = new ManaBaseWidget(this, deckListStatisticsAnalyzer); containerLayout->addWidget(manaBaseWidget); } -void DeckAnalyticsWidget::refreshDisplays(DeckListModel *_deckModel) +void DeckAnalyticsWidget::refreshDisplays() { - deckListModel = _deckModel; - manaCurveWidget->setDeckModel(_deckModel); - manaDevotionWidget->setDeckModel(_deckModel); - manaBaseWidget->setDeckModel(_deckModel); + deckListStatisticsAnalyzer->update(); } diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h index 29dc9c33c..524362aed 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h @@ -7,16 +7,13 @@ #ifndef DECK_ANALYTICS_WIDGET_H #define DECK_ANALYTICS_WIDGET_H -#include "../general/layout_containers/flow_widget.h" #include "mana_base_widget.h" #include "mana_curve_widget.h" #include "mana_devotion_widget.h" #include #include -#include #include -#include #include class DeckAnalyticsWidget : public QWidget @@ -27,10 +24,11 @@ public: explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel); void setDeckList(const DeckList &_deckListModel); std::map analyzeManaCurve(); - void refreshDisplays(DeckListModel *_deckListModel); + void refreshDisplays(); private: DeckListModel *deckListModel; + DeckListStatisticsAnalyzer *deckListStatisticsAnalyzer; QVBoxLayout *mainLayout; QWidget *container; 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 new file mode 100644 index 000000000..3ca18d21c --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp @@ -0,0 +1,114 @@ +#include "deck_list_statistics_analyzer.h" + +#include "deck_list_model.h" +#include "deck_list_statistics_analyzer.h" + +#include +#include +#include + +DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent, + DeckListModel *_model, + DeckListStatisticsAnalyzerConfig cfg) + : QObject(parent), model(_model), config(cfg) +{ + connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::update); +} + +void DeckListStatisticsAnalyzer::update() +{ + manaBaseMap.clear(); + manaCurveMap.clear(); + manaDevotionMap.clear(); + + QList cards = model->getCards(); + + for (const ExactCard &card : cards) { + // ---- Mana curve ---- + if (config.computeManaCurve) { + manaCurveMap[card.getInfo().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(); + } + + // ---- Devotion ---- + if (config.computeDevotion) { + auto devo = countManaSymbols(card.getInfo().getManaCost()); + for (auto &d : devo) + manaDevotionMap[d.first] += d.second; + } + } + + emit statsUpdated(); +} + +QHash DeckListStatisticsAnalyzer::determineManaProduction(const QString &rulesText) +{ + QHash manaCounts = {{"W", 0}, {"U", 0}, {"B", 0}, {"R", 0}, {"G", 0}, {"C", 0}}; + + QString text = rulesText.toLower(); // Normalize case for matching + + // Quick keyword-based checks for any color and colorless mana + if (text.contains("{t}: add one mana of any color") || text.contains("add one mana of any color")) { + for (const auto &color : {QStringLiteral("W"), QStringLiteral("U"), QStringLiteral("B"), QStringLiteral("R"), + QStringLiteral("G")}) { + manaCounts[color]++; + } + } + if (text.contains("{t}: add {c}") || text.contains("add one colorless mana")) { + manaCounts["C"]++; + } + + // Optimized regex for specific mana symbols + static const QRegularExpression specificColorRegex(R"(\{T\}:\s*Add\s*\{([WUBRG])\})"); + QRegularExpressionMatch match = specificColorRegex.match(rulesText); + if (match.hasMatch()) { + manaCounts[match.captured(1)]++; + } + + return manaCounts; +} + +std::unordered_map DeckListStatisticsAnalyzer::countManaSymbols(const QString &manaString) +{ + std::unordered_map manaCounts = {{'W', 0}, {'U', 0}, {'B', 0}, {'R', 0}, {'G', 0}}; + + int len = manaString.length(); + for (int i = 0; i < len; ++i) { + if (manaString[i] == '{') { + ++i; // Move past '{' + if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { + char mana1 = manaString[i].toLatin1(); + ++i; // Move to next character + if (i < len && manaString[i] == '/') { + ++i; // Move past '/' + if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { + char mana2 = manaString[i].toLatin1(); + manaCounts[mana1]++; + manaCounts[mana2]++; + } else { + // Handle cases like "{W/}" where second part is invalid + manaCounts[mana1]++; + } + } else { + manaCounts[mana1]++; + } + } + // Ensure we always skip to the closing '}' + while (i < len && manaString[i] != '}') { + ++i; + } + } + // Check if the character is a standalone mana symbol (not inside {}) + else if (manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { + manaCounts[manaString[i].toLatin1()]++; + } + } + + return manaCounts; +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h new file mode 100644 index 000000000..8fa033971 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h @@ -0,0 +1,60 @@ +#ifndef COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H +#define COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H + +#include "deck_list_model.h" + +#include +#include +#include + +class DeckListModel; + +struct DeckListStatisticsAnalyzerConfig +{ + bool computeManaBase = true; + bool computeManaCurve = true; + bool computeDevotion = true; +}; + +class DeckListStatisticsAnalyzer : public QObject +{ + Q_OBJECT + +public: + explicit DeckListStatisticsAnalyzer(QObject *parent, + DeckListModel *model, + DeckListStatisticsAnalyzerConfig cfg = DeckListStatisticsAnalyzerConfig()); + + void update(); + + [[nodiscard]] const QHash &getManaBase() const + { + return manaBaseMap; + } + [[nodiscard]] const std::unordered_map &getManaCurve() const + { + return manaCurveMap; + } + [[nodiscard]] const std::unordered_map &getDevotion() const + { + return manaDevotionMap; + } + +signals: + void statsUpdated(); + +private: + DeckListModel *model; + DeckListStatisticsAnalyzerConfig config; + + // Internal result containers + QHash manaBaseMap; + std::unordered_map manaCurveMap; + std::unordered_map manaDevotionMap; + + // Internal helper functions + QHash determineManaProduction(const QString &); + std::unordered_map countManaSymbols(const QString &); +}; + +#endif // COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp index 568583af5..2e530db0b 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp @@ -10,8 +10,8 @@ #include #include -ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListModel *_deckListModel) - : QWidget(parent), deckListModel(_deckListModel) +ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer) + : QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer) { layout = new QVBoxLayout(this); setLayout(layout); @@ -24,7 +24,7 @@ ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListModel *_deckListModel) barLayout = new QHBoxLayout(barContainer); layout->addWidget(barContainer); - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaBaseWidget::analyzeManaBase); + connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaBaseWidget::updateDisplay); retranslateUi(); } @@ -34,13 +34,6 @@ void ManaBaseWidget::retranslateUi() bannerWidget->setText(tr("Mana Base")); } -void ManaBaseWidget::setDeckModel(DeckListModel *deckModel) -{ - deckListModel = deckModel; - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaBaseWidget::analyzeManaBase); - analyzeManaBase(); -} - void ManaBaseWidget::updateDisplay() { // Clear the layout first @@ -50,6 +43,8 @@ void ManaBaseWidget::updateDisplay() delete item; } + auto manaBaseMap = deckStatAnalyzer->getManaBase(); + int highestEntry = 0; for (auto entry : manaBaseMap) { if (entry > highestEntry) { @@ -74,56 +69,3 @@ void ManaBaseWidget::updateDisplay() update(); } - -QHash ManaBaseWidget::analyzeManaBase() -{ - manaBaseMap.clear(); - QList cardsInDeck = deckListModel->getDeckList()->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); - if (info) { - auto devotion = determineManaProduction(info->getText()); - mergeManaCounts(manaBaseMap, devotion); - } - } - } - - updateDisplay(); - return manaBaseMap; -} - -QHash ManaBaseWidget::determineManaProduction(const QString &rulesText) -{ - QHash manaCounts = {{"W", 0}, {"U", 0}, {"B", 0}, {"R", 0}, {"G", 0}, {"C", 0}}; - - QString text = rulesText.toLower(); // Normalize case for matching - - // Quick keyword-based checks for any color and colorless mana - if (text.contains("{t}: add one mana of any color") || text.contains("add one mana of any color")) { - for (const auto &color : {QStringLiteral("W"), QStringLiteral("U"), QStringLiteral("B"), QStringLiteral("R"), - QStringLiteral("G")}) { - manaCounts[color]++; - } - } - if (text.contains("{t}: add {c}") || text.contains("add one colorless mana")) { - manaCounts["C"]++; - } - - // Optimized regex for specific mana symbols - static const QRegularExpression specificColorRegex(R"(\{T\}:\s*Add\s*\{([WUBRG])\})"); - QRegularExpressionMatch match = specificColorRegex.match(rulesText); - if (match.hasMatch()) { - manaCounts[match.captured(1)]++; - } - - return manaCounts; -} - -void ManaBaseWidget::mergeManaCounts(QHash &manaCounts1, const QHash &manaCounts2) -{ - for (auto it = manaCounts2.constBegin(); it != manaCounts2.constEnd(); ++it) { - manaCounts1[it.key()] += it.value(); - } -} diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h index e88fe3b85..079449353 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h @@ -8,6 +8,7 @@ #define MANA_BASE_WIDGET_H #include "../general/display/banner_widget.h" +#include "deck_list_statistics_analyzer.h" #include #include @@ -20,21 +21,15 @@ class ManaBaseWidget : public QWidget Q_OBJECT public: - explicit ManaBaseWidget(QWidget *parent, DeckListModel *deckListModel); - QHash analyzeManaBase(); + explicit ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer); void updateDisplay(); - QHash determineManaProduction(const QString &manaString); - void mergeManaCounts(QHash &manaCounts1, const QHash &manaCounts2); - public slots: - void setDeckModel(DeckListModel *deckModel); void retranslateUi(); private: - DeckListModel *deckListModel; + DeckListStatisticsAnalyzer *deckStatAnalyzer; BannerWidget *bannerWidget; - QHash manaBaseMap; QVBoxLayout *layout; QWidget *barContainer; QHBoxLayout *barLayout; diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp index 4515b723b..c094eb590 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp @@ -10,8 +10,8 @@ #include #include -ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListModel *_deckListModel) - : QWidget(parent), deckListModel(_deckListModel) +ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer) + : QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer) { layout = new QVBoxLayout(this); setLayout(layout); @@ -24,7 +24,7 @@ ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListModel *_deckListModel) barLayout = new QHBoxLayout(barContainer); layout->addWidget(barContainer); - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaCurveWidget::analyzeManaCurve); + connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaCurveWidget::updateDisplay); retranslateUi(); } @@ -34,34 +34,6 @@ void ManaCurveWidget::retranslateUi() bannerWidget->setText(tr("Mana Curve")); } -void ManaCurveWidget::setDeckModel(DeckListModel *deckModel) -{ - deckListModel = deckModel; - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaCurveWidget::analyzeManaCurve); - analyzeManaCurve(); -} - -std::unordered_map ManaCurveWidget::analyzeManaCurve() -{ - manaCurveMap.clear(); - - QList cardsInDeck = deckListModel->getDeckList()->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); - if (info) { - int cmc = info->getCmc().toInt(); - manaCurveMap[cmc]++; - } - } - } - - updateDisplay(); - - return manaCurveMap; -} - void ManaCurveWidget::updateDisplay() { // Clear the layout first @@ -73,6 +45,8 @@ void ManaCurveWidget::updateDisplay() } } + auto manaCurveMap = deckStatAnalyzer->getManaCurve(); + int highestEntry = 0; for (const auto &entry : manaCurveMap) { if (entry.second > highestEntry) { diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h index 7362e0c72..fad1fb0f8 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h @@ -8,6 +8,7 @@ #define MANA_CURVE_WIDGET_H #include "../general/display/banner_widget.h" +#include "deck_list_statistics_analyzer.h" #include #include @@ -19,17 +20,14 @@ class ManaCurveWidget : public QWidget Q_OBJECT public: - explicit ManaCurveWidget(QWidget *parent, DeckListModel *deckListModel); + explicit ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer); void updateDisplay(); public slots: - void setDeckModel(DeckListModel *deckModel); - std::unordered_map analyzeManaCurve(); void retranslateUi(); private: - DeckListModel *deckListModel; - std::unordered_map manaCurveMap; + DeckListStatisticsAnalyzer *deckStatAnalyzer; QVBoxLayout *layout; BannerWidget *bannerWidget; QWidget *barContainer; diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp index 3cafdaeab..476bb8077 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp @@ -1,20 +1,17 @@ #include "mana_devotion_widget.h" -#include "../../../main.h" #include "../../deck_loader/deck_loader.h" #include "../general/display/banner_widget.h" #include "../general/display/bar_widget.h" -#include #include #include #include #include -#include #include -ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListModel *_deckListModel) - : QWidget(parent), deckListModel(_deckListModel) +ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer) + : QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer) { layout = new QVBoxLayout(this); setLayout(layout); @@ -26,7 +23,7 @@ ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListModel *_deckList barLayout = new QHBoxLayout(); layout->addLayout(barLayout); - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaDevotionWidget::analyzeManaDevotion); + connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaDevotionWidget::updateDisplay); retranslateUi(); } @@ -36,33 +33,6 @@ void ManaDevotionWidget::retranslateUi() bannerWidget->setText(tr("Mana Devotion")); } -void ManaDevotionWidget::setDeckModel(DeckListModel *deckModel) -{ - deckListModel = deckModel; - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaDevotionWidget::analyzeManaDevotion); - analyzeManaDevotion(); -} - -std::unordered_map ManaDevotionWidget::analyzeManaDevotion() -{ - manaDevotionMap.clear(); - - QList cardsInDeck = deckListModel->getDeckList()->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); - if (info) { - auto devotion = countManaSymbols(info->getManaCost()); - mergeManaCounts(manaDevotionMap, devotion); - } - } - } - - updateDisplay(); - return manaDevotionMap; -} - void ManaDevotionWidget::updateDisplay() { // Clear the layout first @@ -72,6 +42,8 @@ void ManaDevotionWidget::updateDisplay() delete item; } + auto manaDevotionMap = deckStatAnalyzer->getDevotion(); + int highestEntry = 0; for (auto entry : manaDevotionMap) { if (highestEntry < entry.second) { @@ -92,50 +64,3 @@ void ManaDevotionWidget::updateDisplay() update(); // Update the widget display } - -std::unordered_map ManaDevotionWidget::countManaSymbols(const QString &manaString) -{ - std::unordered_map manaCounts = {{'W', 0}, {'U', 0}, {'B', 0}, {'R', 0}, {'G', 0}}; - - int len = manaString.length(); - for (int i = 0; i < len; ++i) { - if (manaString[i] == '{') { - ++i; // Move past '{' - if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { - char mana1 = manaString[i].toLatin1(); - ++i; // Move to next character - if (i < len && manaString[i] == '/') { - ++i; // Move past '/' - if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { - char mana2 = manaString[i].toLatin1(); - manaCounts[mana1]++; - manaCounts[mana2]++; - } else { - // Handle cases like "{W/}" where second part is invalid - manaCounts[mana1]++; - } - } else { - manaCounts[mana1]++; - } - } - // Ensure we always skip to the closing '}' - while (i < len && manaString[i] != '}') { - ++i; - } - } - // Check if the character is a standalone mana symbol (not inside {}) - else if (manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { - manaCounts[manaString[i].toLatin1()]++; - } - } - - return manaCounts; -} - -void ManaDevotionWidget::mergeManaCounts(std::unordered_map &manaCounts1, - const std::unordered_map &manaCounts2) -{ - for (const auto &pair : manaCounts2) { - manaCounts1[pair.first] += pair.second; // Add values for matching keys - } -} diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h index b8ec7950a..ff2e86159 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h @@ -8,6 +8,7 @@ #define MANA_DEVOTION_WIDGET_H #include "../general/display/banner_widget.h" +#include "deck_list_statistics_analyzer.h" #include #include @@ -20,21 +21,15 @@ class ManaDevotionWidget : public QWidget Q_OBJECT public: - explicit ManaDevotionWidget(QWidget *parent, DeckListModel *deckListModel); + explicit ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer); void updateDisplay(); - std::unordered_map countManaSymbols(const QString &manaString); - void mergeManaCounts(std::unordered_map &manaCounts1, const std::unordered_map &manaCounts2); - public slots: - void setDeckModel(DeckListModel *deckModel); - std::unordered_map analyzeManaDevotion(); void retranslateUi(); private: - DeckListModel *deckListModel; + DeckListStatisticsAnalyzer *deckStatAnalyzer; BannerWidget *bannerWidget; - std::unordered_map manaDevotionMap; QVBoxLayout *layout; QHBoxLayout *barLayout; }; 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 b5aa007f0..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 @@ -7,10 +7,8 @@ #include "../../pixel_map_generator.h" #include -#include #include #include -#include #include #include #include @@ -136,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)); } } @@ -174,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_database_display_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h index 0d3ce9f4c..593be7825 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h @@ -32,7 +32,7 @@ public: QTreeView *getDatabaseView() { return databaseView; - }; + } public slots: ExactCard currentCard() const; 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 4db7b8774..543f1a1c0 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 @@ -56,7 +56,7 @@ void DeckEditorDeckDockWidget::createDeckDock() deckModel->setObjectName("deckModel"); connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); - deckLoader = new DeckLoader(this, deckModel->getDeckList()); + deckLoader = new DeckLoader(this); proxy = new DeckListStyleProxy(this); proxy->setSourceModel(deckModel); @@ -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); @@ -124,6 +124,12 @@ void DeckEditorDeckDockWidget::createDeckDock() quickSettingsWidget->addSettingsWidget(showBannerCardCheckBox); quickSettingsWidget->addSettingsWidget(showTagsWidgetCheckBox); + formatLabel = new QLabel(this); + + formatComboBox = new QComboBox(this); + formatComboBox->addItem(tr("Loading Database...")); + formatComboBox->setEnabled(false); // Disable until loaded + commentsLabel = new QLabel(); commentsLabel->setObjectName("commentsLabel"); commentsEdit = new QTextEdit; @@ -150,12 +156,17 @@ void DeckEditorDeckDockWidget::createDeckDock() // Delay the update to avoid race conditions QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox); }); + connect(deckModel, &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand); + connect(deckModel, &DeckListModel::deckReplaced, this, &DeckEditorDeckDockWidget::expandAll); + connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeckEditorDeckDockWidget::setBannerCard); bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags()); deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); + connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, + &DeckEditorDeckDockWidget::setTags); activeGroupCriteriaLabel = new QLabel(this); @@ -167,13 +178,11 @@ void DeckEditorDeckDockWidget::createDeckDock() deckModel->setActiveGroupCriteria(static_cast( activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt())); deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - deckView->expandAll(); - deckView->expandAll(); }); 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); @@ -191,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); @@ -206,13 +215,16 @@ void DeckEditorDeckDockWidget::createDeckDock() upperLayout->addWidget(commentsLabel, 1, 0); upperLayout->addWidget(commentsEdit, 1, 1); - upperLayout->addWidget(bannerCardLabel, 2, 0); - upperLayout->addWidget(bannerCardComboBox, 2, 1); + upperLayout->addWidget(formatLabel, 2, 0); + upperLayout->addWidget(formatComboBox, 2, 1); - upperLayout->addWidget(deckTagsDisplayWidget, 3, 1); + upperLayout->addWidget(bannerCardLabel, 3, 0); + upperLayout->addWidget(bannerCardComboBox, 3, 1); - upperLayout->addWidget(activeGroupCriteriaLabel, 4, 0); - upperLayout->addWidget(activeGroupCriteriaComboBox, 4, 1); + upperLayout->addWidget(deckTagsDisplayWidget, 4, 1); + + upperLayout->addWidget(activeGroupCriteriaLabel, 5, 0); + upperLayout->addWidget(activeGroupCriteriaComboBox, 5, 1); hashLabel1 = new QLabel(); hashLabel1->setObjectName("hashLabel1"); @@ -261,6 +273,45 @@ void DeckEditorDeckDockWidget::createDeckDock() refreshShortcuts(); retranslateUi(); + + connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this, + &DeckEditorDeckDockWidget::initializeFormats); + + if (CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok) { + initializeFormats(); + } +} + +void DeckEditorDeckDockWidget::initializeFormats() +{ + QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys(); + + formatComboBox->clear(); // Remove "Loading Database..." + formatComboBox->setEnabled(true); + + // Populate with formats + formatComboBox->addItem("", ""); + 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())); + } else { + // Ensure no selection is visible initially + formatComboBox->setCurrentIndex(-1); + } + + connect(formatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + if (index >= 0) { + QString formatKey = formatComboBox->itemData(index).toString(); + deckModel->setActiveFormat(formatKey); + } else { + deckModel->setActiveFormat(QString()); // clear format if deselected + } + emit deckModified(); + }); } ExactCard DeckEditorDeckDockWidget::getCurrentCard() @@ -268,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; } @@ -296,7 +347,7 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const void DeckEditorDeckDockWidget::updateName(const QString &name) { emit requestDeckHistorySave( - QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName())); + QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeck().deckList.getName())); deckModel->getDeckList()->setName(name); deckEditor->setModified(name.isEmpty()); emit nameChanged(); @@ -306,7 +357,7 @@ void DeckEditorDeckDockWidget::updateName(const QString &name) void DeckEditorDeckDockWidget::updateComments() { emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)") - .arg(deckLoader->getDeckList()->getComments().size()) + .arg(deckLoader->getDeck().deckList.getComments().size()) .arg(commentsEdit->toPlainText().size())); deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); @@ -335,15 +386,15 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Collect unique (name, providerId) pairs QSet> bannerCardSet; - QList cardsInDeck = deckModel->getDeckList()->getCardNodes(); + QList cardsInDeck = deckModel->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 @@ -383,6 +434,13 @@ void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) 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>(); @@ -416,15 +474,13 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() /** * Sets the currently active deck for this tab - * @param _deck The deck. Takes ownership of the object + * @param _deck The deck. */ -void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck) +void DeckEditorDeckDockWidget::setDeck(const LoadedDeck &_deck) { - deckLoader = _deck; - deckLoader->setParent(this); - deckModel->setDeckList(deckLoader->getDeckList()); + deckLoader->setDeck(_deck); + deckModel->setDeckList(&deckLoader->getDeck().deckList); connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); - connect(deckLoader->getDeckList(), &DeckList::deckHashChanged, deckModel, &DeckListModel::deckHashChanged); emit requestDeckHistoryClear(); historyManagerWidget->setDeckListModel(deckModel); @@ -450,14 +506,17 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() bannerCardComboBox->blockSignals(false); updateHash(); sortDeckModelToDeckView(); - expandAll(); - deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); + deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); } void DeckEditorDeckDockWidget::sortDeckModelToDeckView() { deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); + deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); + formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); + + emit deckChanged(); } DeckLoader *DeckEditorDeckDockWidget::getDeckLoader() @@ -465,9 +524,9 @@ DeckLoader *DeckEditorDeckDockWidget::getDeckLoader() return deckLoader; } -DeckList *DeckEditorDeckDockWidget::getDeckList() +const DeckList &DeckEditorDeckDockWidget::getDeckList() const { - return deckModel->getDeckList(); + return *deckModel->getDeckList(); } /** @@ -485,20 +544,75 @@ void DeckEditorDeckDockWidget::cleanDeck() emit deckModified(); emit deckChanged(); updateBannerCardComboBox(); - deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); + deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); } -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(); + + 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()); } /** @@ -520,16 +634,53 @@ 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; + + emit requestDeckHistorySave(tr("Added (%1): %2 (%3) %4") + .arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), + card.getPrinting().getProperty("num"))); + + QModelIndex newCardIndex = deckModel->addCard(card, zoneName); + + if (!newCardIndex.isValid()) { + return; + } + + deckView->clearSelection(); + deckView->setCurrentIndex(newCardIndex); + + emit deckModified(); +} + +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 = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber); + if (!foundCard.isValid()) { + foundCard = deckModel->findCard(card.getName(), zoneName); + } + + swapCard(foundCard); +} + +void DeckEditorDeckDockWidget::actSwapSelection() { auto selectedRows = getSelectedCardNodes(); @@ -565,22 +716,24 @@ 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 QString cardName = currentIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); + const QString cardProviderID = + currentIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).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 zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + offsetCountAtIndex(currentIndex, false); 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)); + if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) { + deckModel->addCard(card, otherZoneName); + } else { + // Third argument (true) says create the card no matter what, even if not in DB + deckModel->addPreferredPrintingCard(cardName, otherZoneName, true); + } return true; } @@ -602,7 +755,7 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z deckView->clearSelection(); deckView->setCurrentIndex(proxy->mapToSource(idx)); - offsetCountAtIndex(idx, -1); + offsetCountAtIndex(idx, false); } void DeckEditorDeckDockWidget::actDecrementSelection() @@ -616,7 +769,7 @@ void DeckEditorDeckDockWidget::actDecrementSelection() } for (const auto &index : selectedRows) { - offsetCountAtIndex(index, -1); + offsetCountAtIndex(index, false); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -638,7 +791,7 @@ void DeckEditorDeckDockWidget::actRemoveCard() continue; } QModelIndex sourceIndex = proxy->mapToSource(index); - QString cardName = sourceIndex.sibling(sourceIndex.row(), 1).data().toString(); + QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); emit requestDeckHistorySave(QString(tr("Removed \"%1\" (all copies)")).arg(cardName)); @@ -653,7 +806,12 @@ void DeckEditorDeckDockWidget::actRemoveCard() } } -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)) { return; @@ -661,27 +819,20 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of QModelIndex sourceIndex = proxy->mapToSource(idx); - const QModelIndex numberIndex = sourceIndex.sibling(sourceIndex.row(), 0); - const QModelIndex nameIndex = sourceIndex.sibling(sourceIndex.row(), 1); + QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + QString providerId = + sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); - 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()); + const auto reason = QString(tr("%1 %2 × \"%3\" (%4)")) + .arg(isIncrement ? tr("Added") : tr("Removed")) + .arg(1) + .arg(cardName) + .arg(providerId); emit requestDeckHistorySave(reason); - if (new_count <= 0) { - deckModel->removeRow(sourceIndex.row(), sourceIndex.parent()); - } else { - deckModel->setData(numberIndex, new_count, Qt::EditRole); - } + int offset = isIncrement ? 1 : -1; + deckModel->offsetCountAtIndex(sourceIndex, offset); emit deckModified(); } @@ -716,6 +867,8 @@ void DeckEditorDeckDockWidget::retranslateUi() showTagsWidgetCheckBox->setText(tr("Show tags selection menu")); commentsLabel->setText(tr("&Comments:")); activeGroupCriteriaLabel->setText(tr("Group by:")); + formatLabel->setText(tr("Format:")); + hashLabel1->setText(tr("Hash:")); aIncrement->setText(tr("&Increment number")); 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 86f3db9f8..a50434f0b 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 @@ -15,7 +15,6 @@ #include "deck_list_history_manager_widget.h" #include "deck_list_style_proxy.h" -#include #include #include #include @@ -57,19 +56,22 @@ public: public slots: void cleanDeck(); + void selectPrevCard(); + void selectNextCard(); void updateBannerCardComboBox(); - void setDeck(DeckLoader *_deck); + void setDeck(const LoadedDeck &_deck); void syncDisplayWidgetsToModel(); void sortDeckModelToDeckView(); DeckLoader *getDeckLoader(); - DeckList *getDeckList(); - void actIncrement(); - bool swapCard(const QModelIndex &idx); + const DeckList &getDeckList() const; + 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(); signals: void nameChanged(); @@ -100,24 +102,30 @@ private: LineEditUnfocusable *hashLabel; QLabel *activeGroupCriteriaLabel; QComboBox *activeGroupCriteriaComboBox; + QLabel *formatLabel; + QComboBox *formatComboBox; QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; - void recursiveExpand(const QModelIndex &index); - QModelIndexList getSelectedCardNodes() const; + [[nodiscard]] QModelIndexList getSelectedCardNodes() const; + void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement); private slots: void decklistCustomMenu(QPoint point); + bool swapCard(const QModelIndex ¤tIndex); void updateCard(QModelIndex, const QModelIndex ¤t); void updateName(const QString &name); void updateComments(); void setBannerCard(int); + void setTags(const QStringList &tags); void syncDeckListBannerCardWithComboBox(); 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(); }; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp index 4957e24ec..5790131c5 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp @@ -7,7 +7,6 @@ #include #include #include -#include DeckEditorFilterDockWidget::DeckEditorFilterDockWidget(AbstractTabDeckEditor *parent) : QDockWidget(parent), deckEditor(parent) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.h index de135460f..b61fc72b5 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.h @@ -12,7 +12,6 @@ #include "../../key_signals.h" #include -#include class FilterTreeModel; class AbstractTabDeckEditor; 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.h b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h index 23619b91b..0e208ad2b 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 @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp index 9c2265dfd..14c5faae7 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp @@ -23,8 +23,7 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const if (role == Qt::BackgroundRole) { if (isCard) { - const bool legal = - true; // TODO: Not implemented yet. QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool(); + const bool legal = QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool(); int base = 255 - (index.row() % 2) * 30; return legal ? QBrush(QColor(base, base, base)) : QBrush(QColor(255, base / 3, base / 3)); } else { diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp index ed32b356c..0bb0eb1c9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp @@ -4,9 +4,7 @@ #include #include -#include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h b/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h index 7c5ea303a..61eea1d6e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h @@ -22,7 +22,7 @@ public: explicit DialogConvertDeckToCodFormat(QWidget *parent); void retranslateUi(); - bool dontAskAgain() const; + [[nodiscard]] bool dontAskAgain() const; private: QVBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp index 629b23787..7f109f600 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -110,7 +109,7 @@ void DlgCreateGame::sharedCtor() gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0); gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1); gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0); - if (room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0); } else { createGameAsJudgeCheckBox->setChecked(false); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp index 5b120b1e2..349f4ca4c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp @@ -3,6 +3,7 @@ #include "../../../client/settings/cache_settings.h" #include +#include #include DlgDefaultTagsEditor::DlgDefaultTagsEditor(QWidget *parent) : QDialog(parent) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h index b552ae737..27af74105 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h @@ -11,7 +11,6 @@ #include #include #include -#include #include class DlgDefaultTagsEditor : public QDialog diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp index 7330c749b..db5f21701 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include 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_edit_password.h b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h index 38f782228..ee8e45985 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h @@ -7,7 +7,6 @@ #ifndef DLG_EDITPASSWORD_H #define DLG_EDITPASSWORD_H -#include #include #include @@ -20,11 +19,11 @@ class DlgEditPassword : public QDialog Q_OBJECT public: explicit DlgEditPassword(QWidget *parent = nullptr); - QString getOldPassword() const + [[nodiscard]] QString getOldPassword() const { return oldPasswordEdit->text(); } - QString getNewPassword() const + [[nodiscard]] QString getNewPassword() const { return newPasswordEdit->text(); } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp index a47a8221a..fe9cd181c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp @@ -1,7 +1,6 @@ #include "dlg_edit_tokens.h" #include "../interface/widgets/utility/get_text_with_max.h" -#include "../main.h" #include #include @@ -16,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp index c39c87d4a..dcfbc91e9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h index 37b208749..abfdab28a 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h @@ -58,32 +58,32 @@ public: const GamesProxyModel *_gamesProxyModel, QWidget *parent = nullptr); - bool getHideFullGames() const; - bool getHideGamesThatStarted() const; - bool getHidePasswordProtectedGames() const; + [[nodiscard]] bool getHideFullGames() const; + [[nodiscard]] bool getHideGamesThatStarted() const; + [[nodiscard]] bool getHidePasswordProtectedGames() const; void setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden); - bool getHideBuddiesOnlyGames() const; + [[nodiscard]] bool getHideBuddiesOnlyGames() const; void setHideBuddiesOnlyGames(bool _hideBuddiesOnlyGames); - bool getHideOpenDecklistGames() const; + [[nodiscard]] bool getHideOpenDecklistGames() const; void setHideOpenDecklistGames(bool _hideOpenDecklistGames); - bool getHideIgnoredUserGames() const; + [[nodiscard]] bool getHideIgnoredUserGames() const; void setHideIgnoredUserGames(bool _hideIgnoredUserGames); - bool getHideNotBuddyCreatedGames() const; - QString getGameNameFilter() const; + [[nodiscard]] bool getHideNotBuddyCreatedGames() const; + [[nodiscard]] QString getGameNameFilter() const; void setGameNameFilter(const QString &_gameNameFilter); - QStringList getCreatorNameFilters() const; + [[nodiscard]] QStringList getCreatorNameFilters() const; void setCreatorNameFilter(const QString &_creatorNameFilter); - QSet getGameTypeFilter() const; + [[nodiscard]] QSet getGameTypeFilter() const; void setGameTypeFilter(const QSet &_gameTypeFilter); - int getMaxPlayersFilterMin() const; - int getMaxPlayersFilterMax() const; + [[nodiscard]] int getMaxPlayersFilterMin() const; + [[nodiscard]] int getMaxPlayersFilterMax() const; void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); - QTime getMaxGameAge() const; + [[nodiscard]] QTime getMaxGameAge() const; const QMap gameAgeMap; - bool getShowOnlyIfSpectatorsCanWatch() const; - bool getShowSpectatorPasswordProtected() const; - bool getShowOnlyIfSpectatorsCanChat() const; - bool getShowOnlyIfSpectatorsCanSeeHands() const; + [[nodiscard]] bool getShowOnlyIfSpectatorsCanWatch() const; + [[nodiscard]] bool getShowSpectatorPasswordProtected() const; + [[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const; + [[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const; }; #endif diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp index 7ad957e6c..24e9030e0 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp @@ -2,8 +2,6 @@ #include "../../../client/settings/cache_settings.h" -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp index 8f79b543f..c33a41bed 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp @@ -2,8 +2,6 @@ #include "../../../client/settings/cache_settings.h" -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h index da4d9e726..e51dad810 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h @@ -20,15 +20,15 @@ class DlgForgotPasswordRequest : public QDialog Q_OBJECT public: explicit DlgForgotPasswordRequest(QWidget *parent = nullptr); - QString getHost() const + [[nodiscard]] QString getHost() const { return hostEdit->text(); } - int getPort() const + [[nodiscard]] int getPort() const { return portEdit->text().toInt(); } - QString getPlayerName() const + [[nodiscard]] QString getPlayerName() const { return playernameEdit->text(); } 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 f08347879..c9c41722e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp @@ -2,8 +2,6 @@ #include "../../../client/settings/cache_settings.h" -#include -#include #include #include #include @@ -123,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_forgot_password_reset.h b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h index ede22e2aa..3d110c71d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h @@ -20,23 +20,23 @@ class DlgForgotPasswordReset : public QDialog Q_OBJECT public: explicit DlgForgotPasswordReset(QWidget *parent = nullptr); - QString getHost() const + [[nodiscard]] QString getHost() const { return hostEdit->text(); } - int getPort() const + [[nodiscard]] int getPort() const { return portEdit->text().toInt(); } - QString getPlayerName() const + [[nodiscard]] QString getPlayerName() const { return playernameEdit->text(); } - QString getToken() const + [[nodiscard]] QString getToken() const { return tokenEdit->text(); } - QString getPassword() const + [[nodiscard]] QString getPassword() const { return newpasswordEdit->text(); } 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 2a276479f..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 @@ -1,6 +1,7 @@ #include "dlg_load_deck_from_clipboard.h" #include "../../../client/settings/cache_settings.h" +#include "../../deck_loader/card_node_function.h" #include "../../deck_loader/deck_loader.h" #include "dlg_settings.h" @@ -65,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::resolveSetNameAndNumberToProviderID(deckLoader->getDeckList()); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); } else { - DeckLoader::clearSetNamesAndNumbers(deckLoader->getDeckList()); + deckList.forEachCard(CardNodeFunction::ClearPrintingData()); } return true; } @@ -107,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")); @@ -121,8 +122,6 @@ void DlgLoadDeckFromClipboard::actRefresh() void DlgLoadDeckFromClipboard::actOK() { - deckList = new DeckLoader(this); - if (loadIntoDeck(deckList)) { accept(); } else { @@ -133,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(); } @@ -154,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); @@ -164,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 f29984ab0..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 @@ -8,6 +8,7 @@ #include #include #include +#include DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent) { @@ -67,6 +68,7 @@ void DlgLoadDeckFromWebsite::accept() } QNetworkRequest request(QUrl(info.fullUrl)); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); QNetworkReply *reply = nam->get(request); QEventLoop loop; @@ -95,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); - DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList()); - 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_load_remote_deck.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp index 214ccc588..ffe08bec9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp @@ -1,10 +1,8 @@ #include "dlg_load_remote_deck.h" #include "../interface/widgets/server/remote/remote_decklist_tree_widget.h" -#include "../main.h" #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp index 2d3bc08a4..2cda51f2a 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp @@ -3,11 +3,9 @@ #include "../../../client/settings/cache_settings.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/widgets/utility/custom_line_edit.h" -#include "../main.h" #include #include -#include #include #include #include @@ -15,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h index c3a3c2455..d7341caff 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h @@ -11,7 +11,6 @@ #include #include #include -#include class CardDatabase; class LineEditUnfocusable; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp index 3493c1047..a3f232d9b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp @@ -3,13 +3,11 @@ #include "../../../client/settings/cache_settings.h" #include -#include #include #include #include #include #include -#include #include DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) @@ -358,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_register.h b/cockatrice/src/interface/widgets/dialogs/dlg_register.h index a614386fc..bebbfc500 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.h @@ -20,31 +20,31 @@ class DlgRegister : public QDialog Q_OBJECT public: explicit DlgRegister(QWidget *parent = nullptr); - QString getHost() const + [[nodiscard]] QString getHost() const { return hostEdit->text(); } - int getPort() const + [[nodiscard]] int getPort() const { return portEdit->text().toInt(); } - QString getPlayerName() const + [[nodiscard]] QString getPlayerName() const { return playernameEdit->text(); } - QString getPassword() const + [[nodiscard]] QString getPassword() const { return passwordEdit->text(); } - QString getEmail() const + [[nodiscard]] QString getEmail() const { return emailEdit->text(); } - QString getCountry() const + [[nodiscard]] QString getCountry() const { return countryEdit->currentIndex() == 0 ? "" : countryEdit->currentText(); } - QString getRealName() const + [[nodiscard]] QString getRealName() const { return realnameEdit->text(); } 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 121ce4c86..587407065 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 @@ -1,9 +1,9 @@ #include "dlg_select_set_for_cards.h" +#include "../../deck_loader/card_node_function.h" #include "../../deck_loader/deck_loader.h" #include "../interface/widgets/cards/card_info_picture_widget.h" #include "../interface/widgets/general/layout_containers/flow_widget.h" -#include "dlg_select_set_for_cards.h" #include #include @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -142,34 +143,58 @@ 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.")); + } + 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; - } - model->removeRow(find_card.row(), find_card.parent()); - CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(card); - PrintingInfo printing = CardDatabaseManager::query()->getSpecificPrinting(card, modifiedSet, ""); - model->addCard(ExactCard(cardInfo, printing), DECK_ZONE_MAIN); + swapPrinting(model, modifiedSet, card); } } + + if (!modifiedSetsAndCardsMap.isEmpty()) { + emit deckModified(); + } accept(); } void DlgSelectSetForCards::actClear() { - DeckLoader::clearSetNamesAndNumbers(model->getDeckList()); + emit deckAboutToBeModified(tr("Cleared all printing information.")); + model->forEachCard(CardNodeFunction::ClearPrintingData()); + emit deckModified(); accept(); } void DlgSelectSetForCards::actSetAllToPreferred() { - DeckLoader::clearSetNamesAndNumbers(model->getDeckList()); - DeckLoader::setProviderIdToPreferredPrinting(model->getDeckList()); + emit deckAboutToBeModified(tr("Set all printings to preferred.")); + model->forEachCard(CardNodeFunction::ClearPrintingData()); + model->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); + emit deckModified(); accept(); } @@ -205,14 +230,10 @@ QMap DlgSelectSetForCards::getSetsForCards() if (!model) return setCounts; - DeckList *decklist = model->getDeckList(); - if (!decklist) - return setCounts; + QList cardNames = model->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; @@ -248,19 +269,15 @@ void DlgSelectSetForCards::updateCardLists() } } - DeckList *decklist = model->getDeckList(); - if (!decklist) - return; + QList cardNames = model->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 @@ -269,16 +286,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); @@ -337,20 +354,16 @@ QMap DlgSelectSetForCards::getCardsForSets() if (!model) return setCards; - DeckList *decklist = model->getDeckList(); - if (!decklist) - return setCards; + QList cardNames = model->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 335b86d2c..5cdef5a30 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 @@ -37,6 +37,8 @@ public: signals: void widgetOrderChanged(); void orderChanged(); + void deckAboutToBeModified(const QString &reason); + void deckModified(); public slots: void actOK(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index e92593d55..191cc5d0b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -1914,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?"), @@ -1925,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/dialogs/dlg_tip_of_the_day.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp index 0197d3dbf..59330e27e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h index 46598fb02..3dad7c652 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h @@ -10,10 +10,8 @@ #include #include #include -#include #include #include -#include #include inline Q_LOGGING_CATEGORY(DlgTipOfTheDayLog, "dlg_tip_of_the_day"); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp index 19182c55b..0a9244dec 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp @@ -5,12 +5,10 @@ #include "../client/network/update/client/release_channel.h" #include "../interface/window_main.h" -#include #include #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h b/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h index 1cb0cf6fb..d61fddab5 100644 --- a/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h +++ b/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h @@ -14,19 +14,19 @@ class TipOfTheDay { public: explicit TipOfTheDay(QString _title, QString _content, QString _imagePath, QDate _date); - QString getTitle() const + [[nodiscard]] QString getTitle() const { return title; } - QString getContent() const + [[nodiscard]] QString getContent() const { return content; } - QString getImagePath() const + [[nodiscard]] QString getImagePath() const { return imagePath; } - QDate getDate() const + [[nodiscard]] QDate getDate() const { return date; } @@ -51,8 +51,8 @@ public: explicit TipsOfTheDay(QString xmlPath, QObject *parent = nullptr); ~TipsOfTheDay() override; TipOfTheDay getTip(int tipId); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; private: QList *tipList; diff --git a/cockatrice/src/interface/widgets/general/display/background_plate_widget.cpp b/cockatrice/src/interface/widgets/general/display/background_plate_widget.cpp new file mode 100644 index 000000000..aad259900 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/background_plate_widget.cpp @@ -0,0 +1,34 @@ +#include "background_plate_widget.h" + +#include +#include +#include +#include + +BackgroundPlateWidget::BackgroundPlateWidget(QWidget *parent) : QWidget(parent) +{ + setAutoFillBackground(true); // For automatic background filling +} + +void BackgroundPlateWidget::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Set the background color to semi-transparent black with rounded corners + QRect rect = this->rect(); + painter.setPen(Qt::NoPen); // No border + if (focused) { + painter.setBrush(QColor(85, 190, 75, 140)); + } else { + painter.setBrush(QColor(0, 0, 0, 140)); // semi-transparent black + } + painter.drawRoundedRect(rect, 6, 6); // rounded corners +} + +void BackgroundPlateWidget::setFocused(bool _focused) +{ + focused = _focused; + update(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/general/display/background_plate_widget.h b/cockatrice/src/interface/widgets/general/display/background_plate_widget.h new file mode 100644 index 000000000..1fc1bc3ba --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/background_plate_widget.h @@ -0,0 +1,22 @@ +#ifndef COCKATRICE_BACKGROUND_PLATE_WIDGET_H +#define COCKATRICE_BACKGROUND_PLATE_WIDGET_H + +#include + +class BackgroundPlateWidget : public QWidget +{ + Q_OBJECT + +public: + explicit BackgroundPlateWidget(QWidget *parent); + + void setFocused(bool focused); + +private: + bool focused = false; + +protected: + void paintEvent(QPaintEvent *event) override; +}; + +#endif // COCKATRICE_BACKGROUND_PLATE_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/bar_widget.h b/cockatrice/src/interface/widgets/general/display/bar_widget.h index 672f5b7ac..67ad24995 100644 --- a/cockatrice/src/interface/widgets/general/display/bar_widget.h +++ b/cockatrice/src/interface/widgets/general/display/bar_widget.h @@ -8,8 +8,6 @@ #ifndef BAR_WIDGET_H #define BAR_WIDGET_H -#include -#include #include class BarWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/general/display/color_bar.cpp b/cockatrice/src/interface/widgets/general/display/color_bar.cpp new file mode 100644 index 000000000..d1eb7ef4c --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/color_bar.cpp @@ -0,0 +1,163 @@ + +#include "color_bar.h" + +#include +#include +#include +#include + +ColorBar::ColorBar(const QMap &_colors, QWidget *parent) : QWidget(parent), colors(_colors) +{ + setMouseTracking(true); +} + +void ColorBar::setColors(const QMap &_colors) +{ + colors = _colors; + update(); +} + +QSize ColorBar::minimumSizeHint() const +{ + return QSize(200, 22); +} + +void ColorBar::paintEvent(QPaintEvent *) +{ + if (colors.isEmpty()) + return; + + int total = 0; + for (int v : colors.values()) + total += v; + + // Prevent divide-by-zero + if (total == 0) + return; + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing, true); + + const int w = width(); + const int h = height(); + int x = 0; + + // Draw rounded border background + QRectF bounds(0.5, 0.5, w - 1, h - 1); + p.setPen(QPen(Qt::black, 1)); + p.setBrush(Qt::NoBrush); + p.drawRoundedRect(bounds, 6, 6); + + // Clip to inside the border + p.setClipRect(bounds.adjusted(2, 2, -2, -2)); + + // Ensure predictable order + QList sortedKeys = colors.keys(); + std::sort(sortedKeys.begin(), sortedKeys.end()); // Sort alphabetically + + // Draw each color segment in the sorted order + for (const QString &key : sortedKeys) { + int value = colors[key]; + double ratio = double(value) / total; + + if (ratio <= minRatioThreshold) { + continue; + } + + int segmentWidth = int(ratio * w); + + // Ensure the segment width is at least 1 to avoid degenerate rectangles + if (segmentWidth < 1) + segmentWidth = 1; + + QColor base = colorFromName(key); + + // Slight gradient for nicer look + QLinearGradient grad(x, 0, x, h); + grad.setColorAt(0, base.lighter(120)); + grad.setColorAt(1, base.darker(120)); + + p.fillRect(QRect(x, 0, segmentWidth, h), grad); + + x += segmentWidth; + } +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void ColorBar::enterEvent(QEnterEvent *event) +{ + Q_UNUSED(event); + isHovered = true; +} +#else +void ColorBar::enterEvent(QEvent *event) +{ + Q_UNUSED(event); + isHovered = true; +} +#endif + +void ColorBar::leaveEvent(QEvent *) +{ + isHovered = false; +} + +void ColorBar::mouseMoveEvent(QMouseEvent *event) +{ + if (!isHovered || colors.isEmpty()) + return; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + int x = int(event->position().x()); + QPoint gp = event->globalPosition().toPoint(); +#else + int x = event->pos().x(); + QPoint gp = event->globalPos(); +#endif + + QString text = tooltipForPosition(x); + if (!text.isEmpty()) + QToolTip::showText(gp, text, this); +} + +QString ColorBar::tooltipForPosition(int x) const +{ + int total = 0; + for (int v : colors.values()) + total += v; + + if (total == 0) + return {}; + + int pos = 0; + for (auto it = colors.cbegin(); it != colors.cend(); ++it) { + const double ratio = double(it.value()) / total; + const int segmentWidth = int(ratio * width()); + + if (x >= pos && x < pos + segmentWidth) { + const double percent = (100.0 * it.value()) / total; + return QString("%1: %2 cards (%3%)").arg(it.key()).arg(it.value()).arg(QString::number(percent, 'f', 1)); + } + + pos += segmentWidth; + } + + return {}; +} + +QColor ColorBar::colorFromName(const QString &name) const +{ + static QMap map = { + {"R", QColor(220, 30, 30)}, {"G", QColor(40, 170, 40)}, {"U", QColor(40, 90, 200)}, + {"W", QColor(235, 235, 230)}, {"B", QColor(30, 30, 30)}, + }; + + if (map.contains(name)) + return map[name]; + + QColor c(name); + if (!c.isValid()) + c = Qt::gray; + + return c; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/general/display/color_bar.h b/cockatrice/src/interface/widgets/general/display/color_bar.h new file mode 100644 index 000000000..f61ab6942 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/color_bar.h @@ -0,0 +1,128 @@ +#ifndef COCKATRICE_COLOR_BAR_H +#define COCKATRICE_COLOR_BAR_H + +#include +#include +#include +#include + +/** + * @class ColorBar + * @brief A widget for visualizing proportional color distributions as a horizontal bar. + * + * This widget renders a horizontal bar divided into colored segments whose widths reflect + * the relative values associated with each color key in a `QMap`. The class + * is designed as a small, lightweight, and self-contained visualization component suitable + * for representing distributions such as color counts, mana statistics, categorical frequencies, and similar data sets. + * + * Key features: + * - Filled segments for better visual clarity. + * - Deterministic alphabetical ordering of color keys. + * - Optional minimum percentage threshold for filtering out insignificant segments. + * - Mouse-hover tooltips showing each segment’s key, count, and percentage of total. + * + * Default color mappings exist for `"R"`, `"G"`, `"U"`, `"W"`, and `"B"`, using named + * colors, but any string recognized by `QColor` may be used. If an unknown name is provided, + * the segment will fall back to gray. + * + * This component is display-only and does not interpret or mutate domain-level data. + */ +class ColorBar : public QWidget +{ + Q_OBJECT + +public: + /** + * @brief Constructs a ColorBar widget. + * + * @param colors Map of color identifiers to integer counts. + * @param parent Optional parent widget. + */ + explicit ColorBar(const QMap &colors, QWidget *parent = nullptr); + + /** + * @brief Updates the color distribution map. + * @param colors New color → count mapping. + * + * Triggers an immediate repaint. + */ + void setColors(const QMap &colors); + + /** + * @brief Sets a minimum percentage threshold below which segments are not drawn. + * + * @param treshold Percentage from 0 to 100. + * + * Internally converted into a ratio (0.05 = 5%). + */ + void setMinPercentThreshold(double treshold) + { + minRatioThreshold = treshold / 100.0; + } + + /** + * @brief Returns the recommended minimum size. + */ + QSize minimumSizeHint() const override; + +protected: + /** + * @brief Paints the color distribution bar. + * + * Draws: + * - A rounded border + * - Filled segments for each color + * - Only segments above the minimum ratio threshold + */ + void paintEvent(QPaintEvent *event) override; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + /** + * @brief Handles mouse hover entering (Qt6 version). + */ + void enterEvent(QEnterEvent *event) override; +#else + /** + * @brief Handles mouse hover entering (Qt5 version). + */ + void enterEvent(QEvent *event) override; +#endif + + /** + * @brief Handles mouse hover leaving. + */ + void leaveEvent(QEvent *event) override; + + /** + * @brief Handles mouse movement to update contextual tooltips. + */ + void mouseMoveEvent(QMouseEvent *event) override; + +private: + /// Map of color keys to counts used for rendering. + QMap colors; + + /// True if the mouse is currently inside the widget. + bool isHovered = false; + + /// Minimum ratio a segment must exceed to be drawn. + double minRatioThreshold = 0.0; + + /** + * @brief Converts a color name into a display QColor. + * + * Recognized special keys: `"R", "G", "U", "W", "B"`. + * Other strings are treated as QColor names or fall back to gray. + */ + QColor colorFromName(const QString &name) const; + + /** + * @brief Returns tooltip text for a given x-coordinate in the bar. + * + * @param x Horizontal coordinate relative to widget. + * @return Tooltip text or empty string if no segment applies. + */ + QString tooltipForPosition(int x) const; +}; + +#endif // COCKATRICE_COLOR_BAR_H diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp index ed4db1fd3..f485e0040 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp @@ -1,7 +1,6 @@ #include "dynamic_font_size_label.h" #define FONT_PRECISION (0.5) -#include #include DynamicFontSizeLabel::DynamicFontSizeLabel(QWidget *parent, Qt::WindowFlags f) : QLabel(parent, f) diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp index fce4512e8..653b2cd5c 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp @@ -2,7 +2,6 @@ #include "dynamic_font_size_label.h" -#include #include DynamicFontSizePushButton::DynamicFontSizePushButton(QWidget *parent) : QPushButton(parent) diff --git a/cockatrice/src/interface/widgets/general/display/percent_bar_widget.h b/cockatrice/src/interface/widgets/general/display/percent_bar_widget.h index 9ebdb172a..ff7d91363 100644 --- a/cockatrice/src/interface/widgets/general/display/percent_bar_widget.h +++ b/cockatrice/src/interface/widgets/general/display/percent_bar_widget.h @@ -8,7 +8,6 @@ #ifndef PERCENT_BAR_WIDGET_H #define PERCENT_BAR_WIDGET_H -#include #include #include diff --git a/cockatrice/src/interface/widgets/general/display/shadow_background_label.cpp b/cockatrice/src/interface/widgets/general/display/shadow_background_label.cpp index fbca8df8d..7db18c57d 100644 --- a/cockatrice/src/interface/widgets/general/display/shadow_background_label.cpp +++ b/cockatrice/src/interface/widgets/general/display/shadow_background_label.cpp @@ -12,12 +12,18 @@ */ ShadowBackgroundLabel::ShadowBackgroundLabel(QWidget *parent, const QString &text) : QLabel(parent) { - setAttribute(Qt::WA_TranslucentBackground); // Allows transparency. - setText("" + text + ""); ///< Ensures the text is rendered in white. + setAttribute(Qt::WA_TranslucentBackground); // Allows transparency. + setLabelText(text); setAlignment(Qt::AlignCenter); ///< Centers the text within the label. setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); ///< Ensures minimum size constraints. } +void ShadowBackgroundLabel::setLabelText(const QString &text) +{ + setText("" + text + ""); ///< Ensures the text is rendered in white. + update(); +} + /** * @brief Handles resizing of the label. * diff --git a/cockatrice/src/interface/widgets/general/display/shadow_background_label.h b/cockatrice/src/interface/widgets/general/display/shadow_background_label.h index 3483452f5..b2344b7d0 100644 --- a/cockatrice/src/interface/widgets/general/display/shadow_background_label.h +++ b/cockatrice/src/interface/widgets/general/display/shadow_background_label.h @@ -15,6 +15,7 @@ class ShadowBackgroundLabel : public QLabel public: explicit ShadowBackgroundLabel(QWidget *parent, const QString &text); + void setLabelText(const QString &text); protected: void resizeEvent(QResizeEvent *event) override; diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index cbf53ee4f..f35f90256 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -6,6 +6,7 @@ #include "background_sources.h" #include "home_styled_button.h" +#include #include #include #include @@ -19,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", - DeckLoader::CockatriceFormat, false); gradientColors = extractDominantColors(background); @@ -71,13 +68,20 @@ void HomeWidget::initializeBackgroundFromSource() cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); break; case BackgroundSources::DeckFileArt: - backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", - DeckLoader::CockatriceFormat, 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()); @@ -94,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()) { @@ -182,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, @@ -195,6 +199,9 @@ QGroupBox *HomeWidget::createButtons() auto edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors); connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab); boxLayout->addWidget(edhrecButton); + auto archidektButton = new HomeStyledButton(tr("Browse Archidekt"), gradientColors); + connect(archidektButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addArchidektTab); + boxLayout->addWidget(archidektButton); auto replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors); connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); }); boxLayout->addWidget(replaybutton); diff --git a/cockatrice/src/interface/widgets/general/home_widget.h b/cockatrice/src/interface/widgets/general/home_widget.h index 23598cc5a..233fda543 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.h +++ b/cockatrice/src/interface/widgets/general/home_widget.h @@ -12,7 +12,6 @@ #include "home_styled_button.h" #include -#include #include #include @@ -41,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/menus/deck_editor_menu.cpp b/cockatrice/src/interface/widgets/menus/deck_editor_menu.cpp index 775efafbd..23d19abbb 100644 --- a/cockatrice/src/interface/widgets/menus/deck_editor_menu.cpp +++ b/cockatrice/src/interface/widgets/menus/deck_editor_menu.cpp @@ -2,6 +2,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" +#include "../tabs/abstract_tab_deck_editor.h" DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), deckEditor(parent) { diff --git a/cockatrice/src/interface/widgets/menus/deck_editor_menu.h b/cockatrice/src/interface/widgets/menus/deck_editor_menu.h index b35f35864..ac8f3b787 100644 --- a/cockatrice/src/interface/widgets/menus/deck_editor_menu.h +++ b/cockatrice/src/interface/widgets/menus/deck_editor_menu.h @@ -7,8 +7,6 @@ #ifndef DECK_EDITOR_MENU_H #define DECK_EDITOR_MENU_H -#include "../interface/widgets/tabs/abstract_tab_deck_editor.h" - #include class AbstractTabDeckEditor; 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..d8bd88b37 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 @@ -23,8 +23,7 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, QTreeView *deckView, 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); 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 bf8ade36a..6ce10cf2e 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 @@ -10,7 +10,6 @@ #include "../../deck_loader/deck_loader.h" #include "card_amount_widget.h" -#include #include #include @@ -37,11 +36,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 416de18dc..623b797a7 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -61,6 +61,10 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, // Connect slider for dynamic font size adjustment connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); + + if (deckEditor) { + connect(this, &CardAmountWidget::deckModified, deckEditor, &AbstractTabDeckEditor::onDeckHistorySaveRequested); + } } /** @@ -133,6 +137,31 @@ 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) { + for (int i = 0; i < extraCopies; i++) { + model->addCard(rootCard, zone); + } + 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). * @@ -140,27 +169,41 @@ void CardAmountWidget::updateCardCount() */ void CardAmountWidget::addPrinting(const QString &zone) { - // Add the card and expand the list UI - auto newCardIndex = deckModel->addCard(rootCard, zone); - recursiveExpand(newCardIndex); + // Check if we will need to add extra copies due to replacing copies without providerIds + QModelIndex existing = deckModel->findCard(rootCard.getName(), zone); - // Check if a card without a providerId already exists in the deckModel and replace it, if so. - QModelIndex find_card = deckModel->findCard(rootCard.getName(), zone); - QString foundProviderId = deckModel->data(find_card.sibling(find_card.row(), 4), Qt::DisplayRole).toString(); - if (find_card.isValid() && find_card != newCardIndex && foundProviderId == "") { - auto amount = deckModel->data(find_card, Qt::DisplayRole); - for (int i = 0; i < amount.toInt() - 1; i++) { - deckModel->addCard(rootCard, zone); + int extraCopies = 0; + bool replacingProviderless = false; + + if (existing.isValid()) { + 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; } - deckModel->removeRow(find_card.row(), find_card.parent()); } - // Set Index and Focus as if the user had just clicked the new card and modify the deckEditor saveState - newCardIndex = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), - rootCard.getPrinting().getProperty("num")); - deckView->setCurrentIndex(newCardIndex); - deckView->setFocus(Qt::FocusReason::MouseFocusReason); - deckEditor->setModified(true); + QString reason = QString("Added %1 copies of '%2 (%3) %4' to %5 [ProviderID: %6]%7") + .arg(1 + extraCopies) + .arg(rootCard.getName()) + .arg(rootCard.getPrinting().getSet()->getShortName()) + .arg(rootCard.getPrinting().getProperty("num")) + .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") + .arg(rootCard.getPrinting().getUuid()) + .arg(replacingProviderless ? " (replaced providerless printings)" : ""); + + emit deckModified(reason); + + // Add the card and expand the list UI + auto newCardIndex = addAndReplacePrintings(deckModel, existing, rootCard, zone, extraCopies, replacingProviderless); + + if (newCardIndex.isValid()) { + deckView->setCurrentIndex(newCardIndex); + deckView->setFocus(Qt::FocusReason::MouseFocusReason); + deckEditor->setModified(true); + } } /** @@ -195,43 +238,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. * @@ -239,9 +245,19 @@ void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset) */ void CardAmountWidget::decrementCardHelper(const QString &zone) { + QString reason = QString("Removed 1 copy of '%1 (%2) %3' from %4 [ProviderID: %5]") + .arg(rootCard.getName()) + .arg(rootCard.getPrinting().getSet()->getShortName()) + .arg(rootCard.getPrinting().getProperty("num")) + .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") + .arg(rootCard.getPrinting().getUuid()); + + emit deckModified(reason); + QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), rootCard.getPrinting().getProperty("num")); - offsetCountAtIndex(idx, -1); + + deckModel->offsetCountAtIndex(idx, -1); deckEditor->setModified(true); } @@ -253,29 +269,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 = deckModel->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 543964a79..b4704cede 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -9,12 +9,10 @@ #define CARD_AMOUNT_WIDGET_H #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" -#include "../../deck_loader/deck_loader.h" #include "../general/display/dynamic_font_size_push_button.h" #include #include -#include #include #include #include @@ -23,6 +21,10 @@ class CardAmountWidget : public QWidget { Q_OBJECT + +signals: + void deckModified(const QString &modificationReason); + public: explicit CardAmountWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor, @@ -55,9 +57,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 a551ac290..e64d6a009 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -8,9 +8,8 @@ #include "printing_selector_card_selection_widget.h" #include "printing_selector_card_sorting_widget.h" -#include +#include #include -#include /** * @brief Constructs a PrintingSelector widget to display and manage card printings. @@ -116,9 +115,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; @@ -130,7 +128,6 @@ void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_curre } selectedCard = newCard; - currentZone = _currentZone; if (isVisible()) { updateDisplay(); } @@ -139,58 +136,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. */ @@ -219,8 +164,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, deckModel, deckView, + 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 b83768327..e34ce3fe6 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -13,7 +13,6 @@ #include #include -#include #include #include #include @@ -34,23 +33,36 @@ 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; @@ -66,10 +78,8 @@ private: DeckListModel *deckModel; QTreeView *deckView; 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 0da80b522..86d6659a8 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 @@ -1,11 +1,9 @@ #include "printing_selector_card_display_widget.h" -#include "card_amount_widget.h" #include "printing_selector_card_overlay_widget.h" #include "set_name_and_collectors_number_display_widget.h" #include -#include #include #include @@ -19,22 +17,19 @@ * 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 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. */ 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, + DeckListModel *deckModel, + QTreeView *deckView, + QSlider *cardSizeSlider, + const ExactCard &rootCard) + : QWidget(parent) { layout = new QVBoxLayout(this); setLayout(layout); 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 931322117..608c2df5c 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 @@ -11,9 +11,7 @@ #include "printing_selector_card_overlay_widget.h" #include "set_name_and_collectors_number_display_widget.h" -#include #include -#include #include class PrintingSelectorCardDisplayWidget : public QWidget @@ -22,12 +20,11 @@ class PrintingSelectorCardDisplayWidget : public QWidget public: PrintingSelectorCardDisplayWidget(QWidget *parent, - AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, - QSlider *_cardSizeSlider, - const ExactCard &_rootCard, - QString &_currentZone); + AbstractTabDeckEditor *deckEditor, + DeckListModel *deckModel, + QTreeView *deckView, + QSlider *cardSizeSlider, + const ExactCard &rootCard); public slots: void clampSetNameToPicture(); @@ -38,12 +35,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..04ab07a59 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,18 @@ * * @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 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 _rootCard The root card object that contains information about the card. */ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, - QSlider *_cardSizeSlider, + DeckListModel *deckModel, + QTreeView *deckView, + 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); 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 f2351f509..3bd5ce247 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 @@ -8,12 +8,9 @@ #define PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" -#include "../cards/card_info_picture_widget.h" #include "all_zones_card_amount_widget.h" -#include "card_amount_widget.h" #include "set_name_and_collectors_number_display_widget.h" -#include #include class PrintingSelectorCardOverlayWidget : public QWidget @@ -51,9 +48,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 57d5fd895..317ce83b6 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 @@ -1,6 +1,7 @@ #include "printing_selector_card_selection_widget.h" #include "../../../interface/widgets/dialogs/dlg_select_set_for_cards.h" +#include "../tabs/abstract_tab_deck_editor.h" /** * @brief Constructs a PrintingSelectorCardSelectionWidget for navigating through cards in the deck. @@ -41,13 +42,17 @@ 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; } 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/replay/replay_timeline_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp index fb5936cae..25ee58b67 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp @@ -4,7 +4,6 @@ #include #include -#include #include ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent) diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h index e55d408ce..07a0c0b4c 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h @@ -9,7 +9,6 @@ #include "../../../game/player/event_processing_options.h" -#include #include #include @@ -60,10 +59,10 @@ public: explicit ReplayTimelineWidget(QWidget *parent = nullptr); void setTimeline(const QList &_replayTimeline); - QSize sizeHint() const override; - QSize minimumSizeHint() const override; + [[nodiscard]] QSize sizeHint() const override; + [[nodiscard]] QSize minimumSizeHint() const override; void setTimeScaleFactor(qreal _timeScaleFactor); - int getCurrentEvent() const + [[nodiscard]] int getCurrentEvent() const { return currentEvent; } 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/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index f4366d8ab..48a12d5b3 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -10,17 +10,12 @@ #include "games_model.h" #include "user/user_list_manager.h" -#include -#include #include #include #include -#include -#include #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp index c63b52450..daab4d6eb 100644 --- a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h index f7e0c6fb1..642fdd1c4 100644 --- a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h @@ -4,10 +4,7 @@ #include "../tabs/tab_supervisor.h" #include "games_model.h" -#include -#include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp index dfb41fd29..05d363fee 100644 --- a/cockatrice/src/interface/widgets/server/games_model.cpp +++ b/cockatrice/src/interface/widgets/server/games_model.cpp @@ -7,10 +7,7 @@ #include "user/user_list_widget.h" #include -#include #include -#include -#include #include #include @@ -424,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/server/games_model.h b/cockatrice/src/interface/widgets/server/games_model.h index c704befcc..56c806fb6 100644 --- a/cockatrice/src/interface/widgets/server/games_model.h +++ b/cockatrice/src/interface/widgets/server/games_model.h @@ -3,11 +3,9 @@ #include "game_type_map.h" -#include #include #include #include -#include #include #include @@ -42,19 +40,20 @@ public: */ GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override { return parent.isValid() ? 0 : gameList.size(); } - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override + [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return NUM_COLS; } - QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; /** * @brief Formats the game creation time into a human-readable string. @@ -148,71 +147,71 @@ public: explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr); // Getters for filter parameters - bool getHideBuddiesOnlyGames() const + [[nodiscard]] bool getHideBuddiesOnlyGames() const { return hideBuddiesOnlyGames; } - bool getHideIgnoredUserGames() const + [[nodiscard]] bool getHideIgnoredUserGames() const { return hideIgnoredUserGames; } - bool getHideFullGames() const + [[nodiscard]] bool getHideFullGames() const { return hideFullGames; } - bool getHideGamesThatStarted() const + [[nodiscard]] bool getHideGamesThatStarted() const { return hideGamesThatStarted; } - bool getHidePasswordProtectedGames() const + [[nodiscard]] bool getHidePasswordProtectedGames() const { return hidePasswordProtectedGames; } - bool getHideNotBuddyCreatedGames() const + [[nodiscard]] bool getHideNotBuddyCreatedGames() const { return hideNotBuddyCreatedGames; } - bool getHideOpenDecklistGames() const + [[nodiscard]] bool getHideOpenDecklistGames() const { return hideOpenDecklistGames; } - QString getGameNameFilter() const + [[nodiscard]] QString getGameNameFilter() const { return gameNameFilter; } - QStringList getCreatorNameFilters() const + [[nodiscard]] QStringList getCreatorNameFilters() const { return creatorNameFilters; } - QSet getGameTypeFilter() const + [[nodiscard]] QSet getGameTypeFilter() const { return gameTypeFilter; } - int getMaxPlayersFilterMin() const + [[nodiscard]] int getMaxPlayersFilterMin() const { return maxPlayersFilterMin; } - int getMaxPlayersFilterMax() const + [[nodiscard]] int getMaxPlayersFilterMax() const { return maxPlayersFilterMax; } - const QTime &getMaxGameAge() const + [[nodiscard]] const QTime &getMaxGameAge() const { return maxGameAge; } - bool getShowOnlyIfSpectatorsCanWatch() const + [[nodiscard]] bool getShowOnlyIfSpectatorsCanWatch() const { return showOnlyIfSpectatorsCanWatch; } - bool getShowSpectatorPasswordProtected() const + [[nodiscard]] bool getShowSpectatorPasswordProtected() const { return showSpectatorPasswordProtected; } - bool getShowOnlyIfSpectatorsCanChat() const + [[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const { return showOnlyIfSpectatorsCanChat; } - bool getShowOnlyIfSpectatorsCanSeeHands() const + [[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const { return showOnlyIfSpectatorsCanSeeHands; } @@ -328,7 +327,7 @@ public: /** * @brief Returns the number of games filtered out by the current filter. */ - int getNumFilteredGames() const; + [[nodiscard]] int getNumFilteredGames() const; /** * @brief Resets all filter parameters to default values. @@ -338,7 +337,7 @@ public: /** * @brief Returns true if all filter parameters are set to their defaults. */ - bool areFilterParametersSetToDefaults() const; + [[nodiscard]] bool areFilterParametersSetToDefaults() const; /** * @brief Loads filter parameters from persistent settings. @@ -358,8 +357,8 @@ public: void refresh(); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - bool filterAcceptsRow(int sourceRow) const; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow) const; }; #endif diff --git a/cockatrice/src/interface/widgets/server/handle_public_servers.cpp b/cockatrice/src/interface/widgets/server/handle_public_servers.cpp index 6a8754a79..f37c957a4 100644 --- a/cockatrice/src/interface/widgets/server/handle_public_servers.cpp +++ b/cockatrice/src/interface/widgets/server/handle_public_servers.cpp @@ -3,8 +3,6 @@ #include "../../../client/settings/cache_settings.h" #include -#include -#include #include #include diff --git a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h index 0153f0414..8254eb57b 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h +++ b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h @@ -8,7 +8,6 @@ #ifndef REMOTEDECKLIST_TREEWIDGET_H #define REMOTEDECKLIST_TREEWIDGET_H -#include #include #include @@ -34,11 +33,11 @@ public: { } virtual ~Node() = default; - DirectoryNode *getParent() const + [[nodiscard]] DirectoryNode *getParent() const { return parent; } - QString getName() const + [[nodiscard]] QString getName() const { return name; } @@ -49,9 +48,9 @@ public: explicit DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = nullptr); ~DirectoryNode() override; void clearTree(); - QString getPath() const; + [[nodiscard]] QString getPath() const; DirectoryNode *getNodeByPath(QStringList path); - FileNode *getNodeById(int id) const; + [[nodiscard]] FileNode *getNodeById(int id) const; }; class FileNode : public Node { @@ -64,17 +63,17 @@ public: : Node(_name, _parent), id(_id), uploadTime(_uploadTime) { } - int getId() const + [[nodiscard]] int getId() const { return id; } - QDateTime getUploadTime() const + [[nodiscard]] QDateTime getUploadTime() const { return uploadTime; } }; - template T getNode(const QModelIndex &index) const + template [[nodiscard]] T getNode(const QModelIndex &index) const { if (!index.isValid()) return dynamic_cast(root); @@ -96,15 +95,16 @@ private slots: public: explicit RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); ~RemoteDeckList_TreeModel() override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QModelIndex parent(const QModelIndex &index) const override; + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; - DirectoryNode *getRoot() const + [[nodiscard]] DirectoryNode *getRoot() const { return root; } @@ -124,11 +124,11 @@ private: public: explicit RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); - RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const; - RemoteDeckList_TreeModel::Node *getCurrentItem() const; - QList getCurrentSelection() const; - RemoteDeckList_TreeModel::DirectoryNode *getNodeByPath(const QString &path) const; - RemoteDeckList_TreeModel::FileNode *getNodeById(int id) const; + [[nodiscard]] RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const; + [[nodiscard]] RemoteDeckList_TreeModel::Node *getCurrentItem() const; + [[nodiscard]] QList getCurrentSelection() const; + [[nodiscard]] RemoteDeckList_TreeModel::DirectoryNode *getNodeByPath(const QString &path) const; + [[nodiscard]] RemoteDeckList_TreeModel::FileNode *getNodeById(int id) const; void addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, RemoteDeckList_TreeModel::DirectoryNode *parent); void addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, RemoteDeckList_TreeModel::DirectoryNode *parent); diff --git a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h index d5d70af53..6d81aefca 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h +++ b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h @@ -9,7 +9,6 @@ #ifndef REMOTEREPLAYLIST_TREEWIDGET_H #define REMOTEREPLAYLIST_TREEWIDGET_H -#include #include #include #include @@ -35,7 +34,7 @@ private: { } virtual ~Node() = default; - QString getName() const + [[nodiscard]] QString getName() const { return name; } @@ -91,21 +90,22 @@ private slots: public: explicit RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); ~RemoteReplayList_TreeModel() override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return numberOfColumns; } - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QModelIndex parent(const QModelIndex &index) const override; + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; void clearTree(); void refreshTree(); - ServerInfo_Replay const *getReplay(const QModelIndex &index) const; - ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &index) const; - ServerInfo_ReplayMatch const *getEnclosingReplayMatch(const QModelIndex &index) const; + [[nodiscard]] ServerInfo_Replay const *getReplay(const QModelIndex &index) const; + [[nodiscard]] ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &index) const; + [[nodiscard]] ServerInfo_ReplayMatch const *getEnclosingReplayMatch(const QModelIndex &index) const; void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo); void updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo); void removeMatchInfo(int gameId); @@ -119,10 +119,10 @@ private: public: explicit RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); - ServerInfo_Replay const *getReplay(const QModelIndex &ind) const; - ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &ind) const; - QList getSelectedReplays() const; - QSet getSelectedReplayMatches() const; + [[nodiscard]] ServerInfo_Replay const *getReplay(const QModelIndex &ind) const; + [[nodiscard]] ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &ind) const; + [[nodiscard]] QList getSelectedReplays() const; + [[nodiscard]] QSet getSelectedReplayMatches() const; void clearTree() { treeModel->clearTree(); diff --git a/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp b/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp index cb7d2eca9..7083ac899 100644 --- a/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp @@ -12,8 +12,6 @@ #include #include -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/server/user/user_info_connection.h b/cockatrice/src/interface/widgets/server/user/user_info_connection.h index 90be05570..cfba22346 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_connection.h +++ b/cockatrice/src/interface/widgets/server/user/user_info_connection.h @@ -30,31 +30,31 @@ private: public: UserConnection_Information(); UserConnection_Information(QString, QString, QString, QString, QString, bool, QString); - QString getSaveName() const + [[nodiscard]] QString getSaveName() const { return saveName; } - QString getServer() const + [[nodiscard]] QString getServer() const { return server; } - QString getPort() const + [[nodiscard]] QString getPort() const { return port; } - QString getUsername() const + [[nodiscard]] QString getUsername() const { return username; } - QString getPassword() const + [[nodiscard]] QString getPassword() const { return password; } - bool getSavePassword() const + [[nodiscard]] bool getSavePassword() const { return savePassword; } - QString getSite() const + [[nodiscard]] QString getSite() const { return site; } diff --git a/cockatrice/src/interface/widgets/server/user/user_list_manager.cpp b/cockatrice/src/interface/widgets/server/user/user_list_manager.cpp index 9b930b7f6..4bc2c84d6 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_manager.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_manager.cpp @@ -167,4 +167,4 @@ const ServerInfo_User *UserListManager::getOnlineUser(const QString &userName) c } return nullptr; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/server/user/user_list_manager.h b/cockatrice/src/interface/widgets/server/user/user_list_manager.h index 56f50c0be..f378bd08d 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_manager.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_manager.h @@ -60,11 +60,11 @@ public: return ignoredUsers; } - bool isOwnUserRegistered() const override; - QString getOwnUsername() const override; - bool isUserBuddy(const QString &userName) const override; - bool isUserIgnored(const QString &userName) const override; - const ServerInfo_User *getOnlineUser(const QString &userName) const override; + [[nodiscard]] bool isOwnUserRegistered() const override; + [[nodiscard]] QString getOwnUsername() const override; + [[nodiscard]] bool isUserBuddy(const QString &userName) const override; + [[nodiscard]] bool isUserIgnored(const QString &userName) const override; + [[nodiscard]] const ServerInfo_User *getOnlineUser(const QString &userName) const override; public slots: void handleConnect(); diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index b6d65c5fa..852743479 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -5,7 +5,6 @@ #include "../../interface/widgets/tabs/tab_supervisor.h" #include "../game_selector.h" #include "user_context_menu.h" -#include "user_list_manager.h" #include #include @@ -14,21 +13,15 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include -#include #include -#include -#include -#include #include BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(parent) diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.h b/cockatrice/src/interface/widgets/server/user/user_list_widget.h index 897c01b83..036837673 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.h @@ -45,13 +45,13 @@ private slots: public: explicit BanDialog(const ServerInfo_User &info, QWidget *parent = nullptr); - QString getBanName() const; - QString getBanIP() const; - QString getBanId() const; - int getMinutes() const; - QString getReason() const; - QString getVisibleReason() const; - int getDeleteMessages() const; + [[nodiscard]] QString getBanName() const; + [[nodiscard]] QString getBanIP() const; + [[nodiscard]] QString getBanId() const; + [[nodiscard]] int getMinutes() const; + [[nodiscard]] QString getReason() const; + [[nodiscard]] QString getVisibleReason() const; + [[nodiscard]] int getDeleteMessages() const; }; class WarningDialog : public QDialog @@ -68,10 +68,10 @@ private slots: public: WarningDialog(const QString userName, const QString clientID, QWidget *parent = nullptr); - QString getName() const; - QString getWarnID() const; - QString getReason() const; - int getDeleteMessages() const; + [[nodiscard]] QString getName() const; + [[nodiscard]] QString getWarnID() const; + [[nodiscard]] QString getReason() const; + [[nodiscard]] int getDeleteMessages() const; void addWarningOption(const QString warning); }; @@ -85,11 +85,11 @@ private: public: explicit AdminNotesDialog(const QString &_userName, const QString &_notes, QWidget *_parent = nullptr); - QString getName() const + [[nodiscard]] QString getName() const { return userName; } - QString getNotes() const; + [[nodiscard]] QString getNotes() const; }; class UserListItemDelegate : public QStyledItemDelegate @@ -159,7 +159,7 @@ public: void processUserInfo(const ServerInfo_User &user, bool online); bool deleteUser(const QString &userName); void setUserOnline(const QString &userName, bool online); - const QMap &getUsers() const + [[nodiscard]] const QMap &getUsers() const { return users; } 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 a3d3fe2cf..cb2002199 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -14,7 +14,6 @@ #include "../client/network/interfaces/tapped_out_interface.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/pixel_map_generator.h" -#include "../interface/widgets/cards/card_info_frame_widget.h" #include "../interface/widgets/dialogs/dlg_load_deck.h" #include "../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../interface/widgets/dialogs/dlg_load_deck_from_website.h" @@ -22,15 +21,11 @@ #include #include -#include #include -#include #include -#include #include #include #include -#include #include #include #include @@ -38,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -107,7 +101,7 @@ 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. */ @@ -130,7 +124,7 @@ void AbstractTabDeckEditor::onDeckModified() */ void AbstractTabDeckEditor::onDeckHistorySaveRequested(const QString &modificationReason) { - historyManager->save(deckDockWidget->getDeckList()->createMemento(modificationReason)); + historyManager->save(deckDockWidget->getDeckList().createMemento(modificationReason)); } /** @@ -146,22 +140,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->deckView->clearSelection(); - deckDockWidget->deckView->setCurrentIndex(newCardIndex); - setModified(true); + deckDockWidget->actAddCard(card, zoneName); databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length()); } @@ -198,45 +179,27 @@ 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); -} - /** * @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->getLastFileName().isEmpty()) { - SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastFileName()); + 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())); + CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList().getCardRefList())); setModified(false); aDeckDockVisible->setChecked(true); @@ -250,7 +213,7 @@ DeckLoader *AbstractTabDeckEditor::getDeckLoader() const } /** @brief Returns the currently loaded deck list. */ -DeckList *AbstractTabDeckEditor::getDeckList() const +const DeckList &AbstractTabDeckEditor::getDeckList() const { return deckDockWidget->getDeckList(); } @@ -270,8 +233,8 @@ void AbstractTabDeckEditor::setModified(bool _modified) */ bool AbstractTabDeckEditor::isBlankNewDeck() const { - DeckLoader *deck = deckDockWidget->getDeckLoader(); - return !modified && deck->getDeckList()->isBlankDeck() && deck->hasNotBeenLoaded(); + const LoadedDeck &loadedDeck = deckDockWidget->getDeckLoader()->getDeck(); + return !modified && loadedDeck.isEmpty(); } /** @brief Creates a new deck. Handles opening in new tab if needed. */ @@ -282,7 +245,7 @@ void AbstractTabDeckEditor::actNewDeck() return; if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(nullptr); + emit openDeckEditor(LoadedDeck()); return; } @@ -385,19 +348,17 @@ void AbstractTabDeckEditor::actOpenRecent(const QString &fileName) */ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation) { - DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); + 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); @@ -410,16 +371,16 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo */ bool AbstractTabDeckEditor::actSaveDeck() { - DeckLoader *const deck = getDeckLoader(); - if (deck->getLastRemoteDeckId() != -1) { - QString deckString = deck->getDeckList()->writeToString_Native(); + const LoadedDeck &loadedDeck = getDeckLoader()->getDeck(); + 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->getLastRemoteDeckId())); + cmd.set_deck_id(static_cast(loadedDeck.lastLoadInfo.remoteDeckId)); cmd.set_deck_list(deckString.toStdString()); PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); @@ -427,9 +388,11 @@ bool AbstractTabDeckEditor::actSaveDeck() tabSupervisor->getClient()->sendCommand(pend); return true; - } else if (deck->getLastFileName().isEmpty()) + } + if (loadedDeck.lastLoadInfo.fileName.isEmpty()) return actSaveDeckAs(); - else if (deck->saveToFile(deck->getLastFileName(), deck->getLastFileFormat())) { + + if (getDeckLoader()->saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) { setModified(false); return true; } @@ -451,13 +414,13 @@ bool AbstractTabDeckEditor::actSaveDeckAs() dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("cod"); dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); - dialog.selectFile(getDeckList()->getName().trimmed()); + dialog.selectFile(getDeckList().getName().trimmed()); if (!dialog.exec()) return false; QString fileName = dialog.selectedFiles().at(0); - DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); + DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); if (!getDeckLoader()->saveToFile(fileName, fmt)) { QMessageBox::critical( @@ -498,9 +461,9 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() return; if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(dlg.getDeckList()); + emit openDeckEditor({.deckList = dlg.getDeckList()}); } else { - setDeck(dlg.getDeckList()); + setDeck({.deckList = dlg.getDeckList()}); setModified(true); } @@ -513,11 +476,11 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() */ void AbstractTabDeckEditor::editDeckInClipboard(bool annotated) { - DlgEditDeckInClipboard dlg(getDeckLoader(), annotated, this); + DlgEditDeckInClipboard dlg(getDeckLoader()->getDeck().deckList, annotated, this); if (!dlg.exec()) return; - setDeck(dlg.getDeckList()); + setDeck({dlg.getDeckList(), getDeckLoader()->getDeck().lastLoadInfo}); setModified(true); deckMenu->setSaveStatus(true); } @@ -581,9 +544,9 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() return; if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(dlg.getDeck()); + emit openDeckEditor({.deckList = dlg.getDeck()}); } else { - setDeck(dlg.getDeck()); + setDeck({.deckList = dlg.getDeck()}); setModified(true); } @@ -596,26 +559,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(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. */ 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..bfbda778c 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -77,7 +77,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,15 +113,15 @@ 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); + void openDeck(const LoadedDeck &deck); /** @brief Returns the currently active deck loader. */ DeckLoader *getDeckLoader() const; /** @brief Returns the currently active deck list. */ - DeckList *getDeckList() const; + const DeckList &getDeckList() const; /** @brief Sets the modified state of the tab. * @param _windowModified Whether the tab is modified. @@ -198,7 +197,7 @@ 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); @@ -286,7 +285,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); @@ -320,10 +319,7 @@ protected: 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); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp new file mode 100644 index 000000000..40882db04 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp @@ -0,0 +1,29 @@ +#include "archidekt_deck_listing_api_response.h" + +#include +#include + +void ArchidektDeckListingApiResponse::fromJson(const QJsonObject &json) +{ + count = json.value("count").toInt(); + next = QUrl(json.value("next").toString()); + + QJsonArray containerJson = json.value("results").toArray(); + + for (const QJsonValue &deckListingValue : containerJson) { + ArchidektApiResponseDeckListingContainer listingResult; + listingResult.fromJson(deckListingValue.toObject()); + results.append(listingResult); + } +} + +void ArchidektDeckListingApiResponse::debugPrint() const +{ + qDebug() << "Count:" << count; + qDebug() << "Next:" << next; + + qDebug() << "Results:"; + for (const auto &deckListing : results) { + deckListing.debugPrint(); + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h new file mode 100644 index 000000000..723c65ce2 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h @@ -0,0 +1,22 @@ +#ifndef COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_H +#define COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_H + +#include "deck_listings/archidekt_api_response_deck_listing_container.h" + +#include +#include +#include + +class ArchidektDeckListingApiResponse +{ + +public: + int count; + QUrl next; + QVector results; + + void fromJson(const QJsonObject &json); + void debugPrint() const; +}; + +#endif // COCKATRICE_DECK_LISTING_API_RESPONSE_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 new file mode 100644 index 000000000..5b879f8ae --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp @@ -0,0 +1,61 @@ +#include "archidekt_api_response_card.h" + +void ArchidektApiResponseCard::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + artist = json.value("artist").toString(); + tcgProductId = json.value("tcgProductId").toInt(); + ckFoilId = json.value("ckFoilId").toInt(); + ckNormalId = json.value("ckNormalId").toInt(); + cmEd = json.value("cmEd").toString(); + scgSku = json.value("scgSku").toString(); + scgFoilSku = json.value("scgFoilSku").toString(); + collectorNumber = json.value("collectorNumber").toString(); + multiverseId = json.value("multiverseId").toInt(); + mtgoFoilId = json.value("mtgoFoilId").toInt(); + mtgoNormalId = json.value("mtgoNormalId").toInt(); + uid = json.value("uid").toString(); + displayName = json.value("displayName").toString(); + releasedAt = json.value("releasedAt").toString(); + + edition.fromJson(json.value("edition").toObject()); + + flavor = json.value("flavor").toString(); + //! \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 + //! \todo globalCategories = {""}; +} + +void ArchidektApiResponseCard::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "id:" << artist; + qDebug() << "artist:" << tcgProductId; + qDebug() << "tcgProductId:" << ckFoilId; + qDebug() << "ckFoilId:" << ckNormalId; + qDebug() << "ckNormalId:" << cmEd; + qDebug() << "cmEd:" << scgSku; + qDebug() << "scgSku:" << scgFoilSku; + qDebug() << "scgFoilSku:" << collectorNumber; + qDebug() << "collectorNumber:" << multiverseId; + qDebug() << "multiverseId:" << mtgoFoilId; + qDebug() << "mtgoFoilId:" << mtgoNormalId; + qDebug() << "mtgoNormalId:" << uid; + qDebug() << "uid:" << displayName; + qDebug() << "displayName:" << releasedAt; + qDebug() << "releasedAt:" << flavor; + qDebug() << "flavor:" << games; + qDebug() << "games:" << options; + qDebug() << "options:" << scryfallImageHash; + qDebug() << "scryfallImageHash:" << owned; + qDebug() << "owned:" << pinnedStatus; + qDebug() << "pinnedStatus:" << rarity; + qDebug() << "rarity:" << globalCategories; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h new file mode 100644 index 000000000..265498228 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h @@ -0,0 +1,68 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_H + +#include "archidekt_api_response_edition.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseCard +{ +public: + // Constructor + ArchidektApiResponseCard() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + QJsonObject getOracleCard() const + { + return oracleCard; + }; + + QString getCollectorNumber() const + { + return collectorNumber; + } + + ArchidektApiResponseEdition getEdition() const + { + return edition; + } + +private: + int id; + QString artist; + int tcgProductId; + int ckFoilId; + int ckNormalId; + QString cmEd; + QString scgSku; + QString scgFoilSku; + QString collectorNumber; + int multiverseId; + int mtgoFoilId; + int mtgoNormalId; + QString uid; + QString displayName; + QString releasedAt; + ArchidektApiResponseEdition edition; + QString flavor; + QStringList games; + QStringList options; + QString scryfallImageHash; + QJsonObject oracleCard; + int owned; + int pinnedStatus; + // ArchidektApiResponsePrices prices; + QString rarity; + QStringList globalCategories; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp new file mode 100644 index 000000000..7a424de8b --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp @@ -0,0 +1,43 @@ +#include "archidekt_api_response_card_entry.h" + +void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + + auto categoriesJson = json.value("categories").toArray(); + + for (auto category : categoriesJson) { + categories.append(category.toString()); + } + + companion = json.value("companion").toBool(); + flippedDefault = json.value("flippedDefault").toBool(); + label = json.value("label").toString(); + modifier = json.value("modifier").toString(); + quantity = json.value("quantity").toInt(); + customCmc = json.value("customCmc").toInt(); + // removedCategories = {""}; + createdAt = json.value("createdAt").toString(); + updatedAt = json.value("updatedAt").toString(); + deletedAt = json.value("deletedAt").toString(); + notes = json.value("notes").toString(); + card.fromJson(json.value("card").toObject()); +} + +void ArchidektApiResponseCardEntry::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "Categories:" << categories; + qDebug() << "Companion:" << companion; + qDebug() << "FlippedDefault:" << flippedDefault; + qDebug() << "Label:" << label; + qDebug() << "Modifier:" << modifier; + qDebug() << "Quantity:" << quantity; + qDebug() << "CustomCmc:" << customCmc; + qDebug() << "RemovedCategories:" << removedCategories; + qDebug() << "CreatedAt:" << createdAt; + qDebug() << "UpdatedAt:" << updatedAt; + qDebug() << "DeletedAt:" << deletedAt; + qDebug() << "Notes:" << notes; + card.debugPrint(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h new file mode 100644 index 000000000..f7f86e9ed --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h @@ -0,0 +1,56 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_ENTRY_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_ENTRY_H + +#include "archidekt_api_response_card.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseCardEntry +{ +public: + // Constructor + ArchidektApiResponseCardEntry() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + ArchidektApiResponseCard getCard() const + { + return card; + }; + + QStringList getCategories() const + { + return categories; + } + + int getQuantity() const + { + return quantity; + } + +private: + int id; + QStringList categories; + bool companion; + bool flippedDefault; + QString label; + QString modifier; + int quantity; + int customCmc; + QStringList removedCategories; + QString createdAt; + QString updatedAt; + QString deletedAt; + QString notes; + ArchidektApiResponseCard card; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_ENTRY_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp new file mode 100644 index 000000000..122617a22 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp @@ -0,0 +1,19 @@ +#include "archidekt_api_response_edition.h" + +void ArchidektApiResponseEdition::fromJson(const QJsonObject &json) +{ + editionCode = json.value("editioncode").toString(); + editionName = json.value("editionname").toString(); + editionDate = json.value("editiondate").toString(); + editionType = json.value("editiontype").toString(); + mtgoCode = json.value("mtgocode").toString(); +} + +void ArchidektApiResponseEdition::debugPrint() const +{ + qDebug() << "Edition Code: " << editionCode; + qDebug() << "Edition Name: " << editionName; + qDebug() << "Edition Date: " << editionDate; + qDebug() << "Edition Type: " << editionType; + qDebug() << "Mtgo Code: " << mtgoCode; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h new file mode 100644 index 000000000..b898cd816 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h @@ -0,0 +1,51 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H + +#include +#include +#include +#include +#include + +class ArchidektApiResponseEdition +{ +public: + // Constructor + ArchidektApiResponseEdition() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + [[nodiscard]] QString getEditionCode() const + { + return editionCode; + } + [[nodiscard]] QString getEditionName() const + { + return editionName; + } + [[nodiscard]] QString getEditionDate() const + { + return editionDate; + } + [[nodiscard]] QString getEditionType() const + { + return editionType; + } + [[nodiscard]] QString getMtgoCode() const + { + return mtgoCode; + } + +private: + QString editionCode; + QString editionName; + QString editionDate; + QString editionType; + QString mtgoCode; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp new file mode 100644 index 000000000..98d8c6c8c --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp @@ -0,0 +1,79 @@ +#include "archidekt_api_response_deck.h" + +#include "../card/archidekt_api_response_card_entry.h" + +void ArchidektApiResponseDeck::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + name = json.value("name").toString(); + size = json.value("size").toInt(); + updatedAt = json.value("updatedAt").toString(); + createdAt = json.value("createdAt").toString(); + deckFormat = json.value("deckFormat").toInt(); + edhBracket = json.value("edhBracket").toInt(); + featured = json.value("featured").toString(); + customFeatured = json.value("customFeatured").toString(); + viewCount = json.value("viewCount").toInt(); + privateDeck = json.value("private").toBool(); + unlisted = json.value("unlisted").toBool(); + theoryCrafted = json.value("theoryCrafted").toBool(); + points = json.value("points").toInt(); + userInput = json.value("userInput").toInt(); + owner.fromJson(json.value("owner").toObject()); + commentRoot = json.value("commentRoot").toInt(); + editors = json.value("editors").toString(); + parentFolderId = json.value("parentFolderId").toInt(); + bookmarked = json.value("bookmarked").toBool(); + + auto categoriesJson = json.value("categories").toArray(); + for (auto category : categoriesJson) { + ArchidektApiResponseDeckCategory categoryEntry; + categoryEntry.fromJson(category.toObject()); + categories.append(categoryEntry); + } + + // deckTags = {""}; + playgroupDeckUrl = json.value("playgroupDeckUrl").toString(); + cardPackage = json.value("cardPackage").toString(); + + auto cardsObject = json.value("cards").toArray(); + + for (auto card : cardsObject) { + ArchidektApiResponseCardEntry entry; + entry.fromJson(card.toObject()); + cards.append(entry); + } + + // TODO but not really important + // customCards = {""}; +} + +void ArchidektApiResponseDeck::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "Name:" << name; + qDebug() << "Size:" << size; + qDebug() << "UpdatedAt:" << updatedAt; + qDebug() << "CreatedAt:" << createdAt; + qDebug() << "DeckFormat:" << deckFormat; + qDebug() << "EdhBracket:" << edhBracket; + qDebug() << "Featured:" << featured; + qDebug() << "CustomFeatured:" << customFeatured; + qDebug() << "ViewCount:" << viewCount; + qDebug() << "Private:" << privateDeck; + qDebug() << "Unlisted:" << unlisted; + qDebug() << "TheoryCrafted:" << theoryCrafted; + qDebug() << "Points:" << points; + qDebug() << "UserInput:" << userInput; + owner.debugPrint(); + qDebug() << "CommentRoot:" << commentRoot; + qDebug() << "Editors:" << editors; + qDebug() << "ParentFolderId:" << parentFolderId; + qDebug() << "Bookmarked:" << bookmarked; + qDebug() << "DeckTags:" << deckTags; + qDebug() << "PlaygroupDeckUrl:" << playgroupDeckUrl; + qDebug() << "CardPackage:" << cardPackage; + for (auto card : cards) { + card.debugPrint(); + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..fce437751 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h @@ -0,0 +1,76 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H + +#include "../card/archidekt_api_response_card.h" +#include "../card/archidekt_api_response_card_entry.h" +#include "../deck_listings/archidekt_api_response_deck_owner.h" +#include "archidekt_api_response_deck_category.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeck +{ +public: + // Constructor + ArchidektApiResponseDeck() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + QVector getCards() const + { + return cards; + }; + + QVector getCategories() const + { + return categories; + } + + QString getDeckName() const + { + return name; + }; + + int getDeckFormat() const + { + return deckFormat; + } + +private: + int id; + QString name; + int size; + QString updatedAt; + QString createdAt; + int deckFormat; + int edhBracket; + QString featured; + QString customFeatured; + int viewCount; + bool privateDeck; + bool unlisted; + bool theoryCrafted; + int points; + int userInput; + ArchidektApiResponseDeckOwner owner; + int commentRoot; + QString editors; + int parentFolderId; + bool bookmarked; + QVector categories; + QStringList deckTags; + QString playgroupDeckUrl; + QString cardPackage; + QVector cards; + QStringList customCards; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp new file mode 100644 index 000000000..990293bc6 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp @@ -0,0 +1,19 @@ +#include "archidekt_api_response_deck_category.h" + +void ArchidektApiResponseDeckCategory::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + name = json.value("name").toString(); + isPremier = json.value("isPremier").toBool(); + includedInDeck = json.value("includedInDeck").toBool(); + includedInPrice = json.value("includedInPrice").toBool(); +} + +void ArchidektApiResponseDeckCategory::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "Name:" << name; + qDebug() << "isPremier:" << isPremier; + qDebug() << "IncludedInDeck:" << includedInDeck; + qDebug() << "IncludedInPrice:" << includedInPrice; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h new file mode 100644 index 000000000..5e218b6a4 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h @@ -0,0 +1,55 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_CATEGORY_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_CATEGORY_H + +#include "../card/archidekt_api_response_card.h" +#include "../card/archidekt_api_response_card_entry.h" +#include "../deck_listings/archidekt_api_response_deck_owner.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeckCategory +{ +public: + // Constructor + ArchidektApiResponseDeckCategory() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + [[nodiscard]] int getId() const + { + return id; + } + [[nodiscard]] QString getName() const + { + return name; + } + [[nodiscard]] bool getIsPremier() const + { + return isPremier; + } + [[nodiscard]] bool getIncludedInDeck() const + { + return includedInDeck; + } + [[nodiscard]] bool getIncludedInPrice() const + { + return includedInPrice; + } + +private: + int id; + QString name; + bool isPremier; + bool includedInDeck; + bool includedInPrice; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_CATEGORY_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp new file mode 100644 index 000000000..16ea60487 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp @@ -0,0 +1,64 @@ +#include "archidekt_api_response_deck_listing_container.h" + +#include "archidekt_api_response_deck_owner.h" + +#include +#include +#include + +void ArchidektApiResponseDeckListingContainer::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + name = json.value("name").toString(); + size = json.value("size").toInt(); + updatedAt = json.value("updatedAt").toString(); + createdAt = json.value("createdAt").toString(); + deckFormat = json.value("deckFormat").toInt(); + edhBracket = json.value("edhBracket").toInt(); + featured = json.value("featured").toString(); + customFeatured = json.value("customFeatured").toString(); + viewCount = json.value("viewCount").toInt(); + privateDeck = json.value("private").toBool(); + unlisted = json.value("unlisted").toBool(); + theoryCrafted = json.value("theoryCrafted").toBool(); + game = json.value("game").toString(); + hasDescription = json.value("hasDescription").toBool(); + // TODO + // tags = {""}; + parentFolderId = json.value("parentFolderId").toInt(); + owner.fromJson(json.value("owner").toObject()); + + auto colorsJson = json.value("colors").toObject(); + + for (auto color : colorsJson.keys()) { + colors[color] = colorsJson[color].toInt(); + } + + cardPackage = json.value("cardPackage").toString(); + contest = json.value("contest").toString(); +} + +void ArchidektApiResponseDeckListingContainer::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "Name:" << name; + qDebug() << "Size:" << size; + qDebug() << "UpdatedAt:" << updatedAt; + qDebug() << "CreatedAt:" << createdAt; + qDebug() << "DeckFormat:" << deckFormat; + qDebug() << "EdhBracket:" << edhBracket; + qDebug() << "Featured:" << featured; + qDebug() << "CustomFeatured:" << customFeatured; + qDebug() << "ViewCount:" << viewCount; + qDebug() << "Private:" << privateDeck; + qDebug() << "Unlisted:" << unlisted; + qDebug() << "TheoryCrafted:" << theoryCrafted; + qDebug() << "Game:" << game; + qDebug() << "HasDescription:" << hasDescription; + qDebug() << "Tags:" << tags; + qDebug() << "ParentFolderId:" << parentFolderId; + owner.debugPrint(); + qDebug() << "Colors:" << colors; + qDebug() << "CardPackage" << cardPackage; + qDebug() << "Contest:" << contest; +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h new file mode 100644 index 000000000..8924bb3f5 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h @@ -0,0 +1,133 @@ +#ifndef COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H +#define COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H + +#include "archidekt_api_response_deck_owner.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeckListingContainer +{ +public: + // Constructor + ArchidektApiResponseDeckListingContainer() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + [[nodiscard]] int getId() const + { + return id; + } + [[nodiscard]] QString getName() const + { + return name; + } + [[nodiscard]] int getSize() const + { + return size; + } + [[nodiscard]] QString getUpdatedAt() const + { + return updatedAt; + } + [[nodiscard]] QString getCreatedAt() const + { + return createdAt; + } + [[nodiscard]] int getDeckFormat() const + { + return deckFormat; + } + [[nodiscard]] int getEDHBracket() const + { + return edhBracket; + } + [[nodiscard]] QString getFeatured() const + { + return featured; + } + [[nodiscard]] QString getCustomFeatured() const + { + return customFeatured; + } + [[nodiscard]] int getViewCount() const + { + return viewCount; + } + [[nodiscard]] bool getPrivateDeck() const + { + return privateDeck; + } + [[nodiscard]] bool getUnlisted() const + { + return unlisted; + } + [[nodiscard]] bool getTheoryCrafted() const + { + return theoryCrafted; + } + [[nodiscard]] QString getGame() const + { + return game; + } + [[nodiscard]] bool getHasDescription() const + { + return hasDescription; + } + [[nodiscard]] QStringList getTags() const + { + return tags; + } + [[nodiscard]] int getParentFolderId() const + { + return parentFolderId; + } + [[nodiscard]] ArchidektApiResponseDeckOwner getOwner() const + { + return owner; + } + [[nodiscard]] QMap getColors() const + { + return colors; + } + [[nodiscard]] QString getCardPackage() const + { + return cardPackage; + } + [[nodiscard]] QString getContest() const + { + return contest; + } + +private: + int id; + QString name; + int size; + QString updatedAt; + QString createdAt; + int deckFormat; + int edhBracket; + QString featured; + QString customFeatured; + int viewCount; + bool privateDeck; + bool unlisted; + bool theoryCrafted; + QString game; + bool hasDescription; + QStringList tags; + int parentFolderId; + ArchidektApiResponseDeckOwner owner; + QMap colors; + QString cardPackage; + QString contest; +}; + +#endif // COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp new file mode 100644 index 000000000..2ba926f8b --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp @@ -0,0 +1,22 @@ +#include "archidekt_api_response_deck_owner.h" + +void ArchidektApiResponseDeckOwner::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + userName = json.value("username").toString(); + avatar = QUrl(json.value("avatar").toString()); + moderator = json.value("moderator").toBool(); + pledgeLevel = json.value("pledgeLevel").toInt(); + // TODO but not really important + // roles = {""}; +} + +void ArchidektApiResponseDeckOwner::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "UserName:" << userName; + qDebug() << "Avatar:" << avatar; + qDebug() << "Moderator:" << moderator; + qDebug() << "PledgeLevel:" << pledgeLevel; + qDebug() << "Roles:" << roles; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h new file mode 100644 index 000000000..df3f48fd2 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h @@ -0,0 +1,36 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeckOwner +{ +public: + // Constructor + ArchidektApiResponseDeckOwner() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + [[nodiscard]] QString getName() const + { + return userName; + } + +private: + int id; + QString userName; + QUrl avatar; + bool moderator; + int pledgeLevel; + QStringList roles; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H 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 new file mode 100644 index 000000000..1547cca74 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -0,0 +1,161 @@ +#include "archidekt_api_response_deck_display_widget.h" + +#include "../../../../../deck_loader/card_node_function.h" +#include "../../../../../deck_loader/deck_loader.h" +#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" +#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 +#include + +ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWidget *parent, + ArchidektApiResponseDeck _response, + CardSizeWidget *_cardSizeSlider) + : QWidget(parent), response(_response), cardSizeSlider(_cardSizeSlider) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + openInEditorButton = new QPushButton(this); + layout->addWidget(openInEditorButton); + + connect(openInEditorButton, &QPushButton::clicked, this, + &ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor); + + displayOptionsWidget = new VisualDeckDisplayOptionsWidget(this); + layout->addWidget(displayOptionsWidget); + + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, this, + &ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange); + + scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + layout->addWidget(scrollArea); + + container = new QWidget(scrollArea); + + scrollArea->setWidget(container); + + containerLayout = new QVBoxLayout(container); + container->setLayout(containerLayout); + + zoneContainer = new QWidget(container); + containerLayout->addWidget(zoneContainer); + zoneContainerLayout = new QVBoxLayout(zoneContainer); + zoneContainer->setLayout(zoneContainerLayout); + + QString tempDeck; + QTextStream deckStream(&tempDeck); + + for (auto card : response.getCards()) { + QString fullName = card.getCard().getOracleCard().value("name").toString(); + // We don't really care about the second card, the card database already has it as a relation + QString cleanName = fullName.split("//").first().trimmed(); + + tempDeck += QString("%1 %2 (%3) %4\n") + .arg(card.getQuantity()) + .arg(cleanName) + .arg(card.getCard().getEdition().getEditionCode().toUpper()) + .arg(card.getCard().getCollectorNumber()); + } + + model = new DeckListModel(this); + connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); + model->getDeckList()->loadFromStream_Plain(deckStream, false); + + model->forEachCard(CardNodeFunction::ResolveProviderId()); + + retranslateUi(); +} + +void ArchidektApiResponseDeckDisplayWidget::retranslateUi() +{ + openInEditorButton->setText(tr("Open Deck in Deck Editor")); +} + +void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString &activeGroupCriteria) +{ + model->setActiveGroupCriteria(DeckListModelGroupCriteria::fromString(activeGroupCriteria)); + model->sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); +} + +void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor() +{ + DeckList deckList(*model->getDeckList()); + deckList.setName(response.getDeckName()); + deckList.setGameFormat( + ArchidektFormats::formatToCockatriceName(ArchidektFormats::DeckFormat(response.getDeckFormat() - 1))); + + LoadedDeck loadedDeck = {deckList, {}}; + + emit openInDeckEditor(loadedDeck); +} + +void ArchidektApiResponseDeckDisplayWidget::clearAllDisplayWidgets() +{ + for (auto idx : indexToWidgetMap.keys()) { + auto displayWidget = indexToWidgetMap.value(idx); + zoneContainerLayout->removeWidget(displayWidget); + indexToWidgetMap.remove(idx); + delete displayWidget; + } +} + +void ArchidektApiResponseDeckDisplayWidget::decklistModelReset() +{ + clearAllDisplayWidgets(); + constructZoneWidgetsFromDeckListModel(); +} + +void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListModel() +{ + qDebug() << model->rowCount(model->getRoot()); + QSortFilterProxyModel proxy; + proxy.setSourceModel(model); + proxy.setSortRole(Qt::EditRole); + proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); + + for (int i = 0; i < proxy.rowCount(); ++i) { + QModelIndex proxyIndex = proxy.index(i, 0); + QModelIndex sourceIndex = proxy.mapToSource(proxyIndex); + + // Make a persistent index from the *source* model + QPersistentModelIndex persistent(sourceIndex); + + if (indexToWidgetMap.contains(persistent)) { + continue; + } + + QString zoneName = + persistent.sibling(persistent.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + + DeckCardZoneDisplayWidget *zoneDisplayWidget = + new DeckCardZoneDisplayWidget(zoneContainer, model, nullptr, persistent, zoneName, "maintype", {"name"}, + DisplayType::Overlap, 20, 10, cardSizeSlider); + + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::displayTypeChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::refreshDisplayType); + zoneContainerLayout->addWidget(zoneDisplayWidget); + + indexToWidgetMap.insert(persistent, zoneDisplayWidget); + } +} + +void ArchidektApiResponseDeckDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layout->invalidate(); + layout->activate(); + layout->update(); +} 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 new file mode 100644 index 000000000..58aca429e --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h @@ -0,0 +1,125 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H + +#include "../../../../../deck_loader/deck_loader.h" +#include "../../../../cards/card_size_widget.h" +#include "../../../../general/layout_containers/flow_widget.h" +#include "../../../../visual_deck_editor/visual_deck_display_options_widget.h" +#include "../api_response/deck/archidekt_api_response_deck.h" +#include "deck_list_model.h" + +#include +#include +#include +#include +#include + +/** + * @class ArchidektApiResponseDeckDisplayWidget + * @brief Displays a full deck fetched from an Archidekt API response. + * + * This widget visualizes all cards in a deck retrieved from the Archidekt API. + * It supports: + * - Interactive display options via a VisualDeckDisplayOptionsWidget. + * - Scrollable display of deck zones/cards with DeckCardZoneDisplayWidget. + * - Integration with a CardSizeWidget slider for card scaling. + * - Opening the deck in a deck editor. + * + * The widget internally constructs a DeckListModel from the Archidekt API response, + * then builds zone widgets for each group of cards according to the active group + * criteria. It also responds dynamically to model resets or sorting/grouping changes. + * + * ### Signals + * - `requestNavigation(QString url)` — triggered when navigation to a deck URL is requested. + * - `openInDeckEditor(const LoadedDeck &deck)` — emitted when the user chooses to open the deck + * in the deck editor. + * + * ### Features + * - Automatically generates DeckCardZoneDisplayWidget instances for each card group. + * - Provides a scrollable layout for decks of arbitrary size. + * - Updates layouts dynamically when resized or when display/group/sort criteria change. + */ +class ArchidektApiResponseDeckDisplayWidget : public QWidget +{ + Q_OBJECT + +signals: + /** + * @brief Emitted when navigation to a deck URL is requested. + * @param url URL of the deck on Archidekt. + */ + void requestNavigation(QString url); + + /** + * @brief Emitted when the deck should be opened in the deck editor. + * @param deck LoadedDeck containing the deck data. + */ + void openInDeckEditor(const LoadedDeck &deck); + +public: + /** + * @brief Constructs a display widget for an Archidekt deck. + * @param parent Parent widget. + * @param response API deck data container. + * @param cardSizeSlider Slider controlling card scaling. + */ + explicit ArchidektApiResponseDeckDisplayWidget(QWidget *parent, + ArchidektApiResponseDeck response, + CardSizeWidget *cardSizeSlider); + + /** + * @brief Updates all UI text for retranslation/localization. + * + * Called when the application language changes. + */ + void retranslateUi(); + + /** + * @brief Opens the deck in the deck editor. + */ + void actOpenInDeckEditor(); + + /** + * @brief Clears all dynamically generated card zone display widgets. + */ + void clearAllDisplayWidgets(); + + /** + * @brief Handles model reset by clearing and reconstructing display widgets. + */ + void decklistModelReset(); + + /** + * @brief Builds DeckCardZoneDisplayWidget instances from the current DeckListModel. + */ + void constructZoneWidgetsFromDeckListModel(); + +private slots: + /** + * @brief Slot triggered when the active group criteria change. + * @param activeGroupCriteria Name of the new grouping criteria. + */ + void onGroupCriteriaChange(const QString &activeGroupCriteria); + +private: + ArchidektApiResponseDeck response; ///< API deck data container + CardSizeWidget *cardSizeSlider; ///< Slider for adjusting card sizes + QVBoxLayout *layout; ///< Main vertical layout + QPushButton *openInEditorButton; ///< Button to open deck in editor + VisualDeckDisplayOptionsWidget *displayOptionsWidget; ///< Controls grouping/sorting/display + QScrollArea *scrollArea; ///< Scrollable area for deck zones + QWidget *zoneContainer; ///< Container for deck zones + QVBoxLayout *zoneContainerLayout; ///< Layout for deck zones + QWidget *container; ///< Outer container for scroll area + QHash indexToWidgetMap; ///< Maps model indices to widgets + QVBoxLayout *containerLayout; ///< Layout for container + DeckListModel *model; ///< Deck list model +protected slots: + /** + * @brief Updates layout and display on resize. + * @param event Resize event. + */ + void resizeEvent(QResizeEvent *event) override; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H 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 new file mode 100644 index 000000000..2998a03bc --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -0,0 +1,237 @@ +#include "archidekt_api_response_deck_entry_display_widget.h" + +#include "../../../../../card_picture_loader/card_picture_loader.h" +#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" +#include "../../../../general/display/background_plate_widget.h" +#include "../../../../general/display/color_bar.h" +#include "archidekt_deck_preview_image_display_widget.h" + +#include +#include +#include +#include +#include +#include +#include + +#define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg" + +static QString timeAgo(const QString ×tamp) +{ + QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate); + + if (!dt.isValid()) + return timestamp; // fallback if parsing fails + + qint64 secs = dt.secsTo(QDateTime::currentDateTimeUtc()); + + if (secs < 60) + return QString("%1 seconds ago").arg(secs); + if (secs < 3600) + return QString("%1 minutes ago").arg(secs / 60); + if (secs < 86400) + return QString("%1 hours ago").arg(secs / 3600); + if (secs < 30 * 86400) + return QString("%1 days ago").arg(secs / 86400); + if (secs < 365 * 86400) + return QString("%1 months ago").arg(secs / (30 * 86400)); + + return QString("%1 years ago").arg(secs / (365 * 86400)); +} + +ArchidektApiResponseDeckEntryDisplayWidget::ArchidektApiResponseDeckEntryDisplayWidget( + QWidget *parent, + ArchidektApiResponseDeckListingContainer _response, + QNetworkAccessManager *_imageNetworkManager) + : QWidget(parent), response(_response), imageNetworkManager(_imageNetworkManager) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + this->setMaximumWidth(400); + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + + auto headerLayout = new QVBoxLayout(); + + previewWidget = new ArchidektDeckPreviewImageDisplayWidget(this); + + previewWidget->setMaximumWidth(400); + previewWidget->setMinimumHeight(300); // consistent height + + // Set deck name (ellided) + QFontMetrics fm(previewWidget->topLeftLabel->font()); + QString elided = fm.elidedText(response.getName(), Qt::ElideRight, 280); + previewWidget->topLeftLabel->setLabelText(elided); + previewWidget->topLeftLabel->setToolTip(response.getName()); + + // Set count + previewWidget->topRightLabel->setLabelText(QString::number(response.getSize())); + + // EDH bracket (skip if 0) + if (response.getEDHBracket() != 0) { + previewWidget->bottomLeftLabel->setLabelText(QString("EDH: %1").arg(response.getEDHBracket())); + } else { + previewWidget->bottomLeftLabel->hide(); + } + + // Views + previewWidget->bottomRightLabel->setLabelText(QString("Views: %1").arg(response.getViewCount())); + + // Use preview->imageLabel for image loading + picture = previewWidget->imageLabel; + + imageUrl = response.getFeatured().isEmpty() ? QUrl(ARCHIDEKT_DEFAULT_IMAGE) : QUrl(response.getFeatured()); + + QNetworkRequest req(imageUrl); + req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + QNetworkReply *reply = imageNetworkManager->get(req); + + // tag the reply with "this" so we know it belongs to us later + reply->setProperty("deckWidget", QVariant::fromValue(this)); + reply->setProperty("requestedUrl", imageUrl); + + connect(imageNetworkManager, &QNetworkAccessManager::finished, this, + &ArchidektApiResponseDeckEntryDisplayWidget::onPreviewImageLoadFinished); + + headerLayout->addWidget(previewWidget); + + auto colors = response.getColors(); + + ColorBar *colorBar = new ColorBar(colors, this); + colorBar->setMinPercentThreshold(3); + colorBar->setFixedHeight(22); + + headerLayout->addWidget(colorBar); + + // Create a shared plate for the labels + backgroundPlateWidget = new BackgroundPlateWidget(this); + backgroundPlateWidget->setFixedHeight(120); // Adjust height to fit all labels + + QVBoxLayout *plateLayout = new QVBoxLayout(backgroundPlateWidget); + + // Add labels to the plate layout + QLabel *ownerLabel = new QLabel(QString("Owner: %1").arg(response.getOwner().getName())); + plateLayout->addWidget(ownerLabel); + + QLabel *createdAtLabel = new QLabel(QString("Created: %1").arg(timeAgo(response.getCreatedAt()))); + plateLayout->addWidget(createdAtLabel); + + QLabel *updatedAtLabel = new QLabel(QString("Updated: %1").arg(timeAgo(response.getUpdatedAt()))); + plateLayout->addWidget(updatedAtLabel); + + // Add the shared plate to the header layout + headerLayout->addWidget(backgroundPlateWidget); + + layout->addLayout(headerLayout); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + actRequestNavigationToDeck(); +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void ArchidektApiResponseDeckEntryDisplayWidget::enterEvent(QEnterEvent *event) +#else +void ArchidektApiResponseDeckEntryDisplayWidget::enterEvent(QEvent *event) +#endif +{ + QWidget::enterEvent(event); + backgroundPlateWidget->setFocused(true); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::leaveEvent(QEvent *event) +{ + QWidget::leaveEvent(event); + backgroundPlateWidget->setFocused(false); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::setScaleFactor(int scale) +{ + scaleFactor = scale; + updateScaledPreview(); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::onPreviewImageLoadFinished(QNetworkReply *reply) +{ + // Check if this is our reply + void *owner = reply->property("deckWidget").value(); + if (owner != this) { + return; // not our reply + } + + // Check that the requested URL matches what we asked + QUrl requestedUrl = reply->property("requestedUrl").toUrl(); + if (requestedUrl != imageUrl) { + reply->deleteLater(); + return; + } + + QPixmap loaded; + + if (reply->error() != QNetworkReply::NoError || !loaded.loadFromData(reply->readAll())) { + CardPictureLoader::getCardBackLoadingFailedPixmap(loaded, QSize(400, 400)); + } + + originalPixmap = loaded; + + // Always scale preview widget to this ratio + previewWidget->setAspectRatio(DESIGN_RATIO); + previewWidget->setPreviewWidth(400); + + // Initial scaling + updateScaledPreview(); + + reply->deleteLater(); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::updateScaledPreview() +{ + if (originalPixmap.isNull()) { + return; + } + + int baseWidth = 400; + int newWidth = baseWidth * scaleFactor / 100; + int newHeight = static_cast(newWidth * DESIGN_RATIO); + + previewWidget->setFixedSize(newWidth, newHeight); + + // Scale image to fill the preview area (crop edges) + QPixmap scaled = + originalPixmap.scaled(newWidth, newHeight, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + + // Crop to exact target size + QRect cropRect((scaled.width() - newWidth) / 2, (scaled.height() - newHeight) / 2, newWidth, newHeight); + QPixmap cropped = scaled.copy(cropRect); + + picture->setPixmap(cropped); + picture->setFixedSize(newWidth, newHeight); + + // Update the elided deck name based on new width + int textMaxWidth = int(newWidth * 0.7); // allow 70% of width for text + QFontMetrics fm(previewWidget->topLeftLabel->font()); + QString elided = fm.elidedText(response.getName(), Qt::ElideRight, textMaxWidth); + previewWidget->topLeftLabel->setText(elided); + previewWidget->topLeftLabel->setToolTip(response.getName()); + + setFixedWidth(newWidth); + + layout->invalidate(); + layout->activate(); + updateGeometry(); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layout->invalidate(); + layout->activate(); + layout->update(); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::actRequestNavigationToDeck() +{ + emit requestNavigation(QString("https://archidekt.com/api/decks/%1/").arg(response.getId())); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h new file mode 100644 index 000000000..365a99c9c --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h @@ -0,0 +1,121 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H + +#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" +#include "../api_response/deck_listings/archidekt_api_response_deck_listing_container.h" +#include "archidekt_deck_preview_image_display_widget.h" + +#include +#include +#include +#include +#include + +class BackgroundPlateWidget; + +/** + * @class ArchidektApiResponseDeckEntryDisplayWidget + * @brief Displays a single Archidekt deck listing as a preview card with metadata. + * + * This widget renders a deck entry received from an Archidekt API response. It includes: + * - A scaled deck preview image loaded asynchronously via QNetworkAccessManager. + * - Elided deck name in the top-left corner. + * - Deck size, EDH bracket, and view count labels. + * - A color distribution bar summarizing deck colors. + * - Metadata labels including owner, creation date, and last update date. + * + * The widget dynamically scales the preview image and labels according to a linked + * CardSizeWidget slider. Hovering over the widget highlights the background plate, + * and clicking emits a `requestNavigation` signal pointing to the deck URL. + * + * ### Features + * - Asynchronous image loading with fallback to a default placeholder image. + * - Maintains a fixed aspect ratio for the preview image (150:267). + * - Updates text elision dynamically when resized or scaled. + * - Integrates with FlowWidget containers for scrollable deck galleries. + * + * ### Signals + * - `requestNavigation(QString url)` — emitted when the widget is clicked to request + * navigation to the deck's Archidekt page. + * + * ### Slots + * - `actRequestNavigationToDeck()` — triggers navigation. + * - `setScaleFactor(int scale)` — adjusts preview image scaling. + */ +class ArchidektApiResponseDeckEntryDisplayWidget : public QWidget +{ + Q_OBJECT + +signals: + /** + * @brief Emitted when the user requests navigation. + * @param url Full URL to the Archidekt page. + */ + void requestNavigation(QString url); + +public: + /** + * @brief Constructs a deck entry display widget. + * @param parent Parent widget. + * @param response API container holding deck listing data. + * @param imageNetworkManager Shared network manager for fetching preview images. + */ + explicit ArchidektApiResponseDeckEntryDisplayWidget(QWidget *parent, + ArchidektApiResponseDeckListingContainer response, + QNetworkAccessManager *imageNetworkManager); + + /** + * @brief Handles finished network replies for preview images. + * @param reply QNetworkReply containing image data. + * + * Validates that the reply corresponds to this widget and updates the preview image. + */ + void onPreviewImageLoadFinished(QNetworkReply *reply); + + /** + * @brief Updates the scaled preview image and adjusts layout accordingly. + */ + void updateScaledPreview(); + + /** + * @brief Ensures layout responds correctly on resize events. + * @param event Resize event. + */ + void resizeEvent(QResizeEvent *event) override; + +public slots: + /** + * @brief Emits `requestNavigation` for the deck's URL. + */ + void actRequestNavigationToDeck(); + + /** + * @brief Sets a scaling factor (percentage) for the preview image. + * @param scale Scale percentage (100 = normal size). + */ + void setScaleFactor(int scale); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void enterEvent(QEnterEvent *event) override; ///< Qt6 hover enter +#else + void enterEvent(QEvent *event) override; ///< Qt5 hover enter +#endif + void leaveEvent(QEvent *event) override; + +private: + QVBoxLayout *layout; ///< Main vertical layout + ArchidektApiResponseDeckListingContainer response; ///< Deck data + QUrl imageUrl; ///< URL of the deck's preview image + QNetworkAccessManager *imageNetworkManager; ///< Shared network manager + ArchidektDeckPreviewImageDisplayWidget *previewWidget; ///< Widget showing the deck preview + QLabel *picture; ///< QLabel displaying the scaled pixmap + QPixmap originalPixmap; ///< Original image for scaling (avoids degradation) + int scaleFactor = 100; ///< Current scaling percentage + BackgroundPlateWidget *backgroundPlateWidget; ///< Plate for metadata labels + static constexpr float DESIGN_RATIO = 150.0f / 267.0f; ///< Design aspect ratio +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H 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 new file mode 100644 index 000000000..8746093c7 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp @@ -0,0 +1,44 @@ +#include "archidekt_api_response_deck_listings_display_widget.h" + +#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" +#include "archidekt_api_response_deck_entry_display_widget.h" + +ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsDisplayWidget( + QWidget *parent, + ArchidektDeckListingApiResponse response, + CardSizeWidget *_cardSizeSlider) + : QWidget(parent), cardSizeSlider(_cardSizeSlider) +{ + layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + + imageNetworkManager = new QNetworkAccessManager(this); + imageNetworkManager->setTransferTimeout(); // Use Qt's default timeout + imageNetworkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); + + // Add widgets for deck listings + auto deckListings = response.results; + for (const auto &deckListing : deckListings) { + auto cardListDisplayWidget = + new ArchidektApiResponseDeckEntryDisplayWidget(this, deckListing, imageNetworkManager); + cardListDisplayWidget->setScaleFactor(cardSizeSlider->getSlider()->value()); + connect(cardListDisplayWidget, &ArchidektApiResponseDeckEntryDisplayWidget::requestNavigation, this, + &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation); + connect(cardSizeSlider->getSlider(), &QSlider::valueChanged, cardListDisplayWidget, + &ArchidektApiResponseDeckEntryDisplayWidget::setScaleFactor); + flowWidget->addWidget(cardListDisplayWidget); + } + + layout->addWidget(flowWidget); +} + +void ArchidektApiResponseDeckListingsDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layout->invalidate(); + layout->activate(); + layout->update(); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h new file mode 100644 index 000000000..f00941239 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h @@ -0,0 +1,97 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H + +#include "../../../../cards/card_size_widget.h" +#include "../../../../general/layout_containers/flow_widget.h" +#include "../api_response/archidekt_deck_listing_api_response.h" + +#include +#include +#include +#include +#include + +/** + * @class ArchidektApiResponseDeckListingsDisplayWidget + * @brief Displays a scrollable horizontal list of Archidekt deck listings with dynamic card sizing. + * + * This widget serves as a container for multiple + * ArchidektApiResponseDeckEntryDisplayWidget instances, each representing one deck listing + * returned from an Archidekt API call. + * + * ### Responsibilities + * - Creates a **FlowWidget** that arranges deck entries horizontally with wrapping. + * - Creates a shared **QNetworkAccessManager** for entry widgets to fetch card thumbnails. + * - Connects a **CardSizeWidget** slider to all deck entries to dynamically rescale their preview cards. + * - Propagates deck-navigation requests (`requestNavigation`) from children to the parent. + * + * ### Layout + * The widget uses a single `QHBoxLayout` containing the `FlowWidget`. + * The FlowWidget automatically manages child flow and scrollbar behavior (horizontal = off, + * vertical = auto), providing an efficient scrollable gallery of deck entries. + * + * ### API Integration + * The constructor consumes an `ArchidektDeckListingApiResponse`, iterates through its + * `results`, and instantiates a child entry widget for each deck. + * + * ### Signals + * - `requestNavigation(QString url)` — emitted whenever a child entry widget requests + * navigation to a deck or related Archidekt page. + * + * ### Performance Notes + * - All entry widgets share a single QNetworkAccessManager instance to reuse connections + * and avoid redundant session creation. + * - `resizeEvent()` forces layout invalidation to ensure the flow layout responds properly + * to container resizing. + */ +class ArchidektApiResponseDeckListingsDisplayWidget : public QWidget +{ + Q_OBJECT + +signals: + /** + * @brief Emitted when a child deck entry requests that the UI navigate to a particular URL. + * @param url The destination URL (typically an Archidekt deck page). + */ + void requestNavigation(QString url); + +public: + /** + * @brief Constructs a widget that displays multiple deck listing previews. + * + * @param parent Parent widget. + * @param response The Archidekt API response containing deck listings. + * @param cardSizeSlider A slider widget used to dynamically resize card previews. + * + * Each deck in `response.results` becomes its own + * ArchidektApiResponseDeckEntryDisplayWidget, added to the FlowWidget. + */ + explicit ArchidektApiResponseDeckListingsDisplayWidget(QWidget *parent, + ArchidektDeckListingApiResponse response, + CardSizeWidget *cardSizeSlider); + + /** + * @brief Ensures FlowWidget layout properly recomputes on resize. + * + * Forces the layout to invalidate and activate itself so that the + * FlowWidget recalculates wrapping and child placement. + * + * @param event Resize event. + */ + void resizeEvent(QResizeEvent *event) override; + +private: + /// Slider controlling the scale of card thumbnails in all deck entry widgets. + CardSizeWidget *cardSizeSlider; + + /// Main horizontal layout containing the FlowWidget. + QHBoxLayout *layout; + + /// Container providing scrollable multi-row flow layout of deck entries. + FlowWidget *flowWidget; + + /// Shared network manager used to download card images for all child entry widgets. + QNetworkAccessManager *imageNetworkManager; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp new file mode 100644 index 000000000..ced08491e --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp @@ -0,0 +1,80 @@ +#include "archidekt_deck_preview_image_display_widget.h" + +#include +#include + +ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(QWidget *parent) : QWidget(parent) +{ + imageLabel = new QLabel(this); + imageLabel->setAlignment(Qt::AlignCenter); + + topLeftLabel = new ShadowBackgroundLabel(this, ""); + topRightLabel = new ShadowBackgroundLabel(this, ""); + bottomLeftLabel = new ShadowBackgroundLabel(this, ""); + bottomRightLabel = new ShadowBackgroundLabel(this, ""); + + QFont f; + f.setBold(true); + f.setPointSize(16); + topLeftLabel->setFont(f); + topRightLabel->setFont(f); + bottomLeftLabel->setFont(f); + bottomRightLabel->setFont(f); + + // Raise so labels appear above image + topLeftLabel->raise(); + topRightLabel->raise(); + bottomLeftLabel->raise(); + bottomRightLabel->raise(); +} + +void ArchidektDeckPreviewImageDisplayWidget::setAspectRatio(float ratio) +{ + aspectRatio = ratio; +} + +void ArchidektDeckPreviewImageDisplayWidget::setPreviewWidth(int width) +{ + int height = int(width * aspectRatio); + + setFixedSize(width, height); + updateGeometry(); + update(); +} + +void ArchidektDeckPreviewImageDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + // Full size for the image + imageLabel->setGeometry(rect()); + + topLeftLabel->adjustSize(); + topRightLabel->adjustSize(); + bottomLeftLabel->adjustSize(); + bottomRightLabel->adjustSize(); + + // Padding settings + const int horizontalPadding = 8; + const int topPadding = 6; + const int bottomPadding = 6; + + // Left-aligned, top-aligned (Deck Name) + topLeftLabel->move(horizontalPadding, topPadding); + + // Right-aligned, top-aligned (Card Count) + topRightLabel->move(width() - topRightLabel->width() - horizontalPadding, topPadding); + + // Bottom-left, bottom-aligned (EDH bracket) + bottomLeftLabel->move(horizontalPadding, height() - bottomLeftLabel->height() - bottomPadding); + + // Bottom-right, bottom-aligned (views) + bottomRightLabel->move(width() - bottomRightLabel->width() - horizontalPadding, + height() - bottomRightLabel->height() - bottomPadding); + + // Ensure labels stay above image + topLeftLabel->raise(); + topRightLabel->raise(); + bottomLeftLabel->raise(); + bottomRightLabel->raise(); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h new file mode 100644 index 000000000..053823c45 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h @@ -0,0 +1,66 @@ +#ifndef COCKATRICE_ARCHIDEKT_DECK_PREVIEW_IMAGE_DISPLAY_WIDGET_H +#define COCKATRICE_ARCHIDEKT_DECK_PREVIEW_IMAGE_DISPLAY_WIDGET_H + +#include "../../../../general/display/shadow_background_label.h" + +#include +#include + +/** + * @class ArchidektDeckPreviewImageDisplayWidget + * @brief Widget for displaying a deck preview image with overlaid metadata labels. + * + * This widget shows a deck's preview image along with several overlay labels: + * - Top-left: Deck name. + * - Top-right: Card count. + * - Bottom-left: EDH bracket (if applicable). + * - Bottom-right: View count. + * + * Labels automatically scale and position themselves relative to the widget's size. + * The image can be scaled while maintaining a specified aspect ratio. + * + * ### Features + * - Adjustable preview width and aspect ratio. + * - Labels automatically repositioned on resize. + * - Supports overlaying multiple pieces of metadata with shadowed labels for readability. + */ +class ArchidektDeckPreviewImageDisplayWidget : public QWidget +{ + Q_OBJECT +public: + /** + * @brief Constructs the deck preview display widget. + * @param parent Optional parent widget. + */ + explicit ArchidektDeckPreviewImageDisplayWidget(QWidget *parent = nullptr); + + /** + * @brief Sets the aspect ratio for the preview image (height / width). + * @param ratio Aspect ratio to maintain. + */ + void setAspectRatio(float ratio); + + /** + * @brief Sets the width of the preview image; height is adjusted according to the aspect ratio. + * @param width Desired width in pixels. + */ + void setPreviewWidth(int width); + + QLabel *imageLabel; ///< QLabel to display the deck image + ShadowBackgroundLabel *topLeftLabel; ///< Overlay label at top-left (deck name) + ShadowBackgroundLabel *topRightLabel; ///< Overlay label at top-right (card count) + ShadowBackgroundLabel *bottomLeftLabel; ///< Overlay label at bottom-left (EDH bracket) + ShadowBackgroundLabel *bottomRightLabel; ///< Overlay label at bottom-right (views) + +protected: + /** + * @brief Handles resize events to reposition the image and overlay labels. + * @param event Resize event. + */ + void resizeEvent(QResizeEvent *event) override; + +private: + float aspectRatio = 1.0f; ///< Aspect ratio to maintain for the preview image +}; + +#endif // COCKATRICE_ARCHIDEKT_DECK_PREVIEW_IMAGE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp new file mode 100644 index 000000000..3769cd9a2 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -0,0 +1,563 @@ +#include "tab_archidekt.h" + +#include "../../../../../client/settings/cache_settings.h" +#include "../../../cards/additional_info/mana_symbol_widget.h" +#include "../../tab_supervisor.h" +#include "api_response/archidekt_deck_listing_api_response.h" +#include "display/archidekt_api_response_deck_display_widget.h" +#include "display/archidekt_api_response_deck_listings_display_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) +{ + networkManager = new QNetworkAccessManager(this); + networkManager->setTransferTimeout(); // Use Qt's default timeout + networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); + connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); + + searchDebounceTimer = new QTimer(this); + searchDebounceTimer->setSingleShot(true); // We only want it to fire once after inactivity + searchDebounceTimer->setInterval(300); // 300ms debounce + + connect(searchDebounceTimer, &QTimer::timeout, this, [this]() { doSearchImmediate(); }); + + container = new QWidget(this); + mainLayout = new QVBoxLayout(container); + mainLayout->setContentsMargins(0, 0, 0, 0); + container->setLayout(mainLayout); + + navigationContainer = new QWidget(container); + navigationContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + navigationLayout = new QHBoxLayout(navigationContainer); + navigationLayout->setSpacing(3); + navigationContainer->setLayout(navigationLayout); + + // Sort by + + orderByCombo = new QComboBox(navigationContainer); + orderByCombo->addItems({"name", "updatedAt", "createdAt", "viewCount", "size", "edhBracket"}); + orderByCombo->setCurrentText("updatedAt"); // Pre-select updatedAt + + // Asc/Desc toggle + orderDirButton = new QPushButton(tr("Desc."), navigationContainer); + orderDirButton->setCheckable(true); // checked = DESC, unchecked = ASC + orderDirButton->setChecked(true); + + connect(orderByCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + connect(orderDirButton, &QPushButton::clicked, this, [this](bool checked) { + orderDirButton->setText(checked ? tr("Desc.") : tr("Asc.")); + doSearch(); + }); + + // Colors + QHBoxLayout *colorLayout = new QHBoxLayout(); + QString colorIdentity = "WUBRG"; // Optionally include "C" for colorless once we have a symbol for it + + for (const QChar &color : colorIdentity) { + auto *manaSymbol = new ManaSymbolWidget(navigationContainer, color, false, true); + manaSymbol->setFixedWidth(25); + colorLayout->addWidget(manaSymbol); + + connect(manaSymbol, &ManaSymbolWidget::colorToggled, this, [this](QChar c, bool active) { + if (active) { + activeColors.insert(c); + } else { + activeColors.remove(c); + } + doSearch(); + }); + } + + logicalAndCheck = new QCheckBox("Require ALL colors", navigationContainer); + + // Formats + + formatLabel = new QLabel(this); + + formatSettingsWidget = new SettingsButtonWidget(this); + + QStringList formatNames = {"Standard", "Modern", "Commander", "Legacy", "Vintage", + "Pauper", "Custom", "Frontier", "Future Std", "Penny Dreadful", + "1v1 Commander", "Dual Commander", "Brawl"}; + + for (int i = 0; i < formatNames.size(); ++i) { + QCheckBox *formatCheckBox = new QCheckBox(formatNames[i], navigationContainer); + connect(formatCheckBox, &QCheckBox::clicked, this, &TabArchidekt::doSearch); + formatChecks << formatCheckBox; + formatSettingsWidget->addSettingsWidget(formatCheckBox); + } + + // EDH Bracket + edhBracketCombo = new QComboBox(navigationContainer); + edhBracketCombo->addItem(tr("Any Bracket")); + edhBracketCombo->addItems({"1", "2", "3", "4", "5"}); + + connect(edhBracketCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + + // Search for Card Packages instead of Decks + packagesCheck = new QCheckBox("Packages", navigationContainer); + + connect(packagesCheck, &QCheckBox::clicked, this, [this]() { + bool disable = packagesCheck->isChecked(); + for (auto *cb : formatChecks) + cb->setEnabled(!disable); + commandersField->setEnabled(!disable); + deckTagNameField->setEnabled(!disable); + edhBracketCombo->setCurrentIndex(0); + edhBracketCombo->setEnabled(!disable); + doSearch(); + }); + + // Deck Name + nameField = new QLineEdit(navigationContainer); + nameField->setPlaceholderText(tr("Deck name contains...")); + + // Owner Name + ownerField = new QLineEdit(navigationContainer); + ownerField->setPlaceholderText(tr("Owner name contains...")); + + // Contained cards + cardsField = new QLineEdit(navigationContainer); + cardsField->setPlaceholderText("Deck contains card..."); + + // Commanders + commandersField = new QLineEdit(navigationContainer); + commandersField->setPlaceholderText("Deck has commander..."); + + // DB supplemented card search + auto cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this); + auto displayModel = new CardDatabaseDisplayModel(this); + displayModel->setSourceModel(cardDatabaseModel); + auto *searchModel = new CardSearchModel(displayModel, this); + + auto *proxyModel = new CardCompleterProxyModel(this); + proxyModel->setSourceModel(searchModel); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setFilterRole(Qt::DisplayRole); + + auto *completer = new QCompleter(proxyModel, this); + completer->setCompletionRole(Qt::DisplayRole); + completer->setCompletionMode(QCompleter::PopupCompletion); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setFilterMode(Qt::MatchContains); + completer->setMaxVisibleItems(10); + + cardsField->setCompleter(completer); + commandersField->setCompleter(completer); + + connect(cardsField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults); + + connect(cardsField, &QLineEdit::textChanged, this, [=](const QString &text) { + QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; + proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + if (!text.isEmpty()) + completer->complete(); + }); + + connect(commandersField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults); + + connect(commandersField, &QLineEdit::textChanged, this, [=](const QString &text) { + QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; + proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + if (!text.isEmpty()) + completer->complete(); + }); + + // Tag Name + deckTagNameField = new QLineEdit(navigationContainer); + deckTagNameField->setPlaceholderText("Deck tag"); + + connect(deckTagNameField, &QLineEdit::textChanged, this, &TabArchidekt::doSearch); + + // Search button + searchPushButton = new QPushButton(navigationContainer); + searchPushButton->setText("Search"); + + connect(searchPushButton, &QPushButton::clicked, this, &TabArchidekt::doSearch); + + // Card Size settings + settingsButton = new SettingsButtonWidget(this); + cardSizeSlider = new CardSizeWidget(this, nullptr, SettingsCache::instance().getArchidektPreviewSize()); + connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), + &SettingsCache::setArchidektPreviewCardSize); + settingsButton->addSettingsWidget(cardSizeSlider); + + // Min deck size + minDeckSizeLabel = new QLabel(navigationContainer); + + minDeckSizeSpin = new QSpinBox(navigationContainer); + minDeckSizeSpin->setSpecialValueText(tr("Disabled")); + minDeckSizeSpin->setRange(0, 200); + minDeckSizeSpin->setValue(0); + + // Size logic + minDeckSizeLogicCombo = new QComboBox(navigationContainer); + minDeckSizeLogicCombo->addItems({"Exact", "≥", "≤"}); // Exact = unset, ≥ = GTE, ≤ = LTE + minDeckSizeLogicCombo->setCurrentIndex(1); // default GTE + + connect(minDeckSizeSpin, qOverload(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); + connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + + // Page number + pageLabel = new QLabel(navigationContainer); + + pageSpin = new QSpinBox(navigationContainer); + pageSpin->setRange(1, 9999); + pageSpin->setValue(1); + + connect(pageSpin, qOverload(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); + + // Page display + currentPageDisplay = new QWidget(container); + currentPageLayout = new QVBoxLayout(currentPageDisplay); + currentPageLayout->setContentsMargins(0, 0, 0, 0); + currentPageDisplay->setLayout(currentPageLayout); + + // Layout composition + + // Sort section + navigationLayout->addWidget(orderByCombo); + navigationLayout->addWidget(orderDirButton); + + // Colors section + navigationLayout->addLayout(colorLayout); + navigationLayout->addWidget(logicalAndCheck); + + // Formats section + navigationLayout->addWidget(formatSettingsWidget); + navigationLayout->addWidget(formatLabel); + + // EDH Bracket + navigationLayout->addWidget(edhBracketCombo); + + // Packages toggle + navigationLayout->addWidget(packagesCheck); + + // Deck name + navigationLayout->addWidget(nameField); + + // Owner name + navigationLayout->addWidget(ownerField); + + // Contained cards + navigationLayout->addWidget(cardsField); + + // Commanders + navigationLayout->addWidget(commandersField); + + // Deck tag + navigationLayout->addWidget(deckTagNameField); + + // Search button + navigationLayout->addWidget(searchPushButton); + + // Card size settings + navigationLayout->addWidget(settingsButton); + + // Min. # of cards in deck + navigationLayout->addWidget(minDeckSizeLabel); + navigationLayout->addWidget(minDeckSizeSpin); + navigationLayout->addWidget(minDeckSizeLogicCombo); + + // Page number + navigationLayout->addWidget(pageLabel); + navigationLayout->addWidget(pageSpin); + + mainLayout->addWidget(navigationContainer); + mainLayout->addWidget(currentPageDisplay); + + // Ensure navigation stays at the top and currentPageDisplay takes remaining space + mainLayout->setStretch(0, 0); // navigationContainer gets minimum space + mainLayout->setStretch(1, 1); // currentPageDisplay expands as much as possible + + setCentralWidget(container); + + TabArchidekt::retranslateUi(); + + getTopDecks(); +} + +void TabArchidekt::retranslateUi() +{ + searchPushButton->setText(tr("Search")); + formatLabel->setText(tr("Formats")); + minDeckSizeLabel->setText(tr("Min. # of Cards:")); + pageLabel->setText(tr("Page:")); +} + +QString TabArchidekt::buildSearchUrl() +{ + QUrlQuery query; + + // orderBy (field + direction) + { + QString field = orderByCombo->currentText(); + if (!field.isEmpty()) { + bool desc = orderDirButton->isChecked(); + QString final = desc ? "-" + field : field; + query.addQueryItem("orderBy", final); + } + } + + // Colors + QStringList selectedColors; + for (QChar c : activeColors) { + selectedColors.append(c); + } + if (!selectedColors.isEmpty()) { + query.addQueryItem("colors", selectedColors.join(",")); + } + + // logicalAnd + if (logicalAndCheck->isChecked()) { + query.addQueryItem("logicalAnd", "true"); + } + + // Formats + if (!packagesCheck->isChecked()) { + QStringList formatIds; + for (int i = 0; i < formatChecks.size(); ++i) + if (formatChecks[i]->isChecked()) { + formatIds << QString::number(i + 1); + } + + if (!formatIds.isEmpty()) { + query.addQueryItem("deckFormat", formatIds.join(",")); + } + } + + // edhBracket + if (!packagesCheck->isChecked()) { + if (!edhBracketCombo->currentText().isEmpty()) { + if (edhBracketCombo->currentText() != tr("Any Bracket")) { + query.addQueryItem("edhBracket", edhBracketCombo->currentText()); + } + } + } + + // Search for card packages instead of decks + if (packagesCheck->isChecked()) { + query.addQueryItem("packages", "true"); + } + + // Name + if (!nameField->text().isEmpty()) { + query.addQueryItem("name", nameField->text()); + } + + // owner + if (!ownerField->text().isEmpty()) { + query.addQueryItem("ownerUsername", ownerField->text()); + } + + // cards + if (!cardsField->text().isEmpty()) { + query.addQueryItem("cardName", cardsField->text()); + } + + // Commander Name + if (!packagesCheck->isChecked()) { + if (!commandersField->text().isEmpty()) { + query.addQueryItem("commanderName", commandersField->text()); + } + } + + // deckTagName + if (!packagesCheck->isChecked()) { + if (!deckTagNameField->text().isEmpty()) { + query.addQueryItem("deckTagName", deckTagNameField->text()); + } + } + + // page number + if (pageSpin->value() <= 1) { + query.addQueryItem("page", QString::number(pageSpin->value())); + } + + // Min deck size + if (minDeckSizeSpin->value() != 0) { + query.addQueryItem("size", QString::number(minDeckSizeSpin->value())); + + QString logic = "GTE"; // default + QString selected = minDeckSizeLogicCombo->currentText(); + if (selected == "≥") + logic = "GTE"; + else if (selected == "≤") + logic = "LTE"; + else + logic = ""; // Exact = unset + + if (!logic.isEmpty()) { + query.addQueryItem("sizeLogic", logic); + } + } + + // build final URL + QUrl url("https://archidekt.com/api/decks/v3/"); + url.setQuery(query); + + return url.toString(); +} + +void TabArchidekt::doSearch() +{ + searchDebounceTimer->start(); +} + +void TabArchidekt::doSearchImmediate() +{ + QString url = buildSearchUrl(); + QNetworkRequest req{QUrl(url)}; + req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + networkManager->get(req); +} + +void TabArchidekt::actNavigatePage(QString url) +{ + QNetworkRequest request{QUrl(url)}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + networkManager->get(request); +} + +void TabArchidekt::getTopDecks() +{ + QNetworkRequest request{QUrl(buildSearchUrl())}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + networkManager->get(request); +} + +void TabArchidekt::processApiJson(QNetworkReply *reply) +{ + if (reply->error() != QNetworkReply::NoError) { + qDebug() << "Network error occurred:" << reply->errorString(); + reply->deleteLater(); + return; + } + + QByteArray responseData = reply->readAll(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData); + + if (!jsonDoc.isObject()) { + qDebug() << "Invalid JSON response received."; + reply->deleteLater(); + return; + } + + QJsonObject jsonObj = jsonDoc.object(); + + // Get the actual URL from the reply + QString responseUrl = reply->url().toString(); + + // Check if the response URL matches a commander request + if (responseUrl.startsWith("https://archidekt.com/api/decks/v3/")) { + processTopDecksResponse(jsonObj); + } else if (responseUrl.startsWith("https://archidekt.com/api/decks/")) { + processDeckResponse(jsonObj); + } else { + prettyPrintJson(jsonObj, 4); + } + + reply->deleteLater(); +} + +void TabArchidekt::processTopDecksResponse(QJsonObject reply) +{ + ArchidektDeckListingApiResponse deckData; + deckData.fromJson(reply); + + // **Remove previous page display to prevent stacking** + if (currentPageDisplay) { + mainLayout->removeWidget(currentPageDisplay); + delete currentPageDisplay; + currentPageDisplay = nullptr; + } + + // **Create new currentPageDisplay** + currentPageDisplay = new QWidget(container); + currentPageLayout = new QVBoxLayout(currentPageDisplay); + currentPageDisplay->setLayout(currentPageLayout); + + auto display = new ArchidektApiResponseDeckListingsDisplayWidget(currentPageDisplay, deckData, cardSizeSlider); + connect(display, &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation, this, + &TabArchidekt::actNavigatePage); + currentPageLayout->addWidget(display); + + mainLayout->addWidget(currentPageDisplay); + + // **Ensure layout stays correct** + mainLayout->setStretch(0, 0); // Keep navigationContainer at the top + mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space +} + +void TabArchidekt::processDeckResponse(QJsonObject reply) +{ + ArchidektApiResponseDeck deckData; + deckData.fromJson(reply); + + // **Remove previous page display to prevent stacking** + if (currentPageDisplay) { + mainLayout->removeWidget(currentPageDisplay); + delete currentPageDisplay; + currentPageDisplay = nullptr; + } + + // **Create new currentPageDisplay** + currentPageDisplay = new QWidget(container); + currentPageLayout = new QVBoxLayout(currentPageDisplay); + currentPageDisplay->setLayout(currentPageLayout); + + auto display = new ArchidektApiResponseDeckDisplayWidget(currentPageDisplay, deckData, cardSizeSlider); + connect(display, &ArchidektApiResponseDeckDisplayWidget::requestNavigation, this, &TabArchidekt::actNavigatePage); + connect(display, &ArchidektApiResponseDeckDisplayWidget::openInDeckEditor, tabSupervisor, + &TabSupervisor::openDeckInNewTab); + currentPageLayout->addWidget(display); + + mainLayout->addWidget(currentPageDisplay); + + // **Ensure layout stays correct** + mainLayout->setStretch(0, 0); // Keep navigationContainer at the top + mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space +} + +void TabArchidekt::prettyPrintJson(const QJsonValue &value, int indentLevel) +{ + const QString indent(indentLevel * 2, ' '); // Adjust spacing as needed for pretty printing + + if (value.isObject()) { + QJsonObject obj = value.toObject(); + for (auto it = obj.begin(); it != obj.end(); ++it) { + qDebug().noquote() << indent + it.key() + ":"; + prettyPrintJson(it.value(), indentLevel + 1); + } + } else if (value.isArray()) { + QJsonArray array = value.toArray(); + for (int i = 0; i < array.size(); ++i) { + qDebug().noquote() << indent + QString("[%1]:").arg(i); + prettyPrintJson(array[i], indentLevel + 1); + } + } else if (value.isString()) { + qDebug().noquote() << indent + "\"" + value.toString() + "\""; + } else if (value.isDouble()) { + qDebug().noquote() << indent + QString::number(value.toDouble()); + } else if (value.isBool()) { + qDebug().noquote() << indent + (value.toBool() ? "true" : "false"); + } else if (value.isNull()) { + qDebug().noquote() << indent + "null"; + } +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h new file mode 100644 index 000000000..68a5b78c9 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h @@ -0,0 +1,250 @@ +#ifndef COCKATRICE_TAB_ARCHIDEKT_H +#define COCKATRICE_TAB_ARCHIDEKT_H + +#include "../../interface/widgets/cards/card_size_widget.h" +#include "../../interface/widgets/quick_settings/settings_button_widget.h" +#include "../../tab.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** Base API link for Archidekt deck search */ +inline QString archidektApiLink = "https://archidekt.com/api/decks/v3/?name="; + +/** + * @brief Tab for browsing, searching, and filtering Archidekt decks. + * + * This class provides a comprehensive interface for querying decks from the Archidekt API. + * Users can filter decks by name, owner, included cards, commanders, deck tags, colors, EDH bracket, + * and formats. It also provides sorting and pagination, as well as a card size adjustment widget. + */ +class TabArchidekt : public Tab +{ + Q_OBJECT +public: + /** + * @brief Construct a new TabArchidekt object + * @param _tabSupervisor Parent tab supervisor responsible for tab management and callbacks + * + * Initializes the network manager, creates all UI components, sets up layouts, + * connects signals and slots, and triggers an initial fetch of top decks. + */ + explicit TabArchidekt(TabSupervisor *_tabSupervisor); + + /** + * @brief Update all UI text to reflect the current language or translation + * + * This function re-applies translations to all labels, buttons, and placeholders. + * It should be called after a language change. + */ + void retranslateUi() override; + + /** + * @brief Construct the search URL from all current filters + * @return QString Fully constructed URL including all query parameters + * + * The search URL is dynamically built using the state of all filter widgets. + * Parameters included: + * - Deck name + * - Owner + * - Included cards + * - Commander cards + * - Deck tag + * - Colors and logical AND requirement + * - Formats + * - EDH bracket + * - Packages toggle + * - Sorting field and direction + * - Minimum amount of cards in the deck + * - Pagination (page) + */ + QString buildSearchUrl(); + + /** + * @brief Retrieve the tab display text + * @return QString Human-readable title for the tab + * + * If a card is pre-selected (cardToQuery), its name is appended to the tab title. + */ + QString getTabText() const override + { + auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName(); + return tr("Archidekt: ") + cardName; + } + + /** + * @brief Get the card size slider widget + * @return CardSizeWidget* Pointer to the card size adjustment slider + * + * Allows external code to read or manipulate the current card size or hook up the sliders signals. + */ + CardSizeWidget *getCardSizeSlider() + { + return cardSizeSlider; + } + + /** @brief Network manager for handling API requests */ + QNetworkAccessManager *networkManager; + +public slots: + /** + * @brief Trigger a search using the current filters + * + * Sends a network request to the Archidekt API using the URL generated by buildSearchUrl(). + * Updates the current page display with results asynchronously. + */ + void doSearch(); + void doSearchImmediate(); + /** + * @brief Process a network reply containing JSON data + * @param reply QNetworkReply object with the API response + * + * Determines whether the response corresponds to a top decks query or a single deck, + * and dispatches it to the appropriate handler. + */ + void processApiJson(QNetworkReply *reply); + + /** + * @brief Handle a JSON response containing multiple decks + * @param reply QJsonObject containing top deck listings + * + * Clears the previous page display and creates a new display widget for the results. + */ + void processTopDecksResponse(QJsonObject reply); + + /** + * @brief Handle a JSON response for a single deck + * @param reply QJsonObject containing deck data + * + * Clears the previous page display and creates a new display widget for the deck details. + */ + void processDeckResponse(QJsonObject reply); + + /** + * @brief Pretty-print a QJsonValue for debugging + * @param value The JSON value to print + * @param indentLevel The indentation depth (number of levels) + */ + void prettyPrintJson(const QJsonValue &value, int indentLevel); + + /** + * @brief Navigate to a specified page URL + * @param url The URL to request + * + * Typically called when a navigation button is clicked in a deck listing. + */ + void actNavigatePage(QString url); + + /** + * @brief Fetch top decks from the Archidekt API + * + * Called on initialization to populate the initial page display. + */ + void getTopDecks(); + +private: + QTimer *searchDebounceTimer; ///< Timer to debounce search requests by spin-boxes etc. + + // --------------------------------------------------------------------- + // Layout Containers + // --------------------------------------------------------------------- + + QWidget *container; ///< Root container for the entire tab + QVBoxLayout *mainLayout; ///< Outer vertical layout containing navigation and page display + QWidget *navigationContainer; ///< Container for all navigation/filter controls + QHBoxLayout *navigationLayout; ///< Layout for horizontal arrangement of filter widgets + QWidget *currentPageDisplay; ///< Widget containing the currently displayed deck(s) + QVBoxLayout *currentPageLayout; ///< Layout for deck display widgets + + // --------------------------------------------------------------------- + // Sorting Controls + // --------------------------------------------------------------------- + + QComboBox *orderByCombo; ///< Dropdown for selecting the sort field + QPushButton *orderDirButton; ///< Toggle button for ascending/descending sort + + // --------------------------------------------------------------------- + // Color Filters + // --------------------------------------------------------------------- + + QSet activeColors; ///< Set of currently active mana colors + QCheckBox *logicalAndCheck; ///< Require ALL selected colors instead of ANY + + // --------------------------------------------------------------------- + // Format Filters + // --------------------------------------------------------------------- + + QLabel *formatLabel; ///< Label displaying "Formats" + SettingsButtonWidget *formatSettingsWidget; ///< Collapsible widget containing format checkboxes + QVector formatChecks; ///< Individual checkboxes for each format + + // --------------------------------------------------------------------- + // EDH Bracket / Package Toggle + // --------------------------------------------------------------------- + + QComboBox *edhBracketCombo; ///< Dropdown for EDH bracket selection + QCheckBox *packagesCheck; ///< Toggle for searching card packages instead of full decks + + // --------------------------------------------------------------------- + // Basic Search Fields + // --------------------------------------------------------------------- + + QLineEdit *nameField; ///< Input for deck name filter + QLineEdit *ownerField; ///< Input for owner name filter + + // --------------------------------------------------------------------- + // Card Filters + // --------------------------------------------------------------------- + + QLineEdit *cardsField; ///< Input for cards included in the deck (comma-separated) + QLineEdit *commandersField; ///< Input for commander cards (comma-separated) + + // --------------------------------------------------------------------- + // Deck Tag + // --------------------------------------------------------------------- + + QLineEdit *deckTagNameField; ///< Input for deck tag filtering + + // --------------------------------------------------------------------- + // Search Trigger + // --------------------------------------------------------------------- + + QPushButton *searchPushButton; ///< Button to trigger the search manually + + // --------------------------------------------------------------------- + // UI Settings (Card Size) + // --------------------------------------------------------------------- + + SettingsButtonWidget *settingsButton; ///< Container for additional UI settings + CardSizeWidget *cardSizeSlider; ///< Slider to adjust card size in results + + // --------------------------------------------------------------------- + // Minimum Cards in Deck + // --------------------------------------------------------------------- + + QLabel *minDeckSizeLabel; ///< Label for minimum number of cards per deck + QSpinBox *minDeckSizeSpin; ///< Spinner to select minimum deck size + QComboBox *minDeckSizeLogicCombo; ///< Combo box for the size logic to apply + + // --------------------------------------------------------------------- + // Pagination + // --------------------------------------------------------------------- + + QLabel *pageLabel; ///< Label for current page selection + QSpinBox *pageSpin; ///< Spinner to select the page number for results + + // --------------------------------------------------------------------- + // Optional Context + // --------------------------------------------------------------------- + + CardInfoPtr cardToQuery; ///< Optional pre-selected card for initial filtering +}; + +#endif // COCKATRICE_TAB_ARCHIDEKT_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp index f071949f2..145a3fca0 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp @@ -2,7 +2,6 @@ #include #include -#include #include void EdhrecApiResponseArchidektLink::fromJson(const QJsonObject &json) diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h index 236e36305..d265c164d 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h @@ -1,7 +1,6 @@ #ifndef ARCHIDEKTENTRY_H #define ARCHIDEKTENTRY_H -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h index 1a60b95d0..70d37cbae 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h @@ -6,7 +6,6 @@ #include "../commander/edhrec_commander_api_response_average_deck_statistics.h" #include "edhrec_deck_api_response.h" -#include #include #include 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 73c26050d..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 @@ -6,7 +6,6 @@ #include #include #include -#include void EdhrecDeckApiResponse::fromJson(const QJsonArray &json) { @@ -15,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 b362819df..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 @@ -9,12 +9,6 @@ #include "../../../../../../deck_loader/deck_loader.h" -#include -#include -#include -#include -#include - class EdhrecDeckApiResponse { public: @@ -27,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/api_response/cards/edhrec_api_response_card_container.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h index d23c4f27d..07b53dd97 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h @@ -10,8 +10,6 @@ #include "edhrec_api_response_card_list.h" #include "edhrec_commander_api_response_commander_details.h" -#include -#include #include #include #include @@ -29,27 +27,27 @@ public: void debugPrint() const; // Getter methods for deck container - const QString &getDescription() const + [[nodiscard]] const QString &getDescription() const { return description; } - const QVector &getBreadcrumb() const + [[nodiscard]] const QVector &getBreadcrumb() const { return breadcrumb; } - const EdhrecCommanderApiResponseCommanderDetails &getCommanderDetails() const + [[nodiscard]] const EdhrecCommanderApiResponseCommanderDetails &getCommanderDetails() const { return card; } - const QVector &getCardlists() const + [[nodiscard]] const QVector &getCardlists() const { return cardlists; } - const QString &getKeywords() const + [[nodiscard]] const QString &getKeywords() const { return keywords; } - const QString &getTitle() const + [[nodiscard]] const QString &getTitle() const { return title; } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h index 068945a30..15691fbe4 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h @@ -10,7 +10,6 @@ #include "edhrec_api_response_card_details.h" #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h index 64b5970da..629eb8273 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h @@ -10,7 +10,6 @@ #include "../card_prices/edhrec_api_response_card_prices.h" #include -#include #include #include @@ -27,119 +26,119 @@ public: void debugPrint() const; // Getters for the card data - const QString &getAetherhubUri() const + [[nodiscard]] const QString &getAetherhubUri() const { return aetherhubUri; } - const QString &getArchidektUri() const + [[nodiscard]] const QString &getArchidektUri() const { return archidektUri; } - int getCmc() const + [[nodiscard]] int getCmc() const { return cmc; } - const QJsonArray &getColorIdentity() const + [[nodiscard]] const QJsonArray &getColorIdentity() const { return colorIdentity; } - bool isCombos() const + [[nodiscard]] bool isCombos() const { return combos; } - const QString &getDeckstatsUri() const + [[nodiscard]] const QString &getDeckstatsUri() const { return deckstatsUri; } - const QVector &getImageUris() const + [[nodiscard]] const QVector &getImageUris() const { return imageUris; } - int getInclusion() const + [[nodiscard]] int getInclusion() const { return inclusion; } - bool getIsCommander() const + [[nodiscard]] bool getIsCommander() const { return isCommander; } - const QString &getLabel() const + [[nodiscard]] const QString &getLabel() const { return label; } - const QString &getLayout() const + [[nodiscard]] const QString &getLayout() const { return layout; } - bool getLegalCommander() const + [[nodiscard]] bool getLegalCommander() const { return legalCommander; } - const QString &getMoxfieldUri() const + [[nodiscard]] const QString &getMoxfieldUri() const { return moxfieldUri; } - const QString &getMtggoldfishUri() const + [[nodiscard]] const QString &getMtggoldfishUri() const { return mtggoldfishUri; } - const QString &getName() const + [[nodiscard]] const QString &getName() const { return name; } - const QJsonArray &getNames() const + [[nodiscard]] const QJsonArray &getNames() const { return names; } - int getNumDecks() const + [[nodiscard]] int getNumDecks() const { return numDecks; } - int getPotentialDecks() const + [[nodiscard]] int getPotentialDecks() const { return potentialDecks; } - const QString &getPrecon() const + [[nodiscard]] const QString &getPrecon() const { return precon; } - const CardPrices &getPrices() const + [[nodiscard]] const CardPrices &getPrices() const { return prices; } - const QString &getPrimaryType() const + [[nodiscard]] const QString &getPrimaryType() const { return primaryType; } - const QString &getRarity() const + [[nodiscard]] const QString &getRarity() const { return rarity; } - double getSalt() const + [[nodiscard]] double getSalt() const { return salt; } - const QString &getSanitized() const + [[nodiscard]] const QString &getSanitized() const { return sanitized; } - const QString &getSanitizedWo() const + [[nodiscard]] const QString &getSanitizedWo() const { return sanitizedWo; } - const QString &getScryfallUri() const + [[nodiscard]] const QString &getScryfallUri() const { return scryfallUri; } - const QString &getSpellbookUri() const + [[nodiscard]] const QString &getSpellbookUri() const { return spellbookUri; } - const QString &getType() const + [[nodiscard]] const QString &getType() const { return type; } - const QString &getUrl() const + [[nodiscard]] const QString &getUrl() const { return url; } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h index 9e6629142..64f2fd7cd 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h @@ -5,7 +5,6 @@ #include "../cards/edhrec_api_response_card_container.h" #include "edhrec_commander_api_response_average_deck_statistics.h" -#include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h index a9a6316e3..fb89c0b12 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h @@ -9,7 +9,6 @@ #include "../cards/edhrec_api_response_card_container.h" -#include #include class EdhrecTopCardsApiResponse diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h index 1e99e90f0..26c5afedb 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h @@ -9,7 +9,6 @@ #include "../cards/edhrec_api_response_card_container.h" -#include #include class EdhrecTopCommandersApiResponse diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h index 382e2899e..48219893a 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h @@ -9,7 +9,6 @@ #include "../cards/edhrec_api_response_card_container.h" -#include #include class EdhrecTopTagsApiResponse 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 108694d8d..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 @@ -1,5 +1,6 @@ #include "edhrec_api_response_card_details_display_widget.h" +#include "../../../../../general/display/background_plate_widget.h" #include "../../tab_edhrec_main.h" #include @@ -18,15 +19,21 @@ 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); - layout->addWidget(inclusionDisplayWidget); - layout->addWidget(synergyDisplayWidget); + backgroundPlateWidget = new BackgroundPlateWidget(this); + auto plateLayout = new QVBoxLayout(backgroundPlateWidget); + + plateLayout->addWidget(nameLabel); + plateLayout->addWidget(cardPictureWidget); + plateLayout->addWidget(inclusionDisplayWidget); + plateLayout->addWidget(synergyDisplayWidget); + + layout->addWidget(backgroundPlateWidget); QWidget *currentParent = parentWidget(); TabEdhRecMain *parentTab = nullptr; @@ -49,6 +56,28 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi } } +void EdhrecApiResponseCardDetailsDisplayWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + actRequestPageNavigation(); +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void EdhrecApiResponseCardDetailsDisplayWidget::enterEvent(QEnterEvent *event) +#else +void EdhrecApiResponseCardDetailsDisplayWidget::enterEvent(QEvent *event) +#endif +{ + QWidget::enterEvent(event); + backgroundPlateWidget->setFocused(true); +} + +void EdhrecApiResponseCardDetailsDisplayWidget::leaveEvent(QEvent *event) +{ + QWidget::leaveEvent(event); + backgroundPlateWidget->setFocused(false); +} + void EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation() { emit requestUrl(toDisplay.url); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h index 548cdc0af..82dcfb8b0 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h @@ -8,6 +8,7 @@ #define EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H #include "../../../../../cards/card_info_picture_widget.h" +#include "../../../../../general/display/background_plate_widget.h" #include "../../api_response/cards/edhrec_api_response_card_details.h" #include "edhrec_api_response_card_inclusion_display_widget.h" #include "edhrec_api_response_card_synergy_display_widget.h" @@ -30,9 +31,20 @@ private: EdhrecApiResponseCardDetails toDisplay; QVBoxLayout *layout; CardInfoPictureWidget *cardPictureWidget; + BackgroundPlateWidget *backgroundPlateWidget; ///< Plate for metadata labels QLabel *nameLabel; EdhrecApiResponseCardInclusionDisplayWidget *inclusionDisplayWidget; EdhrecApiResponseCardSynergyDisplayWidget *synergyDisplayWidget; + +protected slots: + void mousePressEvent(QMouseEvent *event) override; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void enterEvent(QEnterEvent *event) override; ///< Qt6 hover enter +#else + void enterEvent(QEvent *event) override; ///< Qt5 hover enter +#endif + void leaveEvent(QEvent *event) override; }; #endif // EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h index 0ebb9b927..d34c3da34 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h @@ -24,7 +24,7 @@ public: [[nodiscard]] QString getBannerText() const { return header->getText(); - }; + } private: QVBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index 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/display/top_tags/edhrec_top_tags_api_response_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h index 84b401eb7..64228b85d 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h @@ -9,7 +9,6 @@ #include "../../api_response/top_tags/edhrec_top_tags_api_response.h" -#include #include #include #include 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 4c740e5d1..10389f4eb 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp @@ -4,7 +4,6 @@ #include "display/commander/edhrec_commander_api_response_display_widget.h" #include -#include #include #include #include @@ -16,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 0dcd5d0ae..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 @@ -22,10 +22,10 @@ #include #include #include -#include #include #include #include +#include static bool canBeCommander(const CardInfoPtr &cardInfo) { @@ -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 *))); @@ -167,6 +164,7 @@ void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander) } QNetworkRequest request{QUrl(url)}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } @@ -174,6 +172,7 @@ void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander) void TabEdhRecMain::actNavigatePage(QString url) { QNetworkRequest request{QUrl("https://json.edhrec.com/pages" + url + ".json")}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } @@ -181,6 +180,7 @@ void TabEdhRecMain::actNavigatePage(QString url) void TabEdhRecMain::getTopCards() { QNetworkRequest request{QUrl("https://json.edhrec.com/pages/top/year.json")}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } @@ -188,6 +188,7 @@ void TabEdhRecMain::getTopCards() void TabEdhRecMain::getTopCommanders() { QNetworkRequest request{QUrl("https://json.edhrec.com/pages/commanders/year.json")}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } @@ -195,7 +196,7 @@ void TabEdhRecMain::getTopCommanders() void TabEdhRecMain::getTopTags() { QNetworkRequest request{QUrl("https://json.edhrec.com/pages/tags.json")}; - + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } @@ -359,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/api/edhrec/tab_edhrec_main.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h index 2cca18504..04cf03fc1 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h @@ -8,7 +8,6 @@ #define TAB_EDHREC_MAIN_H #include "../../interface/widgets/cards/card_size_widget.h" -#include "../../interface/widgets/general/layout_containers/flow_widget.h" #include "../../interface/widgets/quick_settings/settings_button_widget.h" #include "../../tab.h" #include "display/commander/edhrec_commander_api_response_display_widget.h" @@ -27,13 +26,13 @@ public: void retranslateUi() override; void doSearch(); - QString getTabText() const override + [[nodiscard]] QString getTabText() const override { auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName(); return tr("EDHRec: ") + cardName; } - CardSizeWidget *getCardSizeSlider() + CardSizeWidget *getCardSizeSlider() const { return cardSizeSlider; } diff --git a/cockatrice/src/interface/widgets/tabs/tab.h b/cockatrice/src/interface/widgets/tabs/tab.h index 56a9f0b7e..7cff01130 100644 --- a/cockatrice/src/interface/widgets/tabs/tab.h +++ b/cockatrice/src/interface/widgets/tabs/tab.h @@ -39,15 +39,15 @@ private: public: explicit Tab(TabSupervisor *_tabSupervisor); - const QList &getTabMenus() const + [[nodiscard]] const QList &getTabMenus() const { return tabMenus; } - TabSupervisor *getTabSupervisor() const + [[nodiscard]] TabSupervisor *getTabSupervisor() const { return tabSupervisor; } - bool getContentsChanged() const + [[nodiscard]] bool getContentsChanged() const { return contentsChanged; } @@ -55,7 +55,7 @@ public: { contentsChanged = _contentsChanged; } - virtual QString getTabText() const = 0; + [[nodiscard]] virtual QString getTabText() const = 0; virtual void retranslateUi() = 0; /** diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 88f08eae1..4e3d0c57d 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -1,39 +1,22 @@ #include "tab_deck_editor.h" #include "../../../client/settings/cache_settings.h" -#include "../client/network/interfaces/tapped_out_interface.h" #include "../filters/filter_builder.h" -#include "../filters/filter_tree_model.h" #include "../interface/pixel_map_generator.h" #include "../interface/widgets/cards/card_info_frame_widget.h" #include "../interface/widgets/deck_editor/deck_editor_filter_dock_widget.h" -#include "../interface/widgets/dialogs/dlg_load_deck.h" -#include "../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../interface/widgets/menus/deck_editor_menu.h" #include "tab_supervisor.h" #include -#include #include -#include #include #include #include -#include #include -#include -#include #include -#include -#include #include -#include -#include #include -#include -#include -#include -#include #include #include #include @@ -175,8 +158,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 b483f0616..73b3fc0ac 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -243,10 +242,10 @@ void TabDeckStorage::actOpenLocalDeck() QString filePath = localDirModel->filePath(curLeft); auto deckLoader = new DeckLoader(this); - if (!deckLoader->loadFromFile(filePath, DeckLoader::CockatriceFormat, true)) + if (!deckLoader->loadFromFile(filePath, DeckFileFormat::Cockatrice, true)) continue; - emit openDeckEditor(deckLoader); + emit openDeckEditor(deckLoader->getDeck()); } } @@ -308,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, DeckLoader::CockatriceFormat)) { + 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:"), @@ -323,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; @@ -437,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() @@ -493,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, DeckLoader::CockatriceFormat); + 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 8f187575c..5b6c8d52a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -13,11 +13,9 @@ #include "../game/player/player.h" #include "../game/player/player_list_widget.h" #include "../game/replay.h" -#include "../game/zones/card_zone.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/widgets/cards/card_info_frame_widget.h" #include "../interface/widgets/dialogs/dlg_create_game.h" -#include "../interface/widgets/replay/replay_timeline_widget.h" #include "../interface/widgets/server/user/user_list_manager.h" #include "../interface/widgets/utility/line_edit_completer.h" #include "../interface/window_main.h" @@ -28,15 +26,12 @@ #include #include #include -#include #include #include #include #include -#include #include #include -#include #include #include #include @@ -754,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 e500fefbd..27374ef2a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -14,13 +14,11 @@ #include "../game/player/player.h" #include "../interface/widgets/menus/tearoff_menu.h" #include "../interface/widgets/replay/replay_manager.h" -#include "../interface/widgets/visual_deck_storage/visual_deck_storage_widget.h" #include "tab.h" #include #include #include -#include class ServerInfo_PlayerProperties; class TabbedDeckViewContainer; @@ -47,7 +45,6 @@ class ReplayTimelineWidget; class CardZone; class AbstractCardItem; class CardItem; -class DeckLoader; class QVBoxLayout; class QHBoxLayout; class GameReplay; @@ -121,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_home.cpp b/cockatrice/src/interface/widgets/tabs/tab_home.cpp index a44945315..7a79b1785 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_home.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_home.cpp @@ -1,8 +1,5 @@ #include "tab_home.h" -#include -#include - TabHome::TabHome(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client) { homeWidget = new HomeWidget(this, tabSupervisor); diff --git a/cockatrice/src/interface/widgets/tabs/tab_home.h b/cockatrice/src/interface/widgets/tabs/tab_home.h index 3738b73fd..c40dfc269 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_home.h +++ b/cockatrice/src/interface/widgets/tabs/tab_home.h @@ -10,9 +10,7 @@ #include "../interface/widgets/general/home_widget.h" #include "tab.h" -#include #include -#include class AbstractClient; @@ -26,7 +24,7 @@ private: public: TabHome(TabSupervisor *_tabSupervisor, AbstractClient *_client); void retranslateUi() override; - QString getTabText() const override + [[nodiscard]] QString getTabText() const override { return tr("Home"); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp index 86e10b755..e4f699160 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp @@ -1,20 +1,18 @@ #include "tab_logs.h" -#include "../interface/widgets/dialogs/dlg_manage_sets.h" #include "../interface/widgets/utility/custom_line_edit.h" #include -#include +#include +#include #include -#include #include #include #include #include #include #include -#include -#include +#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.h b/cockatrice/src/interface/widgets/tabs/tab_logs.h index 7cbf6aedc..a73af651b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.h +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.h @@ -13,7 +13,6 @@ class AbstractClient; class LineEditUnfocusable; - class QGroupBox; class QPushButton; class QSpinBox; diff --git a/cockatrice/src/interface/widgets/tabs/tab_message.h b/cockatrice/src/interface/widgets/tabs/tab_message.h index 098089e46..36a0b5f78 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_message.h +++ b/cockatrice/src/interface/widgets/tabs/tab_message.h @@ -54,8 +54,8 @@ public: ~TabMessage() override; void retranslateUi() override; void tabActivated() override; - QString getUserName() const; - QString getTabText() const override; + [[nodiscard]] QString getUserName() const; + [[nodiscard]] QString getTabText() const override; void processUserMessageEvent(const Event_UserMessage &event); diff --git a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp index 8d7081407..2db9e83c5 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.h b/cockatrice/src/interface/widgets/tabs/tab_room.h index c5d0a950a..67d9afc86 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.h +++ b/cockatrice/src/interface/widgets/tabs/tab_room.h @@ -13,7 +13,6 @@ #include #include -#include #include class UserListProxy; diff --git a/cockatrice/src/interface/widgets/tabs/tab_server.cpp b/cockatrice/src/interface/widgets/tabs/tab_server.cpp index bd4fa2be3..aa52b4b1a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_server.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_server.cpp @@ -3,17 +3,12 @@ #include "../interface/widgets/server/user/user_list_widget.h" #include "tab_supervisor.h" -#include -#include #include #include #include -#include #include #include #include -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 902b1f589..b10d615ff 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -5,6 +5,7 @@ #include "../interface/widgets/server/user/user_list_manager.h" #include "../interface/widgets/server/user/user_list_widget.h" #include "../main.h" +#include "api/archidekt/tab_archidekt.h" #include "api/edhrec/tab_edhrec_main.h" #include "tab_account.h" #include "tab_admin.h" @@ -31,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -133,14 +133,17 @@ 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(); }); + aTabArchidekt = new QAction(this); + connect(aTabArchidekt, &QAction::triggered, this, [this] { addArchidektTab(); }); + aTabHome = new QAction(this); aTabHome->setCheckable(true); connect(aTabHome, &QAction::triggered, this, &TabSupervisor::actTabHome); @@ -205,6 +208,7 @@ void TabSupervisor::retranslateUi() aTabDeckEditor->setText(tr("Deck Editor")); aTabVisualDeckEditor->setText(tr("Visual Deck Editor")); aTabEdhRec->setText(tr("EDHRec")); + aTabArchidekt->setText(tr("Archidekt")); aTabHome->setText(tr("Home")); aTabVisualDeckStorage->setText(tr("&Visual Deck Storage")); aTabVisualDatabaseDisplay->setText(tr("Visual Database Display")); @@ -387,6 +391,7 @@ void TabSupervisor::resetTabsMenu() tabsMenu->addAction(aTabDeckEditor); tabsMenu->addAction(aTabVisualDeckEditor); tabsMenu->addAction(aTabEdhRec); + tabsMenu->addAction(aTabArchidekt); tabsMenu->addSeparator(); tabsMenu->addAction(aTabHome); tabsMenu->addAction(aTabVisualDeckStorage); @@ -841,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) { @@ -863,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); @@ -878,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); @@ -900,6 +903,15 @@ TabEdhRecMain *TabSupervisor::addEdhrecMainTab() return tab; } +TabArchidekt *TabSupervisor::addArchidektTab() +{ + auto *tab = new TabArchidekt(this); + + myAddTab(tab); + setCurrentWidget(tab); + return tab; +} + TabVisualDatabaseDisplay *TabSupervisor::addVisualDatabaseDisplayTab() { auto *tab = new TabVisualDatabaseDisplay(this); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 7a3b9cbb9..0c4428f83 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -11,15 +11,14 @@ #include "../../deck_loader/deck_loader.h" #include "../interface/widgets/server/user/user_list_proxy.h" #include "abstract_tab_deck_editor.h" +#include "api/archidekt/tab_archidekt.h" #include "api/edhrec/tab_edhrec.h" #include "api/edhrec/tab_edhrec_main.h" #include "tab_visual_database_display.h" #include "visual_deck_editor/tab_deck_editor_visual.h" -#include "visual_deck_editor/tab_deck_editor_visual_tab_widget.h" #include "visual_deck_storage/tab_deck_storage_visual.h" #include -#include #include #include #include @@ -112,7 +111,7 @@ private: QList deckEditorTabs; bool isLocalGame; - QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabVisualDeckStorage, + QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabArchidekt, *aTabVisualDeckStorage, *aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin, *aTabLog; int myAddTab(Tab *tab, QAction *manager = nullptr); @@ -169,11 +168,12 @@ 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(); TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false); void openReplay(GameReplay *replay); void switchToFirstAvailableNetworkTab(); diff --git a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h index b01f3ab06..66f38fb3d 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h +++ b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h @@ -10,8 +10,6 @@ #include "../interface/widgets/visual_database_display/visual_database_display_widget.h" #include "tab.h" -#include - class TabVisualDatabaseDisplay : public Tab { Q_OBJECT 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 7a811fd28..a124eaa01 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,7 +1,6 @@ #include "tab_deck_editor_visual.h" #include "../../../../client/settings/cache_settings.h" -#include "../../client/network/interfaces/deck_stats_interface.h" #include "../../filters/filter_builder.h" #include "../../interface/pixel_map_generator.h" #include "../../interface/widgets/cards/card_info_frame_widget.h" @@ -12,27 +11,22 @@ #include "tab_deck_editor_visual_tab_widget.h" #include -#include #include #include #include #include -#include #include #include #include -#include #include #include #include #include #include #include -#include #include #include #include -#include /** * @brief Constructs the TabDeckEditorVisual instance. @@ -90,7 +84,7 @@ void TabDeckEditorVisual::onDeckChanged() { AbstractTabDeckEditor::onDeckModified(); tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel(); - tabContainer->deckAnalytics->refreshDisplays(deckDockWidget->deckModel); + tabContainer->deckAnalytics->refreshDisplays(); tabContainer->sampleHandWidget->setDeckModel(deckDockWidget->deckModel); } @@ -197,7 +191,7 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, // Double click = swap if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton) { - actSwapCard(card, zoneName); + deckDockWidget->actSwapCard(card, zoneName); idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); sel->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect); return; @@ -272,8 +266,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 bfb498909..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 @@ -1,12 +1,9 @@ #include "tab_deck_storage_visual.h" -#include "../../interface/widgets/cards/deck_preview_card_picture_widget.h" #include "../../interface/widgets/visual_deck_storage/visual_deck_storage_widget.h" #include "../tab_supervisor.h" #include -#include -#include #include TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) @@ -27,11 +24,11 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath) { - auto deckLoader = new DeckLoader(this); - if (!deckLoader->loadFromFile(filePath, DeckLoader::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/utility/get_text_with_max.cpp b/cockatrice/src/interface/widgets/utility/get_text_with_max.cpp index 73745be02..134352eb3 100644 --- a/cockatrice/src/interface/widgets/utility/get_text_with_max.cpp +++ b/cockatrice/src/interface/widgets/utility/get_text_with_max.cpp @@ -1,5 +1,7 @@ #include "get_text_with_max.h" +#include + QString getTextWithMax(QWidget *parent, const QString &title, const QString &label, diff --git a/cockatrice/src/interface/widgets/utility/get_text_with_max.h b/cockatrice/src/interface/widgets/utility/get_text_with_max.h index 894eb80f2..923d6f427 100644 --- a/cockatrice/src/interface/widgets/utility/get_text_with_max.h +++ b/cockatrice/src/interface/widgets/utility/get_text_with_max.h @@ -7,7 +7,8 @@ #ifndef GETTEXTWITHMAX_H #define GETTEXTWITHMAX_H -#include +#include +#include #include QString getTextWithMax(QWidget *parent, diff --git a/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp b/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp index 7820ad91b..389e69d57 100644 --- a/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp +++ b/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/utility/line_edit_completer.h b/cockatrice/src/interface/widgets/utility/line_edit_completer.h index 23e4d4aba..7b3756e3c 100644 --- a/cockatrice/src/interface/widgets/utility/line_edit_completer.h +++ b/cockatrice/src/interface/widgets/utility/line_edit_completer.h @@ -9,9 +9,7 @@ #include "custom_line_edit.h" -#include #include -#include class LineEditCompleter : public LineEditUnfocusable { diff --git a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp index 7e14d4335..a34f39042 100644 --- a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp +++ b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp @@ -5,7 +5,6 @@ #include #include #include -#include SequenceEdit::SequenceEdit(const QString &_shortcutName, QWidget *parent) : QWidget(parent) { diff --git a/cockatrice/src/interface/widgets/utility/sequence_edit.h b/cockatrice/src/interface/widgets/utility/sequence_edit.h index a146aa752..a5fe1a1c6 100644 --- a/cockatrice/src/interface/widgets/utility/sequence_edit.h +++ b/cockatrice/src/interface/widgets/utility/sequence_edit.h @@ -8,7 +8,6 @@ #define SEQUENCEEDIT_H #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp index d4947d6c5..3ca709071 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp @@ -2,7 +2,6 @@ #include "../cards/additional_info/mana_symbol_widget.h" -#include #include #include diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp index 062774969..44b275afd 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp @@ -5,8 +5,7 @@ #include #include -#include -#include +#include #include VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWidget(QWidget *parent, @@ -19,6 +18,16 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi layout = new QVBoxLayout(this); setLayout(layout); + // Filter search input + searchInput = new QLineEdit(this); + layout->addWidget(searchInput); + + connect(searchInput, &QLineEdit::textChanged, this, &VisualDatabaseDisplayFilterSaveLoadWidget::applySearchFilter); + + // File list container + fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + layout->addWidget(fileListWidget); + // Input for filter filename filenameInput = new QLineEdit(this); layout->addWidget(filenameInput); @@ -26,11 +35,12 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi // Save button saveButton = new QPushButton(this); layout->addWidget(saveButton); - connect(saveButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter); + // Disable save if empty + saveButton->setEnabled(false); + connect(filenameInput, &QLineEdit::textChanged, this, + [this](const QString &text) { saveButton->setEnabled(!text.trimmed().isEmpty()); }); - // File list container - fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); - layout->addWidget(fileListWidget); + connect(saveButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter); refreshFilterList(); // Populate the file list on startup retranslateUi(); @@ -38,6 +48,7 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi void VisualDatabaseDisplayFilterSaveLoadWidget::retranslateUi() { + searchInput->setPlaceholderText(tr("Search filter...")); saveButton->setText(tr("Save Filter")); saveButton->setToolTip(tr("Save all currently applied filters to a file")); filenameInput->setPlaceholderText(tr("Enter filename...")); @@ -113,42 +124,36 @@ void VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter(const QString &filena emit filterModel->layoutChanged(); } +void VisualDatabaseDisplayFilterSaveLoadWidget::applySearchFilter(const QString &text) +{ + fileListWidget->clearLayout(); + + QString filter = text.trimmed(); + QStringList filtered = allFilterFiles; + + if (!filter.isEmpty()) { + filtered = filtered.filter(QRegularExpression(filter, QRegularExpression::CaseInsensitiveOption)); + } + + for (const QString &filename : filtered) { + FilterDisplayWidget *filterWidget = new FilterDisplayWidget(this, filename, filterModel); + fileListWidget->addWidget(filterWidget); + + connect(filterWidget, &FilterDisplayWidget::filterLoadRequested, this, + &VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter); + + connect(filterWidget, &FilterDisplayWidget::filterDeleted, this, + &VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList); + } +} + void VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList() { fileListWidget->clearLayout(); - // Clear existing widgets - for (auto buttonPair : fileButtons) { - buttonPair.first->deleteLater(); - buttonPair.second->deleteLater(); - } - fileButtons.clear(); // Clear the list of buttons + fileButtons.clear(); - // Refresh the filter file list QDir dir(SettingsCache::instance().getFiltersPath()); - QStringList filterFiles = dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name); + allFilterFiles = dir.entryList({"*.json"}, QDir::Files, QDir::Name); - // Loop through the filter files and create widgets for them - for (const QString &filename : filterFiles) { - bool alreadyAdded = false; - - // Check if the widget for this filter file already exists to avoid duplicates - for (const auto &pair : fileButtons) { - if (pair.first->text() == filename) { - alreadyAdded = true; - break; - } - } - - if (!alreadyAdded) { - // Create a new custom widget for the filter - FilterDisplayWidget *filterWidget = new FilterDisplayWidget(this, filename, filterModel); - fileListWidget->addWidget(filterWidget); - - // Connect signals to handle loading and deletion - connect(filterWidget, &FilterDisplayWidget::filterLoadRequested, this, - &VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter); - connect(filterWidget, &FilterDisplayWidget::filterDeleted, this, - &VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList); - } - } + applySearchFilter(searchInput->text()); } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h index 7dea3fd01..bb5921a02 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h @@ -10,10 +10,8 @@ #include "../../../filters/filter_tree_model.h" #include "../general/layout_containers/flow_widget.h" -#include #include #include -#include #include #include #include @@ -29,6 +27,7 @@ public: void saveFilter(); void loadFilter(const QString &filename); + void applySearchFilter(const QString &text); void refreshFilterList(); void deleteFilter(const QString &filename, QPushButton *deleteButton); @@ -39,9 +38,11 @@ private: FilterTreeModel *filterModel; QVBoxLayout *layout; + QLineEdit *searchInput; + FlowWidget *fileListWidget; QLineEdit *filenameInput; QPushButton *saveButton; - FlowWidget *fileListWidget; + QStringList allFilterFiles; QMap> fileButtons; }; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp new file mode 100644 index 000000000..1f1b7b94c --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp @@ -0,0 +1,205 @@ +#include "visual_database_display_format_legality_filter_widget.h" + +#include "../../../filters/filter_tree_model.h" + +#include +#include +#include +#include +#include + +VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLegalityFilterWidget( + QWidget *parent, + FilterTreeModel *_filterModel) + : QWidget(parent), filterModel(_filterModel) +{ + allFormatsWithCount = CardDatabaseManager::query()->getAllFormatsWithCount(); + + setMaximumHeight(75); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + + layout = new QHBoxLayout(this); + setLayout(layout); + layout->setContentsMargins(0, 1, 0, 1); + layout->setSpacing(1); + layout->setAlignment(Qt::AlignTop); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + layout->addWidget(flowWidget); + + // Create the spinbox + spinBox = new QSpinBox(this); + spinBox->setMinimum(1); + spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically + spinBox->setValue(150); + layout->addWidget(spinBox); + connect(spinBox, qOverload(&QSpinBox::valueChanged), this, + &VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility); + + // Create the toggle button for Exact Match/Includes mode + toggleButton = new QPushButton(this); + toggleButton->setCheckable(true); + layout->addWidget(toggleButton); + connect(toggleButton, &QPushButton::toggled, this, + &VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode); + connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { + QTimer::singleShot(100, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel); + }); + + createFormatButtons(); // Populate buttons initially + updateFilterMode(false); // Initialize toggle button text + + retranslateUi(); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::retranslateUi() +{ + spinBox->setToolTip(tr("Do not display formats with less than this amount of cards in the database")); + toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)")); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons() +{ + // Iterate through main types and create buttons + for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) { + auto *button = new QPushButton(it.key(), flowWidget); + button->setCheckable(true); + button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" + "QPushButton:checked { background-color: green; color: white; }"); + + flowWidget->addWidget(button); + formatButtons[it.key()] = button; + + // Connect toggle signal + connect(button, &QPushButton::toggled, this, + [this, mainType = it.key()](bool checked) { handleFormatToggled(mainType, checked); }); + } + updateFormatButtonsVisibility(); // Ensure visibility is updated initially +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility() +{ + int threshold = spinBox->value(); // Get the current spinbox value + + // Iterate through buttons and hide/disable those below the threshold + for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) { + bool visible = allFormatsWithCount[it.key()] >= threshold; + it.value()->setVisible(visible); + it.value()->setEnabled(visible); + } +} + +int VisualDatabaseDisplayFormatLegalityFilterWidget::getMaxMainTypeCount() const +{ + int maxCount = 1; + for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) { + maxCount = qMax(maxCount, it.value()); + } + return maxCount; +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::handleFormatToggled(const QString &format, bool active) +{ + activeFormats[format] = active; + + if (formatButtons.contains(format)) { + formatButtons[format]->setChecked(active); + } + + updateFormatFilter(); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatFilter() +{ + // Clear existing filters related to main type + filterModel->blockSignals(true); + filterModel->filterTree()->blockSignals(true); + filterModel->clearFiltersOfType(CardFilter::Attr::AttrFormat); + + if (exactMatchMode) { + // Exact Match: Only selected main types are allowed + QSet selectedTypes; + for (const auto &type : activeFormats.keys()) { + if (activeFormats[type]) { + selectedTypes.insert(type); + } + } + + if (!selectedTypes.isEmpty()) { + // Require all selected types (TypeAnd) + for (const auto &type : selectedTypes) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat)); + } + + // Exclude any other types (TypeAndNot) + for (const auto &type : formatButtons.keys()) { + if (!selectedTypes.contains(type)) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrFormat)); + } + } + } + } else { + // Default Includes Mode (TypeOr) - match any selected main types + for (const auto &type : activeFormats.keys()) { + if (activeFormats[type]) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat)); + } + } + } + + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode(bool checked) +{ + exactMatchMode = checked; + toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); + updateFormatFilter(); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel() +{ + // Temporarily block signals for each button to prevent toggling while updating button states + for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) { + it.value()->blockSignals(true); + } + + // Uncheck all buttons + for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) { + it.value()->setChecked(false); + } + + // Get active filters for main types + QSet activeTypes; + for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrFormat)) { + if (filter->type() == CardFilter::Type::TypeAnd) { + activeTypes.insert(filter->term()); + } + } + + // Check the buttons for active types + for (const auto &type : activeTypes) { + activeFormats[type] = true; + if (formatButtons.contains(type)) { + formatButtons[type]->setChecked(true); + } + } + + // Re-enable signal emissions for each button + for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) { + it.value()->blockSignals(false); + } + + // Update the visibility of buttons + updateFormatButtonsVisibility(); +} diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h new file mode 100644 index 000000000..a2a00b740 --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h @@ -0,0 +1,43 @@ +#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H +#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H + +#include "../../../filters/filter_tree_model.h" +#include "../general/layout_containers/flow_widget.h" + +#include +#include +#include +#include +#include +#include + +class VisualDatabaseDisplayFormatLegalityFilterWidget : public QWidget +{ + Q_OBJECT +public: + explicit VisualDatabaseDisplayFormatLegalityFilterWidget(QWidget *parent, FilterTreeModel *filterModel); + void retranslateUi(); + void createFormatButtons(); + void updateFormatButtonsVisibility(); + int getMaxMainTypeCount() const; + + void handleFormatToggled(const QString &format, bool active); + void updateFormatFilter(); + void updateFilterMode(bool checked); + void syncWithFilterModel(); + +private: + FilterTreeModel *filterModel; + QMap allFormatsWithCount; + QSpinBox *spinBox; + QHBoxLayout *layout; + FlowWidget *flowWidget; + QPushButton *toggleButton; // Mode switch button + + QMap activeFormats; // Track active filters + QMap formatButtons; // Store toggle buttons + + bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes" +}; + +#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp index 32652c346..368ac8719 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp @@ -33,7 +33,7 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically spinBox->setValue(150); layout->addWidget(spinBox); - connect(spinBox, QOverload::of(&QSpinBox::valueChanged), this, + connect(spinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility); // Create the toggle button for Exact Match/Includes mode diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h index 34c6ddac2..27c61dffc 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include 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 64eed8b16..7551954f3 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 @@ -51,7 +51,7 @@ VisualDatabaseDisplayNameFilterWidget::VisualDatabaseDisplayNameFilterWidget(QWi void VisualDatabaseDisplayNameFilterWidget::retranslateUi() { - searchBox->setPlaceholderText(tr("Filter by name...")); + searchBox->setPlaceholderText(tr("Filter by name... (Exact match)")); loadFromDeckButton->setText(tr("Load from Deck")); loadFromDeckButton->setToolTip(tr("Apply all card names in currently loaded deck as exact match name filters")); loadFromClipboardButton->setText(tr("Load from Clipboard")); @@ -64,14 +64,10 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() 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 +79,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); } @@ -123,14 +119,14 @@ void VisualDatabaseDisplayNameFilterWidget::updateFilterModel() { // Clear existing name filters emit filterModel->layoutAboutToBeChanged(); - filterModel->clearFiltersOfType(CardFilter::Attr::AttrName); + filterModel->clearFiltersOfType(CardFilter::Attr::AttrNameExact); filterModel->blockSignals(true); filterModel->filterTree()->blockSignals(true); for (const auto &name : activeFilters.keys()) { QString nameString = name; - filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrName)); + filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrNameExact)); } filterModel->blockSignals(false); @@ -146,7 +142,7 @@ void VisualDatabaseDisplayNameFilterWidget::updateFilterModel() void VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel() { QStringList currentFilters; - for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrName)) { + for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrNameExact)) { if (filter->type() == CardFilter::Type::TypeOr) { currentFilters.append(filter->term()); } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h index b98d83523..76d3ec29e 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h @@ -14,7 +14,6 @@ #include #include #include -#include #include class VisualDatabaseDisplayNameFilterWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp index 1ea6e8e67..dee89f06a 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp @@ -27,7 +27,7 @@ VisualDatabaseDisplayRecentSetFilterSettingsWidget::VisualDatabaseDisplayRecentS filterToMostRecentSetsAmount->setMaximum(100); filterToMostRecentSetsAmount->setValue( SettingsCache::instance().getVisualDatabaseDisplayFilterToMostRecentSetsAmount()); - connect(filterToMostRecentSetsAmount, QOverload::of(&QSpinBox::valueChanged), &SettingsCache::instance(), + connect(filterToMostRecentSetsAmount, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), &SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount); layout->addWidget(filterToMostRecentSetsCheckBox); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h index ea006c88e..b4362d1db 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp index 7a88b51cf..b34bb65d3 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp @@ -26,7 +26,7 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg spinBox->setMaximum(getMaxSubTypeCount()); spinBox->setValue(150); layout->addWidget(spinBox); - connect(spinBox, QOverload::of(&QSpinBox::valueChanged), this, + connect(spinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility); // Create search box diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h index fdddbed41..c02db29be 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h @@ -13,8 +13,6 @@ #include #include #include -#include -#include #include class VisualDatabaseDisplaySubTypeFilterWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp index 7171d93b2..c5eaa171a 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, @@ -207,6 +206,7 @@ void VisualDatabaseDisplayWidget::initialize() saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel); nameFilterWidget = new VisualDatabaseDisplayNameFilterWidget(this, deckEditor, filterModel); mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel); + formatLegalityWidget = new VisualDatabaseDisplayFormatLegalityFilterWidget(this, filterModel); subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel); setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel); @@ -224,6 +224,7 @@ void VisualDatabaseDisplayWidget::initialize() filterContainerLayout->addWidget(quickFilterSubTypeWidget); filterContainerLayout->addWidget(quickFilterSetWidget); filterContainerLayout->addWidget(mainTypeFilterWidget); + filterContainerLayout->addWidget(formatLegalityWidget); searchLayout->addWidget(colorFilterWidget); searchLayout->addWidget(clearFilterWidget); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h index cc51cb003..24990f8e5 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h @@ -10,7 +10,6 @@ #include "../../../filters/filter_tree_model.h" #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" #include "../../key_signals.h" -#include "../../layouts/flow_layout.h" #include "../cards/card_info_picture_with_text_overlay_widget.h" #include "../cards/card_size_widget.h" #include "../general/layout_containers/flow_widget.h" @@ -18,17 +17,16 @@ #include "../utility/custom_line_edit.h" #include "visual_database_display_color_filter_widget.h" #include "visual_database_display_filter_save_load_widget.h" +#include "visual_database_display_format_legality_filter_widget.h" #include "visual_database_display_main_type_filter_widget.h" #include "visual_database_display_name_filter_widget.h" #include "visual_database_display_set_filter_widget.h" #include "visual_database_display_sub_type_filter_widget.h" #include -#include #include #include #include -#include #include #include #include @@ -94,6 +92,7 @@ private: SettingsButtonWidget *quickFilterNameWidget; VisualDatabaseDisplayNameFilterWidget *nameFilterWidget; VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget; + VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget; SettingsButtonWidget *quickFilterSubTypeWidget; VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget; SettingsButtonWidget *quickFilterSetWidget; diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp new file mode 100644 index 000000000..b169e73cb --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp @@ -0,0 +1,122 @@ +#include "visual_deck_display_options_widget.h" + +#include "../tabs/visual_deck_editor/tab_deck_editor_visual.h" + +#include + +VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) : QWidget(parent) +{ + groupAndSortLayout = new QHBoxLayout(this); + groupAndSortLayout->setAlignment(Qt::AlignLeft); + this->setLayout(groupAndSortLayout); + + groupByLabel = new QLabel(this); + + groupByComboBox = new QComboBox(this); + + if (auto tab = QtUtils::findParentOfType(this)) { + auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox(); + groupByComboBox->setModel(originalBox->model()); + groupByComboBox->setModelColumn(originalBox->modelColumn()); + + // Original -> clone + connect(originalBox, QOverload::of(&QComboBox::currentIndexChanged), + [this](int index) { groupByComboBox->setCurrentIndex(index); }); + + // Clone -> original + connect(groupByComboBox, QOverload::of(&QComboBox::currentIndexChanged), + [originalBox](int index) { originalBox->setCurrentIndex(index); }); + } else { + groupByComboBox->addItem( + tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::MAIN_TYPE))), + DeckListModelGroupCriteria::MAIN_TYPE); + groupByComboBox->addItem( + tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::MANA_COST))), + DeckListModelGroupCriteria::MANA_COST); + groupByComboBox->addItem( + tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::COLOR))), + DeckListModelGroupCriteria::COLOR); + groupByComboBox->setMinimumWidth(300); + connect(groupByComboBox, QOverload::of(&QComboBox::currentTextChanged), this, + &VisualDeckDisplayOptionsWidget::groupCriteriaChanged); + emit groupCriteriaChanged(groupByComboBox->currentText()); + } + + sortByLabel = new QLabel(this); + + sortCriteriaButton = new SettingsButtonWidget(this); + + sortLabel = new QLabel(sortCriteriaButton); + sortLabel->setWordWrap(true); + + QStringList sortProperties = {"colors", "cmc", "name", "maintype"}; + sortByListWidget = new QListWidget(); + sortByListWidget->setSelectionMode(QAbstractItemView::SingleSelection); + sortByListWidget->setDragDropMode(QAbstractItemView::InternalMove); + sortByListWidget->setDefaultDropAction(Qt::MoveAction); + + for (const QString &property : sortProperties) { + QListWidgetItem *item = new QListWidgetItem(property, sortByListWidget); + item->setFlags(item->flags() | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled); + } + + connect(sortByListWidget->model(), &QAbstractItemModel::rowsMoved, this, + &VisualDeckDisplayOptionsWidget::onSortCriteriaChange); + onSortCriteriaChange(); + + sortByListWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + sortCriteriaButton->addSettingsWidget(sortLabel); + sortCriteriaButton->addSettingsWidget(sortByListWidget); + + displayTypeButton = new QPushButton(this); + connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckDisplayOptionsWidget::updateDisplayType); + + groupAndSortLayout->addWidget(groupByLabel); + groupAndSortLayout->addWidget(groupByComboBox); + groupAndSortLayout->addWidget(sortByLabel); + groupAndSortLayout->addWidget(sortCriteriaButton); + groupAndSortLayout->addWidget(displayTypeButton); + + retranslateUi(); +} + +void VisualDeckDisplayOptionsWidget::retranslateUi() +{ + groupByLabel->setText(tr("Group by:")); + groupByComboBox->setToolTip(tr("Change how cards are divided into categories/groups.")); + sortByLabel->setText(tr("Sort by:")); + sortLabel->setText(tr("Click and drag to change the sort order within the groups")); + sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups")); + displayTypeButton->setText(tr("Toggle Layout: Overlap")); + displayTypeButton->setToolTip( + tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); +} + +void VisualDeckDisplayOptionsWidget::onSortCriteriaChange() +{ + QStringList selectedCriteria; + for (int i = 0; i < sortByListWidget->count(); ++i) { + QListWidgetItem *item = sortByListWidget->item(i); + selectedCriteria.append(item->text()); // Collect user-defined sort order + } + + emit sortCriteriaChanged(selectedCriteria); +} + +void VisualDeckDisplayOptionsWidget::updateDisplayType() +{ + // Toggle the display type + currentDisplayType = (currentDisplayType == DisplayType::Overlap) ? DisplayType::Flat : DisplayType::Overlap; + + // Update UI and emit signal + switch (currentDisplayType) { + case DisplayType::Flat: + displayTypeButton->setText(tr("Toggle Layout: Flat")); + break; + case DisplayType::Overlap: + displayTypeButton->setText(tr("Toggle Layout: Overlap")); + break; + } + emit displayTypeChanged(currentDisplayType); +} diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h new file mode 100644 index 000000000..7a447753f --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h @@ -0,0 +1,138 @@ +#ifndef COCKATRICE_VISUAL_DECK_DISPLAY_OPTIONS_WIDGET_H +#define COCKATRICE_VISUAL_DECK_DISPLAY_OPTIONS_WIDGET_H + +#include "visual_deck_editor_widget.h" + +#include +#include +#include +#include +#include +#include + +/** + * @class VisualDeckDisplayOptionsWidget + * @brief A widget that controls how deck cards are displayed in the visual deck editor. + * + * This widget provides: + * - A **group-by** selector (QComboBox) + * - A **sort-by** multi-criteria, draggable list (QListWidget within a SettingsButtonWidget) + * - A **display-type toggler** (flat vs. overlap layout) + * + * Depending on whether the parent is a VisualDeckEditorWidget, this widget can mirror the + * original group by checkbox from the main deck editor UI to maintain synchronization. + * + * It emits signals whenever the grouping criterion, sorting criteria, or display mode changes. + */ +class VisualDeckDisplayOptionsWidget : public QWidget +{ + Q_OBJECT +signals: + /** + * @brief Emitted when the display type (flat or overlapping layout) changes. + * @param displayType The newly selected display layout. + */ + void displayTypeChanged(const DisplayType &displayType); + + /** + * @brief Emitted when a new grouping criterion is selected. + * @param activeGroupCriteria Name of the selected group-by criterion. + */ + void groupCriteriaChanged(const QString &activeGroupCriteria); + + /** + * @brief Emitted when the order of sort criteria changes. + * @param activeSortCriteria Ordered list of sorting keys. + */ + void sortCriteriaChanged(const QStringList &activeSortCriteria); + +public slots: + /** + * @brief Updates all UI text for retranslation/localization. + * + * Called when the application language changes. + */ + void retranslateUi(); + +public: + /** + * @brief Constructs a new VisualDeckDisplayOptionsWidget. + * @param parent The parent QWidget—may trigger cloning of models if the parent is a visual deck editor. + */ + explicit VisualDeckDisplayOptionsWidget(QWidget *parent); + + /** + * @brief Gets the current display type (Overlap or Flat). + */ + DisplayType getDisplayType() const + { + return currentDisplayType; + } + + /** + * @brief Gets the currently active group-by criterion. + */ + QString getActiveGroupCriteria() const + { + return activeGroupCriteria; + } + + /** + * @brief Gets the currently active ordered sort criteria. + */ + QStringList getActiveSortCriteria() const + { + return activeSortCriteria; + } + +private slots: + /** + * @brief Slot triggered whenever the sort list is reordered. + * + * Reads the QListWidget’s order and emits `sortCriteriaChanged()`. + */ + void onSortCriteriaChange(); + + /** + * @brief Toggles the display layout between flat and overlapping modes. + * + * Emits `displayTypeChanged()`. + */ + void updateDisplayType(); + +private: + /// Layout for grouping and sorting UI elements. + QHBoxLayout *groupAndSortLayout; + + /// Current deck display type. + DisplayType currentDisplayType = DisplayType::Overlap; + + /// Button used to toggle the display layout. + QPushButton *displayTypeButton; + + /// Label for the group-by selector. + QLabel *groupByLabel; + + /// Combo box listing group-by criteria. + QComboBox *groupByComboBox; + + /// Currently active group-by criterion. + QString activeGroupCriteria = "maintype"; + + /// Encapsulates the sort settings widgets (label + list). + SettingsButtonWidget *sortCriteriaButton; + + /// Label for “Sort by”. + QLabel *sortByLabel; + + /// Descriptive label inside the sort criteria button. + QLabel *sortLabel; + + /// Draggable list of sort criteria. + QListWidget *sortByListWidget; + + /// Ordered list of current sort criteria. + QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"}; +}; + +#endif // COCKATRICE_VISUAL_DECK_DISPLAY_OPTIONS_WIDGET_H 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 b1db1cf37..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 @@ -24,9 +24,9 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare handSizeSpinBox = new QSpinBox(this); handSizeSpinBox->setValue(SettingsCache::instance().getVisualDeckEditorSampleHandSize()); handSizeSpinBox->setMinimum(1); - connect(handSizeSpinBox, QOverload::of(&QSpinBox::valueChanged), &SettingsCache::instance(), + connect(handSizeSpinBox, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), &SettingsCache::setVisualDeckEditorSampleHandSize); - connect(handSizeSpinBox, QOverload::of(&QSpinBox::valueChanged), this, + connect(handSizeSpinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDeckEditorSampleHandWidget::updateDisplay); resetAndHandSizeLayout->addWidget(handSizeSpinBox); @@ -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 c1e5ead3a..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 @@ -7,9 +7,9 @@ #include "../cards/card_info_picture_with_text_overlay_widget.h" #include "../cards/deck_card_zone_display_widget.h" #include "../general/layout_containers/flow_widget.h" -#include "../general/layout_containers/overlap_control_widget.h" #include "../tabs/visual_deck_editor/tab_deck_editor_visual.h" #include "../tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h" +#include "visual_deck_display_options_widget.h" #include #include @@ -110,75 +110,22 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, } }); - groupAndSortContainer = new QWidget(this); - groupAndSortLayout = new QHBoxLayout(groupAndSortContainer); - groupAndSortLayout->setAlignment(Qt::AlignLeft); - groupAndSortContainer->setLayout(groupAndSortLayout); + displayOptionsAndSearch = new QWidget(this); + displayOptionsAndSearchLayout = new QHBoxLayout(displayOptionsAndSearch); + displayOptionsAndSearchLayout->setAlignment(Qt::AlignLeft); + displayOptionsAndSearch->setLayout(displayOptionsAndSearchLayout); - groupByLabel = new QLabel(groupAndSortContainer); + displayOptionsWidget = new VisualDeckDisplayOptionsWidget(this); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::displayTypeChanged, this, + &VisualDeckEditorWidget::displayTypeChanged); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, this, + &VisualDeckEditorWidget::activeGroupCriteriaChanged); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, this, + &VisualDeckEditorWidget::activeSortCriteriaChanged); - groupByComboBox = new QComboBox(this); - if (auto tabWidget = qobject_cast(parent)) { - // Inside a central widget QWidget container inside TabDeckEditorVisual - if (auto tab = qobject_cast(tabWidget->parent()->parent())) { - auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox(); - groupByComboBox->setModel(originalBox->model()); - groupByComboBox->setModelColumn(originalBox->modelColumn()); - - // Original -> clone - connect(originalBox, QOverload::of(&QComboBox::currentIndexChanged), - [this](int index) { groupByComboBox->setCurrentIndex(index); }); - - // Clone -> original - connect(groupByComboBox, QOverload::of(&QComboBox::currentIndexChanged), - [originalBox](int index) { originalBox->setCurrentIndex(index); }); - } - } else { - QStringList groupProperties = {"maintype", "colors", "cmc", "name"}; - groupByComboBox->addItems(groupProperties); - groupByComboBox->setMinimumWidth(300); - connect(groupByComboBox, QOverload::of(&QComboBox::currentTextChanged), this, - &VisualDeckEditorWidget::actChangeActiveGroupCriteria); - actChangeActiveGroupCriteria(); - } - - sortByLabel = new QLabel(groupAndSortContainer); - - sortCriteriaButton = new SettingsButtonWidget(this); - - sortLabel = new QLabel(sortCriteriaButton); - sortLabel->setWordWrap(true); - - QStringList sortProperties = {"colors", "cmc", "name", "maintype"}; - sortByListWidget = new QListWidget(); - sortByListWidget->setSelectionMode(QAbstractItemView::SingleSelection); - sortByListWidget->setDragDropMode(QAbstractItemView::InternalMove); - sortByListWidget->setDefaultDropAction(Qt::MoveAction); - - for (const QString &property : sortProperties) { - QListWidgetItem *item = new QListWidgetItem(property, sortByListWidget); - item->setFlags(item->flags() | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled); - } - - connect(sortByListWidget->model(), &QAbstractItemModel::rowsMoved, this, - &VisualDeckEditorWidget::actChangeActiveSortCriteria); - actChangeActiveSortCriteria(); - - sortByListWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - - sortCriteriaButton->addSettingsWidget(sortLabel); - sortCriteriaButton->addSettingsWidget(sortByListWidget); - - displayTypeButton = new QPushButton(this); - connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckEditorWidget::updateDisplayType); - - groupAndSortLayout->addWidget(groupByLabel); - groupAndSortLayout->addWidget(groupByComboBox); - groupAndSortLayout->addWidget(sortByLabel); - groupAndSortLayout->addWidget(sortCriteriaButton); - groupAndSortLayout->addWidget(displayTypeButton); - groupAndSortLayout->addWidget(searchBar); - groupAndSortLayout->addWidget(searchPushButton); + displayOptionsAndSearchLayout->addWidget(displayOptionsWidget); + displayOptionsAndSearchLayout->addWidget(searchBar); + displayOptionsAndSearchLayout->addWidget(searchPushButton); scrollArea = new QScrollArea(this); scrollArea->setWidgetResizable(true); @@ -198,7 +145,7 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), &SettingsCache::setVisualDeckEditorCardSize); - mainLayout->addWidget(groupAndSortContainer); + mainLayout->addWidget(displayOptionsAndSearch); mainLayout->addWidget(scrollArea); mainLayout->addWidget(cardSizeWidget); @@ -219,17 +166,9 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, void VisualDeckEditorWidget::retranslateUi() { searchBar->setPlaceholderText(tr("Type a card name here for suggestions from the database...")); - groupByLabel->setText(tr("Group by:")); - groupByComboBox->setToolTip(tr("Change how cards are divided into categories/groups.")); - sortByLabel->setText(tr("Sort by:")); - sortLabel->setText(tr("Click and drag to change the sort order within the groups")); searchPushButton->setText(tr("Quick search and add card")); searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add " "preferred printing to the deck on pressing enter")); - sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups")); - displayTypeButton->setText(tr("Toggle Layout: Overlap")); - displayTypeButton->setToolTip( - tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } void VisualDeckEditorWidget::setSelectionModel(QItemSelectionModel *model) @@ -325,10 +264,12 @@ 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(), activeGroupCriteria, - activeSortCriteria, currentDisplayType, 20, 10, cardSizeWidget); + zoneContainer, deckListModel, selectionModel, persistent, zoneName, + displayOptionsWidget->getActiveGroupCriteria(), displayOptionsWidget->getActiveSortCriteria(), + displayOptionsWidget->getDisplayType(), 20, 10, cardSizeWidget); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this, @@ -339,7 +280,7 @@ void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex p &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget, &DeckCardZoneDisplayWidget::refreshDisplayType); - zoneDisplayWidget->refreshDisplayType(currentDisplayType); + zoneDisplayWidget->refreshDisplayType(displayOptionsWidget->getDisplayType()); zoneContainerLayout->addWidget(zoneDisplayWidget); indexToWidgetMap.insert(persistent, zoneDisplayWidget); @@ -350,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); @@ -371,48 +312,12 @@ void VisualDeckEditorWidget::updateZoneWidgets() { } -void VisualDeckEditorWidget::updateDisplayType() -{ - // Toggle the display type - currentDisplayType = (currentDisplayType == DisplayType::Overlap) ? DisplayType::Flat : DisplayType::Overlap; - - // Update UI and emit signal - switch (currentDisplayType) { - case DisplayType::Flat: - displayTypeButton->setText(tr("Toggle Layout: Flat")); - break; - case DisplayType::Overlap: - displayTypeButton->setText(tr("Toggle Layout: Overlap")); - break; - } - emit displayTypeChanged(currentDisplayType); -} - void VisualDeckEditorWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); zoneContainer->setMaximumWidth(scrollArea->viewport()->width()); } -void VisualDeckEditorWidget::actChangeActiveGroupCriteria() -{ - activeGroupCriteria = groupByComboBox->currentText(); - emit activeGroupCriteriaChanged(activeGroupCriteria); -} - -void VisualDeckEditorWidget::actChangeActiveSortCriteria() -{ - QStringList selectedCriteria; - for (int i = 0; i < sortByListWidget->count(); ++i) { - QListWidgetItem *item = sortByListWidget->item(i); - selectedCriteria.append(item->text()); // Collect user-defined sort order - } - - activeSortCriteria = selectedCriteria; - - emit activeSortCriteriaChanged(selectedCriteria); -} - void VisualDeckEditorWidget::decklistModelReset() { clearAllDisplayWidgets(); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h index e8ededc77..3a60d09a7 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -9,7 +9,6 @@ #include "../cards/card_info_picture_with_text_overlay_widget.h" #include "../cards/card_size_widget.h" -#include "../general/layout_containers/flow_widget.h" #include "../general/layout_containers/overlap_control_widget.h" #include "../quick_settings/settings_button_widget.h" @@ -24,6 +23,7 @@ #include #include +class VisualDeckDisplayOptionsWidget; class DeckCardZoneDisplayWidget; enum class DisplayType { @@ -56,7 +56,6 @@ public: public slots: void decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight); void updateZoneWidgets(); - void updateDisplayType(); void cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget); void onCardAddition(const QModelIndex &parent, int first, int last); void onCardRemoval(const QModelIndex &parent, int first, int last); @@ -74,8 +73,6 @@ signals: protected slots: void onHover(const ExactCard &hoveredCard); void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); - void actChangeActiveGroupCriteria(); - void actChangeActiveSortCriteria(); void decklistModelReset(); private: @@ -86,19 +83,10 @@ private: CardDatabaseDisplayModel *cardDatabaseDisplayModel; CardCompleterProxyModel *proxyModel; QCompleter *completer; + QWidget *displayOptionsAndSearch; + QHBoxLayout *displayOptionsAndSearchLayout; + VisualDeckDisplayOptionsWidget *displayOptionsWidget; QPushButton *searchPushButton; - DisplayType currentDisplayType = DisplayType::Overlap; - QPushButton *displayTypeButton; - QWidget *groupAndSortContainer; - QHBoxLayout *groupAndSortLayout; - QLabel *groupByLabel; - QComboBox *groupByComboBox; - QString activeGroupCriteria = "maintype"; - SettingsButtonWidget *sortCriteriaButton; - QLabel *sortByLabel; - QLabel *sortLabel; - QListWidget *sortByListWidget; - QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"}; QScrollArea *scrollArea; QWidget *zoneContainer; QVBoxLayout *zoneContainerLayout; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp index ef59e90ff..575632724 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp @@ -4,7 +4,6 @@ #include "deck_preview_widget.h" #include -#include DeckPreviewColorIdentityFilterWidget::DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent) : QWidget(parent), layout(new QHBoxLayout(this)) diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h index 0e4a5837c..551dbb35c 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h @@ -9,9 +9,7 @@ #include "../visual_deck_storage_widget.h" -#include #include -#include #include #include 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 eb105697f..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 @@ -11,11 +11,10 @@ #include #include -#include #include -DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList) - : QWidget(_parent), deckList(nullptr) +DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags) + : QWidget(_parent), currentTags(_tags) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); @@ -28,22 +27,14 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); - if (_deckList) { - connectDeckList(_deckList); - } - layout->addWidget(flowWidget); + + refreshTags(); } -void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList) +void DeckPreviewDeckTagsDisplayWidget::setTags(const QStringList &_tags) { - if (deckList) { - disconnect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); - } - - deckList = _deckList; - connect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); - + currentTags = _tags; refreshTags(); } @@ -51,7 +42,7 @@ void DeckPreviewDeckTagsDisplayWidget::refreshTags() { flowWidget->clearLayout(); - for (const QString &tag : deckList->getTags()) { + for (const QString &tag : currentTags) { flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag)); } @@ -78,7 +69,45 @@ static QStringList getAllFiles(const QString &filePath) return allFiles; } -bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath) +/** + * Gets all tags that appear in the deck folder + */ +static QStringList findAllKnownTags() +{ + QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath()); + + QStringList knownTags; + auto loader = DeckLoader(nullptr); + for (const QString &file : allFiles) { + loader.loadFromFile(file, DeckFileFormat::getFormatFromName(file), false); + QStringList tags = loader.getDeck().deckList.getTags(); + knownTags.append(tags); + knownTags.removeDuplicates(); + } + + return knownTags; +} + +void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() +{ + if (qobject_cast(parentWidget())) { + // If we're the child of a DeckPreviewWidget, then we need to handle conversion + auto *deckPreviewWidget = qobject_cast(parentWidget()); + + bool canAddTags = promptFileConversionIfRequired(deckPreviewWidget); + + if (canAddTags) { + QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags(); + execTagDialog(knownTags); + } + } else { + // If we're the child of an AbstractTabDeckEditor, then we don't bother with conversion + QStringList knownTags = findAllKnownTags(); + execTagDialog(knownTags); + } +} + +static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath) { QFileInfo fileInfo(filePath); QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod"); @@ -93,96 +122,70 @@ bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath) return true; // Safe to proceed } -void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() +static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget) { - if (qobject_cast(parentWidget())) { - auto *deckPreviewWidget = qobject_cast(parentWidget()); - QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags(); - QStringList activeTags = deckList->getTags(); + deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); + deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getDeck().lastLoadInfo.fileName; + deckPreviewWidget->refreshBannerCardText(); +} - bool canAddTags = true; +/** + * Checks if the deck's file format supports tags. + * If not, then prompt the user for file conversion. + * @return whether the resulting file can support adding tags + */ +bool DeckPreviewDeckTagsDisplayWidget::promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget) +{ + if (DeckFileFormat::getFormatFromName(deckPreviewWidget->filePath) == DeckFileFormat::Cockatrice) { + return true; + } - if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) != DeckLoader::CockatriceFormat) { - canAddTags = false; - // Retrieve saved preference if the prompt is disabled - if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { - if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { - - if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) - return; - - deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); - deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName(); - deckPreviewWidget->refreshBannerCardText(); - canAddTags = true; - } - } else { - // Show the dialog to the user - DialogConvertDeckToCodFormat conversionDialog(parentWidget()); - if (conversionDialog.exec() == QDialog::Accepted) { - - if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) - return; - - deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); - deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName(); - deckPreviewWidget->refreshBannerCardText(); - canAddTags = true; - - if (conversionDialog.dontAskAgain()) { - SettingsCache::instance().setVisualDeckStoragePromptForConversion(false); - SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true); - } - } else { - SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false); - - if (conversionDialog.dontAskAgain()) { - SettingsCache::instance().setVisualDeckStoragePromptForConversion(false); - } else { - SettingsCache::instance().setVisualDeckStoragePromptForConversion(true); - } - } - } + // Retrieve saved preference if the prompt is disabled + if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { + if (!SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { + return false; } - if (canAddTags) { - DeckPreviewTagDialog dialog(knownTags, activeTags); - if (dialog.exec() == QDialog::Accepted) { - QStringList updatedTags = dialog.getActiveTags(); - deckList->setTags(updatedTags); - deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat); - } + if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) { + return false; } - } else if (parentWidget()) { - // If we're the child of an AbstractTabDeckEditor, we are buried under a ton of childWidgets in the - // DeckInfoDock. - QWidget *currentParent = parentWidget(); - while (currentParent) { - if (qobject_cast(currentParent)) { - break; - } - currentParent = currentParent->parentWidget(); - } - if (qobject_cast(currentParent)) { - auto *deckEditor = qobject_cast(currentParent); - QStringList knownTags; - QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath()); - DeckLoader loader(this); - for (const QString &file : allFiles) { - loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false); - QStringList tags = loader.getDeckList()->getTags(); - knownTags.append(tags); - knownTags.removeDuplicates(); - } - QStringList activeTags = deckList->getTags(); + convertFileToCockatriceFormat(deckPreviewWidget); + return true; + } - DeckPreviewTagDialog dialog(knownTags, activeTags); - if (dialog.exec() == QDialog::Accepted) { - QStringList updatedTags = dialog.getActiveTags(); - deckList->setTags(updatedTags); - deckEditor->setModified(true); - } + // Show the dialog to the user + DialogConvertDeckToCodFormat conversionDialog(parentWidget()); + if (conversionDialog.exec() != QDialog::Accepted) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(!conversionDialog.dontAskAgain()); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false); + + return false; + } + + // Try to convert file + if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) { + return false; + } + + convertFileToCockatriceFormat(deckPreviewWidget); + + if (conversionDialog.dontAskAgain()) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(false); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true); + } + + return true; +} + +void DeckPreviewDeckTagsDisplayWidget::execTagDialog(const QStringList &knownTags) +{ + DeckPreviewTagDialog dialog(knownTags, currentTags); + if (dialog.exec() == QDialog::Accepted) { + QStringList updatedTags = dialog.getActiveTags(); + if (updatedTags != currentTags) { + setTags(updatedTags); + emit tagsChanged(updatedTags); } } -} +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h index 4270e38e5..74ea1cbdf 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h @@ -12,21 +12,31 @@ #include -inline bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath); - class DeckPreviewWidget; class DeckPreviewDeckTagsDisplayWidget : public QWidget { Q_OBJECT -public: - explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList); - void connectDeckList(DeckList *_deckList); - void refreshTags(); - DeckList *deckList; + QStringList currentTags; FlowWidget *flowWidget; +public: + explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags); + void setTags(const QStringList &_tags); + void refreshTags(); + public slots: void openTagEditDlg(); + +private: + bool promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget); + void execTagDialog(const QStringList &knownTags); + +signals: + /** + * Emitted when the tags have changed due to user interaction. + * @param tags The new list of tags. + */ + void tagsChanged(const QStringList &tags); }; #endif // DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp index a221442ba..fdf4b2a28 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp @@ -2,7 +2,6 @@ #include "../../../../client/settings/cache_settings.h" #include "../../../../interface/widgets/tabs/abstract_tab_deck_editor.h" -#include "deck_preview_tag_dialog.h" #include #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h index bb39943b6..c0fa86d19 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h @@ -9,10 +9,6 @@ #include "deck_preview_deck_tags_display_widget.h" -#include -#include -#include - class DeckPreviewTagAdditionWidget : public QWidget { Q_OBJECT diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp index df7f9738d..e8a399fe5 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp @@ -11,7 +11,6 @@ #include #include #include -#include DeckPreviewTagDialog::DeckPreviewTagDialog(const QStringList &knownTags, const QStringList &_activeTags, diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h index f23040a81..59b330a65 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h @@ -8,13 +8,10 @@ #ifndef DECK_PREVIEW_TAG_DIALOG_H #define DECK_PREVIEW_TAG_DIALOG_H -#include #include #include #include #include -#include -#include #include class DeckPreviewTagDialog : public QDialog diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h index 9b828fa3d..a868aa4f1 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h @@ -30,12 +30,12 @@ public: * @param tagName The name of the tag to display. */ explicit DeckPreviewTagDisplayWidget(QWidget *parent = nullptr, const QString &tagName = ""); - QSize sizeHint() const override; - QString getTagName() const + [[nodiscard]] QSize sizeHint() const override; + [[nodiscard]] QString getTagName() const { return tagName; } - TagState getState() const + [[nodiscard]] TagState getState() const { return state; } @@ -44,7 +44,7 @@ public: { state = newState; update(); - }; + } signals: /** 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 8b2bec10b..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 @@ -6,6 +6,7 @@ #include "deck_preview_deck_tags_display_widget.h" #include +#include #include #include #include @@ -31,7 +32,7 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent, many deck loads have finished already and if we've loaded all decks and THEN load all the tags at once. */ connect(deckLoader, &DeckLoader::loadFinished, visualDeckStorageWidget->tagFilterWidget, &VisualDeckStorageTagFilterWidget::refreshTags); - deckLoader->loadFromFileAsync(filePath, DeckLoader::getFormatFromName(filePath), false); + deckLoader->loadFromFileAsync(filePath, DeckFileFormat::getFormatFromName(filePath), false); bannerCardDisplayWidget = new DeckPreviewCardPictureWidget(this, false, visualDeckStorageWidget->deckPreviewSelectionAnimationEnabled); @@ -73,23 +74,24 @@ 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->getLastFileName()); + setFilePath(deckLoader->getDeck().lastLoadInfo.fileName); colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeck().deckList.getTags()); + connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, &DeckPreviewWidget::setTags); bannerCardLabel = new QLabel(this); bannerCardLabel->setObjectName("bannerCardLabel"); 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); @@ -151,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 {}; } @@ -185,8 +187,8 @@ QString DeckPreviewWidget::getColorIdentity() */ QString DeckPreviewWidget::getDisplayName() const { - return deckLoader->getDeckList()->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).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) @@ -233,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) { @@ -267,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 { @@ -285,8 +287,8 @@ void DeckPreviewWidget::setBannerCard(int /* changedIndex */) { auto [name, id] = bannerCardComboBox->currentData().value>(); CardRef cardRef = {name, id}; - deckLoader->getDeckList()->setBannerCard(cardRef); - deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath)); + deckLoader->getDeck().deckList.setBannerCard(cardRef); + deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef)); } @@ -306,13 +308,19 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC emit deckLoadRequested(filePath); } +void DeckPreviewWidget::setTags(const QStringList &tags) +{ + deckLoader->getDeck().deckList.setTags(tags); + deckLoader->saveToFile(filePath, DeckFileFormat::Cockatrice); +} + QMenu *DeckPreviewWidget::createRightClickMenu() { auto *menu = new QMenu(this); 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); @@ -326,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(); @@ -368,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); @@ -377,8 +385,8 @@ void DeckPreviewWidget::actRenameDeck() } // write change - deckLoader->getDeckList()->setName(newName); - deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath)); + deckLoader->getDeck().deckList.setName(newName); + deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); // update VDS refreshBannerCardText(); @@ -408,7 +416,7 @@ void DeckPreviewWidget::actRenameFile() return; } - deckLoader->setLastFileName(newFilePath); + 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 76db9e569..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); @@ -72,6 +72,8 @@ private: void addSetBannerCardMenu(QMenu *menu); private slots: + void setTags(const QStringList &tags); + void actRenameDeck(); void actRenameFile(); void actDeleteFile(); 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 c5758c5a6..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 @@ -2,6 +2,7 @@ #include "../../../client/settings/cache_settings.h" #include "deck_preview/deck_preview_widget.h" +#include "visual_deck_storage_widget.h" #include #include @@ -210,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_folder_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h index fe1693c7e..d76fb0497 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h @@ -9,11 +9,8 @@ #include "../general/display/banner_widget.h" #include "../general/layout_containers/flow_widget.h" -#include "visual_deck_storage_widget.h" - -#include -#include +class VisualDeckStorageWidget; class VisualDeckStorageFolderDisplayWidget : public QWidget { Q_OBJECT @@ -31,7 +28,7 @@ public: [[nodiscard]] FlowWidget *getFlowWidget() const { return flowWidget; - }; + } public slots: void updateVisibility(bool recursive = true); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp index c1e9bfa6a..a396b8cd3 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp @@ -61,10 +61,10 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg unusedColorIdentitiesOpacitySpinBox->setMaximum(100); unusedColorIdentitiesOpacitySpinBox->setValue( SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity()); - connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), this, + connect(unusedColorIdentitiesOpacitySpinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDeckStorageQuickSettingsWidget::unusedColorIdentitiesOpacityChanged); - connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), - &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity); + connect(unusedColorIdentitiesOpacitySpinBox, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity); unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp index dbd7929c5..70b438232 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp @@ -6,6 +6,7 @@ #include "../../pixel_map_generator.h" #include +#include /** * @brief Constructs a PrintingSelectorCardSearchWidget for searching cards by set name or set code. diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h index eff238e7e..67d260b21 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h @@ -7,11 +7,10 @@ #ifndef VISUAL_DECK_STORAGE_SEARCH_WIDGET_H #define VISUAL_DECK_STORAGE_SEARCH_WIDGET_H -#include "visual_deck_storage_widget.h" +#include "deck_preview/deck_preview_widget.h" #include #include -#include #include class VisualDeckStorageWidget; 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 ad49e0065..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 @@ -2,6 +2,8 @@ #include "../../../client/settings/cache_settings.h" +#include + /** * @brief Constructs a PrintingSelectorCardSortWidget for searching cards by set name or set code. * @@ -93,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 4e78a67c4..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 @@ -1,12 +1,10 @@ #include "visual_deck_storage_tag_filter_widget.h" #include "../general/layout_containers/flow_widget.h" -#include "deck_preview/deck_preview_tag_addition_widget.h" #include "deck_preview/deck_preview_tag_display_widget.h" -#include "deck_preview/deck_preview_widget.h" +#include "visual_deck_storage_widget.h" #include -#include VisualDeckStorageTagFilterWidget::VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent) : QWidget(_parent), parent(_parent) @@ -59,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); }); @@ -155,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_tag_filter_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h index f5f160b55..ada94a244 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h @@ -7,7 +7,7 @@ #ifndef VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H #define VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H -#include "visual_deck_storage_widget.h" +#include "deck_preview/deck_preview_widget.h" #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp index d9cf74e7f..bfc425993 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include 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 69e70f969..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 @@ -9,18 +9,14 @@ #include "../../deck_loader/deck_loader.h" #include "../cards/card_size_widget.h" -#include "../general/layout_containers/flow_widget.h" #include "../quick_settings/settings_button_widget.h" #include "deck_preview/deck_preview_color_identity_filter_widget.h" -#include "deck_preview/deck_preview_widget.h" #include "visual_deck_storage_folder_display_widget.h" #include "visual_deck_storage_quick_settings_widget.h" #include "visual_deck_storage_search_widget.h" #include "visual_deck_storage_sort_widget.h" #include "visual_deck_storage_tag_filter_widget.h" -#include -#include #include class QSpinBox; @@ -57,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 622c04a08..41113185c 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -500,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 { diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 620a09467..75b0f5062 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -26,10 +26,8 @@ #define WINDOW_H #include -#include #include #include -#include #include #include #include @@ -135,7 +133,7 @@ private: inline QString getCardUpdaterBinaryName() { return "oracle"; - }; + } void createCardUpdateProcess(bool background = false); void exitCardDatabaseUpdate(); diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index f20dab6fb..7092a3fd7 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -20,7 +20,6 @@ #include "main.h" -#include "QtNetwork/QNetworkInterface" #include "client/network/update/card_spoiler/spoiler_background_updater.h" #include "client/settings/cache_settings.h" #include "client/sound_engine.h" @@ -36,16 +35,11 @@ #include #include #include -#include -#include #include #include #include -#include #include -#include #include -#include #include QTranslator *translator, *qtTranslator; @@ -266,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/cockatrice/src/main.h b/cockatrice/src/main.h index 487abc0ea..b6e086744 100644 --- a/cockatrice/src/main.h +++ b/cockatrice/src/main.h @@ -8,7 +8,6 @@ #define MAIN_H #include -#include inline Q_LOGGING_CATEGORY(MainLog, "main"); inline Q_LOGGING_CATEGORY(QtTranslatorDebug, "qt_translator"); 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 7a5809b1b..000000000 --- a/dbconverter/src/main.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef MAIN_H -#define MAIN_H - -#include -#include -#include - -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(sets, cards, fileName); - } -}; - -#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..1de6fda4f 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,3 @@ -@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 + +TODO diff --git a/doc/doxygen/extra-pages/developer_documentation/index.md b/doc/doxygen/extra-pages/developer_documentation/index.md index b26faf836..da2d444d8 100644 --- a/doc/doxygen/extra-pages/developer_documentation/index.md +++ b/doc/doxygen/extra-pages/developer_documentation/index.md @@ -1,5 +1,7 @@ @page developer_reference Developer Reference +- @subpage logging + - @subpage primer_cards - @subpage card_database_schema_and_parsing diff --git a/doc/doxygen/extra-pages/developer_documentation/logging.md b/doc/doxygen/extra-pages/developer_documentation/logging.md new file mode 100644 index 000000000..8fd5cb015 --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/logging.md @@ -0,0 +1,184 @@ +@page logging Logging + +Cockatrice uses QtLogging from the QtCore module for its logging. See +the [official documentation](https://doc.qt.io/qt-6/qtlogging.html) for further details. + +# Log Message Pattern + +Any message logged through the QtLogging system automatically conforms to this message pattern: + +Generic: + +``` +[ ] [] - [:] +``` + +Example: + +``` +[2025-12-05 14:48:25.908 I] [MainWindow::startupConfigCheck] - Startup: found config with current version [window_main.cpp:951] +``` + +For more information, see [Logging Setup](#logging-setup). + +# Log Level and Categories + +\note The default log level for the application is info. + +This means that you should only use qInfo() in production-level code if you are truly sure that this message is +beneficial to end-users and other developers. As a general rule, if your functionality logs to info more than twice in +response to a user interaction, you are advised to consider moving some of these logs down to the debug level. + +\warning You are strongly advised to avoid the use of the generic logging macros (e.g. qDebug(), qInfo(), qWarn()). + +\note You should instead use the corresponding category logging macros (qCDebug(), qCInfo(), qCWarn()) and define +logging +categories for your log statements. + +Example: + +```c++ +in .h + +inline Q_LOGGING_CATEGORY(ExampleCategory, "cockatrice_example_category"); +inline Q_LOGGING_CATEGORY(ExampleSubCategory, "cockatrice_example_category.sub_category"); + +in .cpp + +qCInfo(ExampleCategory) << "Info level logs are usually sent through the main category" +qCDebug(ExampleSubCategory) << "Debug level logs are permitted their own category to allow selective silencing" +``` + +For more information on how to enable or disable logging categories, +see [Logging Configuration](#logging-configuration). + +# Logging Configuration + +For configuring our logging, we use the qtlogging.ini, located under cockatrice/resources/config/qtlogging.ini, which is +baked into the application in release version and set as the QT_LOGGING_CONF environment variable in main.cpp. + +```c++ +#ifdef Q_OS_APPLE + // /cockatrice/cockatrice.app/Contents/MacOS/cockatrice + const QByteArray configPath = "../../../qtlogging.ini"; +#elif defined(Q_OS_UNIX) + // /cockatrice/cockatrice + const QByteArray configPath = "./qtlogging.ini"; +#elif defined(Q_OS_WIN) + // /cockatrice/Debug/cockatrice.exe + const QByteArray configPath = "../qtlogging.ini"; +#else + const QByteArray configPath = ""; +#endif + + if (!qEnvironmentVariableIsSet(("QT_LOGGING_CONF"))) { + // Set the QT_LOGGING_CONF environment variable + qputenv("QT_LOGGING_CONF", configPath); + } +``` + +For more information on how to use this file and on how Qt evaluates which logging rules/file to use, please +see the [official Qt documentation](https://doc.qt.io/qt-6/qloggingcategory.html#configuring-categories). + +Some examples: + +``` +# Turn off all logging except everything from card_picture_loader and all sub categories + +[Rules] +# The default log level is info +*.debug = false +*.info = false +*.warning = false +*.critical = false +*.fatal = false + +card_picture_loader.* = true +``` + +``` +# Turn off all logging except info level logs from card_picture_loader and all sub categories + +[Rules] +# The default log level is info +*.debug = false +*.info = false +*.warning = false +*.critical = false +*.fatal = false + +card_picture_loader.*.info = true +``` + +``` +[Rules] +# Turn on debug level logs for card_picture_loader but keep logging for sub categories suppressed +*.debug = false + +card_picture_loader.debug = true +``` + +``` +[Rules] +# Turn on all logs for worker subcategory of card_picture_loader +*.debug = false + +card_picture_loader.worker = true +``` + +``` +[Rules] +# Turn off some noisy and irrelevant startup logging for local development +*.debug = false + +qt_translator = false +window_main.* = false +release_channel = false +spoiler_background_updater = false +theme_manager = false +sound_engine = false +tapped_out_interface = false +card_database = false +card_database.loading = false +card_database.loading.success_or_failure = true +cockatrice_xml.* = false +``` + +# Logging Setup + +This is achieved through our logging setup in @ref main.cpp, where we set the message pattern and install a custom +logger which replaces the full file path at the end with just the file name (Qt shows the full and quite lengthy path by +default). + +```c++ + qSetMessagePattern( + "\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} " + "%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{" + "endif}%{if-fatal}\033[1;31mF%{endif}\033[0m] [%{function}] - %{message} [%{file}:%{line}]"); + QApplication app(argc, argv); + + QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit); + + qInstallMessageHandler(CockatriceLogger); +``` + +```c++ +static void CockatriceLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &message) +{ + QString logMessage = qFormatLogMessage(type, ctx, message); + + // Regular expression to match the full path in the square brackets and extract only the filename and line number + QRegularExpression regex(R"(\[(?:.:)?[\/\\].*[\/\\]([^\/\\]+\:\d+)\])"); + QRegularExpressionMatch match = regex.match(logMessage); + + if (match.hasMatch()) { + // Extract the filename and line number (e.g., "main.cpp:211") + QString filenameLine = match.captured(1); + + // Replace the full path in square brackets with just the filename and line number + logMessage.replace(match.captured(0), QString("[%1]").arg(filenameLine)); + } + + Logger::getInstance().log(type, ctx, logMessage); +} +``` \ No newline at end of file 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/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/CMakeLists.txt b/libcockatrice_card/CMakeLists.txt index 857376163..f516cde00 100644 --- a/libcockatrice_card/CMakeLists.txt +++ b/libcockatrice_card/CMakeLists.txt @@ -41,6 +41,8 @@ add_library( libcockatrice/card/relation/card_relation.cpp libcockatrice/card/set/card_set.cpp libcockatrice/card/set/card_set_list.cpp + libcockatrice/card/format/format_legality_rules.cpp + libcockatrice/card/format/format_legality_rules.h ) target_include_directories( diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index ab1d51a49..00e8fec37 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -1,6 +1,7 @@ #ifndef CARD_INFO_H #define CARD_INFO_H +#include "format/format_legality_rules.h" #include "printing/printing_info.h" #include @@ -10,7 +11,6 @@ #include #include #include -#include #include #include @@ -23,10 +23,12 @@ class ICardDatabaseParser; typedef QSharedPointer CardInfoPtr; typedef QSharedPointer CardSetPtr; +typedef QSharedPointer FormatRulesPtr; typedef QMap> SetToPrintingsMap; typedef QHash CardNameMap; typedef QHash SetNameMap; +typedef QHash FormatRulesNameMap; Q_DECLARE_METATYPE(CardInfoPtr) @@ -159,7 +161,7 @@ public: * * @return Shared pointer to the cloned CardInfo. */ - CardInfoPtr clone() const + [[nodiscard]] CardInfoPtr clone() const { auto newCardInfo = CardInfoPtr(new CardInfo(*this)); newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance @@ -179,19 +181,19 @@ public: } /** @name Basic Properties Accessors */ //@{ - inline const QString &getName() const + [[nodiscard]] inline const QString &getName() const { return name; } - const QString &getSimpleName() const + [[nodiscard]] const QString &getSimpleName() const { return simpleName; } const QSet &getAltNames() { return altNames; - }; - const QString &getText() const + } + [[nodiscard]] const QString &getText() const { return text; } @@ -200,15 +202,15 @@ public: text = _text; emit cardInfoChanged(smartThis); } - bool getIsToken() const + [[nodiscard]] bool getIsToken() const { return isToken; } - QStringList getProperties() const + [[nodiscard]] QStringList getProperties() const { return properties.keys(); } - QString getProperty(const QString &propertyName) const + [[nodiscard]] QString getProperty(const QString &propertyName) const { return properties.value(propertyName).toString(); } @@ -217,34 +219,34 @@ public: properties.insert(_name, _value); emit cardInfoChanged(smartThis); } - bool hasProperty(const QString &propertyName) const + [[nodiscard]] bool hasProperty(const QString &propertyName) const { return properties.contains(propertyName); } - const SetToPrintingsMap &getSets() const + [[nodiscard]] const SetToPrintingsMap &getSets() const { return setsToPrintings; } - const QString &getSetsNames() const + [[nodiscard]] const QString &getSetsNames() const { return setsNames; } //@} /** @name Related Cards Accessors */ //@{ - const QList &getRelatedCards() const + [[nodiscard]] const QList &getRelatedCards() const { return relatedCards; } - const QList &getReverseRelatedCards() const + [[nodiscard]] const QList &getReverseRelatedCards() const { return reverseRelatedCards; } - const QList &getReverseRelatedCards2Me() const + [[nodiscard]] const QList &getReverseRelatedCards2Me() const { return reverseRelatedCardsToMe; } - QList getAllRelatedCards() const + [[nodiscard]] QList getAllRelatedCards() const { QList result; result.append(getRelatedCards()); @@ -259,24 +261,24 @@ public: //@} /** @name UI Positioning */ //@{ - const UiAttributes &getUiAttributes() const + [[nodiscard]] const UiAttributes &getUiAttributes() const { return uiAttributes; } //@} - const QChar getColorChar() const; + [[nodiscard]] const QChar getColorChar() const; /** @name Legacy/Convenience Property Accessors */ //@{ - const QString getCardType() const; + [[nodiscard]] const QString getCardType() const; void setCardType(const QString &value); - const QString getCmc() const; - const QString getColors() const; + [[nodiscard]] const QString getCmc() const; + [[nodiscard]] const QString getColors() const; void setColors(const QString &value); - const QString getLoyalty() const; - const QString getMainCardType() const; - const QString getManaCost() const; - const QString getPowTough() const; + [[nodiscard]] const QString getLoyalty() const; + [[nodiscard]] const QString getMainCardType() const; + [[nodiscard]] const QString getManaCost() const; + [[nodiscard]] const QString getPowTough() const; void setPowTough(const QString &value); //@} @@ -287,7 +289,7 @@ public: * * @return Corrected card name as a QString. */ - QString getCorrectedName() const; + [[nodiscard]] QString getCorrectedName() const; /** * @brief Adds a printing to a specific set. diff --git a/libcockatrice_card/libcockatrice/card/card_info_comparator.h b/libcockatrice_card/libcockatrice/card/card_info_comparator.h index bafc303a5..b8d9c47e6 100644 --- a/libcockatrice_card/libcockatrice/card/card_info_comparator.h +++ b/libcockatrice_card/libcockatrice/card/card_info_comparator.h @@ -9,7 +9,6 @@ #include "card_info.h" -#include #include #include @@ -23,8 +22,8 @@ private: QStringList m_properties; // List of properties to sort by Qt::SortOrder m_order; - QVariant getProperty(const CardInfoPtr &card, const QString &property) const; - bool compareVariants(const QVariant &a, const QVariant &b) const; + [[nodiscard]] QVariant getProperty(const CardInfoPtr &card, const QString &property) const; + [[nodiscard]] bool compareVariants(const QVariant &a, const QVariant &b) const; }; #endif // CARD_INFO_COMPARATOR_H diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.cpp b/libcockatrice_card/libcockatrice/card/database/card_database.cpp index 57b3ce06e..5c4b408b3 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database.cpp @@ -1,12 +1,10 @@ #include "card_database.h" #include "../relation/card_relation.h" -#include "parser/cockatrice_xml_3.h" #include "parser/cockatrice_xml_4.h" #include #include -#include #include #include #include @@ -23,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); @@ -201,3 +199,8 @@ void CardDatabase::notifyEnabledSetsChanged() // inform the carddatabasemodels that they need to re-check their list of cards emit cardDatabaseEnabledSetsChanged(); } + +void CardDatabase::addFormat(FormatRulesPtr format) +{ + formats.insert(format->formatName.toLower(), format); +} \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.h b/libcockatrice_card/libcockatrice/card/database/card_database.h index cb46d6d00..7f8fc39db 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database.h @@ -10,10 +10,8 @@ #include #include #include -#include #include #include -#include #include inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database"); @@ -44,6 +42,8 @@ protected: /// Sets indexed by short name SetNameMap sets; + FormatRulesNameMap formats; + /// Loader responsible for file discovery and parsing CardDatabaseLoader *loader; @@ -143,6 +143,8 @@ public slots: */ void addSet(CardSetPtr set); + void addFormat(FormatRulesPtr format); + /** @brief Loads card databases from configured paths. */ void loadCardDatabases(); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp index f56f6a0b5..716477a59 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp @@ -12,17 +12,19 @@ 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 connect(p, &ICardDatabaseParser::addCard, database, &CardDatabase::addCard, Qt::DirectConnection); connect(p, &ICardDatabaseParser::addSet, database, &CardDatabase::addSet, Qt::DirectConnection); + connect(p, &ICardDatabaseParser::addFormat, database, &CardDatabase::addFormat, Qt::DirectConnection); } // when SettingsCache's path changes, trigger reloads @@ -149,6 +151,6 @@ bool CardDatabaseLoader::saveCustomTokensToFile() } } - availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName); + availableParsers.first()->saveToFile(FormatRulesNameMap(), tmpSets, tmpCards, fileName); return true; } diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h index db1376ac7..861cc95b0 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h @@ -4,9 +4,9 @@ #include #include #include -#include #include #include +#include inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading"); inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure"); @@ -53,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; @@ -110,7 +111,7 @@ private: * @brief Collects custom card database paths recursively. * @return Sorted list of file paths to custom databases. */ - QStringList collectCustomDatabasePaths() const; + [[nodiscard]] QStringList collectCustomDatabasePaths() const; private: CardDatabase *database; /**< Non-owning pointer to the target CardDatabase. */ diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp index 65999e590..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]++; @@ -341,4 +337,27 @@ QMap CardDatabaseQuerier::getAllSubCardTypesWithCount() const } return typeCounts; +} + +FormatRulesPtr CardDatabaseQuerier::getFormat(const QString &formatName) const +{ + return db->formats.value(formatName.toLower()); +} + +QMap CardDatabaseQuerier::getAllFormatsWithCount() const +{ + QMap formatCounts; + + for (const auto &card : db->cards.values()) { + QStringList allProps = card->getProperties(); + + for (const QString &prop : allProps) { + if (prop.startsWith("format-")) { + QString formatName = prop.mid(QStringLiteral("format-").size()); + formatCounts[formatName]++; + } + } + } + + return formatCounts; } \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.h b/libcockatrice_card/libcockatrice/card/database/card_database_querier.h index d9a980f06..ff8d7958b 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_querier.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database_querier.h @@ -214,6 +214,8 @@ public: * @return Map of subtype string to count. */ [[nodiscard]] QMap getAllSubCardTypesWithCount() const; + FormatRulesPtr getFormat(const QString &formatName) const; + QMap getAllFormatsWithCount() const; private: const CardDatabase *db; //!< Card database used for all lookups. 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 35012dbc5..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; /** @@ -38,6 +39,7 @@ public: /** * @brief Saves card and set data to a file. + * @param _formats * @param sets Map of sets to save. * @param cards Map of cards to save. * @param fileName Target file path. @@ -45,7 +47,8 @@ public: * @param sourceVersion Optional version string of the source. * @return true if save succeeded. */ - virtual bool saveToFile(SetNameMap sets, + virtual bool saveToFile(FormatRulesNameMap _formats, + SetNameMap sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl = "unknown", @@ -57,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. @@ -79,6 +83,8 @@ signals: /** Emitted when a set is loaded from the database. */ void addSet(CardSetPtr set); + + void addFormat(FormatRulesPtr format); }; Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser") 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 35e2f3d83..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; @@ -438,12 +443,15 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in return xml; } -bool CockatriceXml3Parser::saveToFile(SetNameMap _sets, +bool CockatriceXml3Parser::saveToFile(FormatRulesNameMap _formats, + SetNameMap _sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl, const QString &sourceVersion) { + Q_UNUSED(_formats); + QFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { return false; 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 a727561ea..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; /** @@ -46,7 +46,8 @@ public: /** * @brief Save sets and cards back to an XML3 file. */ - bool saveToFile(SetNameMap _sets, + bool saveToFile(FormatRulesNameMap _formats, + SetNameMap _sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl = "unknown", 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 ab4c8002d..cc0220526 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #define COCKATRICE_XML4_TAGNAME "cockatrice_carddatabase" @@ -13,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) { } @@ -60,7 +62,9 @@ void CockatriceXml4Parser::parseFile(QIODevice &device) } auto xmlName = xml.name().toString(); - if (xmlName == "sets") { + if (xmlName == "formats") { + loadFormats(xml); + } else if (xmlName == "sets") { loadSetsFromXml(xml); } else if (xmlName == "cards") { loadCardsFromXml(xml); @@ -78,6 +82,116 @@ void CockatriceXml4Parser::parseFile(QIODevice &device) } } +static QSharedPointer parseFormat(QXmlStreamReader &xml) +{ + auto rulesPtr = FormatRulesPtr(new FormatRules()); + + if (xml.attributes().hasAttribute("formatName")) { + rulesPtr->formatName = xml.attributes().value("formatName").toString(); + } + + while (!xml.atEnd()) { + auto token = xml.readNext(); + + if (token == QXmlStreamReader::EndElement && xml.name().toString() == "format") { + break; + } + + if (token != QXmlStreamReader::StartElement) { + continue; + } + + QString xmlName = xml.name().toString(); + + if (xmlName == "minDeckSize") { + rulesPtr->minDeckSize = xml.readElementText().toInt(); + } else if (xmlName == "maxDeckSize") { + QString text = xml.readElementText(); + rulesPtr->maxDeckSize = text.toInt(); + } else if (xmlName == "maxSideboardSize") { + rulesPtr->maxSideboardSize = xml.readElementText().toInt(); + } else if (xmlName == "allowedCounts") { + while (!xml.atEnd()) { + token = xml.readNext(); + + if (token == QXmlStreamReader::EndElement && xml.name().toString() == "allowedCounts") { + break; + } + + if (token == QXmlStreamReader::StartElement && xml.name().toString() == "count") { + + AllowedCount c; + + QString maxAttr = xml.attributes().value("max").toString(); + c.max = (maxAttr == "unlimited") ? -1 : maxAttr.toInt(); + + c.label = xml.readElementText().trimmed(); + + rulesPtr->allowedCounts.append(c); + } + } + } else if (xmlName == "exceptions") { + while (!xml.atEnd()) { + token = xml.readNext(); + + if (token == QXmlStreamReader::EndElement && xml.name().toString() == "exceptions") { + break; + } + + if (token == QXmlStreamReader::StartElement && xml.name().toString() == "exception") { + ExceptionRule ex; + + while (!xml.atEnd()) { + token = xml.readNext(); + + if (token == QXmlStreamReader::EndElement && xml.name().toString() == "exception") { + break; + } + + if (token == QXmlStreamReader::StartElement) { + QString ename = xml.name().toString(); + + if (ename == "maxCopies") { + QString text = xml.readElementText(); + ex.maxCopies = (text == "unlimited") ? -1 : text.toInt(); + } else if (ename == "cardCondition") { + CardCondition cond; + cond.field = xml.attributes().value("field").toString(); + cond.matchType = xml.attributes().value("match").toString(); + cond.value = xml.attributes().value("value").toString(); + ex.conditions.append(cond); + xml.skipCurrentElement(); + } else { + xml.skipCurrentElement(); + } + } + } + + rulesPtr->exceptions.append(ex); + } + } + } else { + xml.skipCurrentElement(); + } + } + + return rulesPtr; +} + +void CockatriceXml4Parser::loadFormats(QXmlStreamReader &xml) +{ + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) { + break; + } + + if (xml.name().toString() == "format") { + auto rulesPtr = parseFormat(xml); + emit addFormat(rulesPtr); + } + } +} + void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml) { while (!xml.atEnd()) { @@ -273,6 +387,59 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) } } +static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const QSharedPointer &rulesPtr) +{ + if (rulesPtr.isNull()) { + qCWarning(CockatriceXml4Log) << "&operator<< FormatRules is nullptr"; + return xml; + } + + const FormatRules &rules = *rulesPtr; + + xml.writeStartElement("format"); + if (!rules.formatName.isEmpty()) { + xml.writeAttribute("formatName", rules.formatName); + } + + xml.writeTextElement("minDeckSize", QString::number(rules.minDeckSize)); + xml.writeTextElement("maxDeckSize", rules.maxDeckSize >= 0 ? QString::number(rules.maxDeckSize) : "0"); + xml.writeTextElement("maxSideboardSize", QString::number(rules.maxSideboardSize)); + if (!rules.allowedCounts.isEmpty()) { + xml.writeStartElement("allowedCounts"); + + for (const AllowedCount &c : rules.allowedCounts) { + xml.writeStartElement("count"); + xml.writeAttribute("max", c.max == -1 ? "unlimited" : QString::number(c.max)); + xml.writeCharacters(c.label); + xml.writeEndElement(); // count + } + + xml.writeEndElement(); // allowedCounts + } + + if (!rules.exceptions.isEmpty()) { + xml.writeStartElement("exceptions"); + for (const ExceptionRule &ex : rules.exceptions) { + xml.writeStartElement("exception"); + xml.writeTextElement("maxCopies", ex.maxCopies == -1 ? "unlimited" : QString::number(ex.maxCopies)); + + for (const CardCondition &cond : ex.conditions) { + xml.writeStartElement("cardCondition"); + xml.writeAttribute("field", cond.field); + xml.writeAttribute("match", cond.matchType); + xml.writeAttribute("value", cond.value); + xml.writeEndElement(); // cardCondition + } + + xml.writeEndElement(); // exception + } + xml.writeEndElement(); // exceptions + } + + xml.writeEndElement(); // format + return xml; +} + static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set) { if (set.isNull()) { @@ -399,7 +566,8 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in return xml; } -bool CockatriceXml4Parser::saveToFile(SetNameMap _sets, +bool CockatriceXml4Parser::saveToFile(FormatRulesNameMap _formats, + SetNameMap _sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl, @@ -426,6 +594,14 @@ bool CockatriceXml4Parser::saveToFile(SetNameMap _sets, xml.writeTextElement("sourceVersion", sourceVersion); xml.writeEndElement(); + if (_formats.count() > 0) { + xml.writeStartElement("formats"); + for (FormatRulesPtr format : _formats) { + xml << format; + } + xml.writeEndElement(); + } + if (_sets.count() > 0) { xml.writeStartElement("sets"); for (CardSetPtr set : _sets) { 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 b83f6929e..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; /** @@ -49,7 +50,8 @@ public: /** * @brief Save sets and cards back to an XML4 file. */ - bool saveToFile(SetNameMap _sets, + bool saveToFile(FormatRulesNameMap _formats, + SetNameMap _sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl = "unknown", @@ -72,6 +74,7 @@ private: */ void loadCardsFromXml(QXmlStreamReader &xml); + void loadFormats(QXmlStreamReader &xml); /** * @brief Load all elements from the XML stream. * @param xml The open QXmlStreamReader positioned at the element. diff --git a/libcockatrice_card/libcockatrice/card/format/format_legality_rules.cpp b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.cpp new file mode 100644 index 000000000..a6b3bb038 --- /dev/null +++ b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.cpp @@ -0,0 +1,53 @@ +#include "format_legality_rules.h" + +#include + +bool cardMatchesCondition(const CardInfo &card, const CardCondition &cond) +{ + CardMatchType type = matchTypeFromString(cond.matchType); + QString fieldValue; + if (cond.field == "name") { + fieldValue = card.getName(); + } else if (cond.field == "text") { + fieldValue = card.getText(); + } else { + fieldValue = card.getProperty(cond.field); + } + + switch (type) { + case CardMatchType::Equals: + return fieldValue == cond.value; + case CardMatchType::NotEquals: + return fieldValue != cond.value; + case CardMatchType::Contains: + return fieldValue.contains(cond.value, Qt::CaseInsensitive); + case CardMatchType::NotContains: + return !fieldValue.contains(cond.value, Qt::CaseInsensitive); + case CardMatchType::Regex: { + QRegularExpression re(cond.value, QRegularExpression::CaseInsensitiveOption); + return re.match(fieldValue).hasMatch(); + } + default: + return false; + } +} + +bool exceptionAppliesToCard(const CardInfo &card, const ExceptionRule &rule) +{ + for (const CardCondition &cond : rule.conditions) { + if (!cardMatchesCondition(card, cond)) { + return false; // all conditions must match + } + } + return true; +} + +bool cardHasAnyException(const CardInfo &card, const FormatRules &format) +{ + for (const ExceptionRule &rule : format.exceptions) { + if (exceptionAppliesToCard(card, rule)) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h new file mode 100644 index 000000000..16f2359ab --- /dev/null +++ b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h @@ -0,0 +1,73 @@ +#ifndef COCKATRICE_FORMAT_LEGALITY_RULES_H +#define COCKATRICE_FORMAT_LEGALITY_RULES_H + +#include +#include +#include + +class CardInfo; +using CardInfoPtr = QSharedPointer; + +struct CardCondition +{ + QString field; // e.g. "type", "maintype", "text" + QString matchType; // "contains", "equals", "regex", "notContains", etc. + QString value; // e.g. "Basic Land" +}; + +struct AllowedCount +{ + int max = 0; // 4, 1, 0, or -1 for unlimited + QString label; // "legal", "restricted", "banned" +}; + +struct ExceptionRule +{ + QList conditions; // All must match + int maxCopies = -1; // -1 = unlimited +}; + +struct FormatRules +{ + QString formatName; + int minDeckSize = 60; + int maxDeckSize = -1; // -1 = unlimited + int maxSideboardSize = 15; + + QList allowedCounts; + + QList exceptions; // Cards allowed to break maxCopies +}; + +enum class CardMatchType +{ + Equals, + NotEquals, + Contains, + NotContains, + Regex +}; + +// convert string to enum +inline CardMatchType matchTypeFromString(const QString &str) +{ + if (str == "equals") + return CardMatchType::Equals; + if (str == "notEquals") + return CardMatchType::NotEquals; + if (str == "contains") + return CardMatchType::Contains; + if (str == "notContains") + return CardMatchType::NotContains; + if (str == "regex") + return CardMatchType::Regex; + return CardMatchType::Equals; // fallback default +} + +bool cardMatchesCondition(const CardInfo &card, const CardCondition &cond); + +bool exceptionAppliesToCard(const CardInfo &card, const ExceptionRule &rule); + +bool cardHasAnyException(const CardInfo &card, const FormatRules &format); + +#endif // COCKATRICE_FORMAT_LEGALITY_RULES_H diff --git a/libcockatrice_card/libcockatrice/card/game_specific_terms.h b/libcockatrice_card/libcockatrice/card/game_specific_terms.h index 8f13039da..2931365ad 100644 --- a/libcockatrice_card/libcockatrice/card/game_specific_terms.h +++ b/libcockatrice_card/libcockatrice/card/game_specific_terms.h @@ -53,6 +53,6 @@ inline static const QString getNicePropertyName(QString key) return QCoreApplication::translate("Mtg", "Color Identity"); return key; } -}; // namespace Mtg +} // namespace Mtg #endif diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h index 4329ca2f4..43d82a9cb 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h @@ -5,7 +5,6 @@ #include #include -#include #include class PrintingInfo; @@ -65,7 +64,7 @@ public: * * @return Pointer to the associated CardSet. */ - CardSetPtr getSet() const + [[nodiscard]] CardSetPtr getSet() const { return set; } @@ -75,7 +74,7 @@ public: * * @return List of keys stored in the properties map. */ - QStringList getProperties() const + [[nodiscard]] QStringList getProperties() const { return properties.keys(); } @@ -86,7 +85,7 @@ public: * @param propertyName The key name of the property to query. * @return The property value as a string, or an empty string if not set. */ - QString getProperty(const QString &propertyName) const + [[nodiscard]] QString getProperty(const QString &propertyName) const { return properties.value(propertyName).toString(); } @@ -109,14 +108,14 @@ public: * * @return A string representing the providerID. */ - QString getUuid() const; + [[nodiscard]] QString getUuid() const; /** * @brief Returns the flavorName for this printing. * * @return The flavorName, or empty if it isn't present. */ - QString getFlavorName() const; + [[nodiscard]] QString getFlavorName() const; }; #endif // COCKATRICE_PRINTING_INFO_H diff --git a/libcockatrice_card/libcockatrice/card/set/card_set.h b/libcockatrice_card/libcockatrice/card/set/card_set.h index 5fc769bb6..fe4b66522 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set.h @@ -105,34 +105,34 @@ public: * * @return Sanitized short name. */ - QString getCorrectedShortName() const; + [[nodiscard]] QString getCorrectedShortName() const; /// @return Short identifier of the set. - QString getShortName() const + [[nodiscard]] QString getShortName() const { return shortName; } /// @return Descriptive name of the set. - QString getLongName() const + [[nodiscard]] QString getLongName() const { return longName; } /// @return Type/category string of the set. - QString getSetType() const + [[nodiscard]] QString getSetType() const { return setType; } /// @return Release date of the set. - QDate getReleaseDate() const + [[nodiscard]] QDate getReleaseDate() const { return releaseDate; } /// @return Priority level of the set. - Priority getPriority() const + [[nodiscard]] Priority getPriority() const { return priority; } @@ -182,7 +182,7 @@ public: void loadSetOptions(); /// @return The sort key assigned to this set. - int getSortKey() const + [[nodiscard]] int getSortKey() const { return sortKey; } @@ -194,7 +194,7 @@ public: void setSortKey(unsigned int _sortKey); /// @return True if the set is enabled. - bool getEnabled() const + [[nodiscard]] bool getEnabled() const { return enabled; } @@ -206,7 +206,7 @@ public: void setEnabled(bool _enabled); /// @return True if the set is considered known. - bool getIsKnown() const + [[nodiscard]] bool getIsKnown() const { return isknown; } @@ -222,7 +222,7 @@ public: * * @return True if the long name, type, and release date are all empty. */ - bool getIsKnownIgnored() const + [[nodiscard]] bool getIsKnownIgnored() const { return longName.length() + setType.length() + releaseDate.toString().length() == 0; } diff --git a/libcockatrice_card/libcockatrice/card/set/card_set_list.h b/libcockatrice_card/libcockatrice/card/set/card_set_list.h index 21b4a55d4..81d605374 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set_list.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set_list.h @@ -4,7 +4,6 @@ #include "card_set.h" #include -#include /** * @class CardSetList diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index d9b27354b..a7dd01702 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -3,13 +3,13 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(HEADERS - libcockatrice/deck_list/abstract_deck_list_card_node.h - libcockatrice/deck_list/abstract_deck_list_node.h + libcockatrice/deck_list/tree/abstract_deck_list_card_node.h + libcockatrice/deck_list/tree/abstract_deck_list_node.h + libcockatrice/deck_list/tree/deck_list_card_node.h + libcockatrice/deck_list/tree/inner_deck_list_node.h libcockatrice/deck_list/deck_list.h - libcockatrice/deck_list/deck_list_card_node.h libcockatrice/deck_list/deck_list_history_manager.h libcockatrice/deck_list/deck_list_memento.h - libcockatrice/deck_list/inner_deck_list_node.h ) if(Qt6_FOUND) @@ -21,12 +21,14 @@ endif() add_library( libcockatrice_deck_list STATIC ${MOC_SOURCES} - libcockatrice/deck_list/abstract_deck_list_card_node.cpp - libcockatrice/deck_list/abstract_deck_list_node.cpp + libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp + libcockatrice/deck_list/tree/abstract_deck_list_node.cpp + libcockatrice/deck_list/tree/deck_list_card_node.cpp + libcockatrice/deck_list/tree/inner_deck_list_node.cpp libcockatrice/deck_list/deck_list.cpp - libcockatrice/deck_list/deck_list_card_node.cpp libcockatrice/deck_list/deck_list_history_manager.cpp - libcockatrice/deck_list/inner_deck_list_node.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 e26e55e08..b453a57ef 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -1,14 +1,15 @@ #include "deck_list.h" -#include "abstract_deck_list_node.h" -#include "deck_list_card_node.h" #include "deck_list_memento.h" -#include "inner_deck_list_node.h" +#include "tree/abstract_deck_list_node.h" +#include "tree/deck_list_card_node.h" +#include "tree/inner_deck_list_node.h" #include #include #include #include +#include #include #include @@ -76,21 +77,29 @@ void SideboardPlan::write(QXmlStreamWriter *xml) xml->writeEndElement(); } +bool DeckList::Metadata::isEmpty() const +{ + return name.isEmpty() && comments.isEmpty() && bannerCard.isEmpty() && tags.isEmpty(); +} + DeckList::DeckList() { - root = new InnerDecklistNode; } 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(); @@ -121,25 +130,26 @@ bool DeckList::readElement(QXmlStreamReader *xml) const QString childName = xml->name().toString(); if (xml->isStartElement()) { if (childName == "lastLoadedTimestamp") { - lastLoadedTimestamp = xml->readElementText(); + metadata.lastLoadedTimestamp = xml->readElementText(); } else if (childName == "deckname") { - name = xml->readElementText(); + metadata.name = xml->readElementText(); + } else if (childName == "format") { + metadata.gameFormat = xml->readElementText(); } else if (childName == "comments") { - comments = xml->readElementText(); + metadata.comments = xml->readElementText(); } else if (childName == "bannerCard") { QString providerId = xml->attributes().value("providerId").toString(); QString cardName = xml->readElementText(); - bannerCard = {cardName, providerId}; + metadata.bannerCard = {cardName, providerId}; } else if (childName == "tags") { - tags.clear(); // Clear existing tags + metadata.tags.clear(); // Clear existing tags while (xml->readNextStartElement()) { if (xml->name().toString() == "tag") { - tags.append(xml->readElementText()); + metadata.tags.append(xml->readElementText()); } } } 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)) { @@ -154,29 +164,34 @@ bool DeckList::readElement(QXmlStreamReader *xml) return true; } +static void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metadata) +{ + xml->writeTextElement("lastLoadedTimestamp", metadata.lastLoadedTimestamp); + xml->writeTextElement("deckname", metadata.name); + xml->writeTextElement("format", metadata.gameFormat); + xml->writeStartElement("bannerCard"); + xml->writeAttribute("providerId", metadata.bannerCard.providerId); + xml->writeCharacters(metadata.bannerCard.name); + xml->writeEndElement(); + xml->writeTextElement("comments", metadata.comments); + + // Write tags + xml->writeStartElement("tags"); + for (const QString &tag : metadata.tags) { + xml->writeTextElement("tag", tag); + } + xml->writeEndElement(); +} + void DeckList::write(QXmlStreamWriter *xml) const { xml->writeStartElement("cockatrice_deck"); xml->writeAttribute("version", "1"); - xml->writeTextElement("lastLoadedTimestamp", lastLoadedTimestamp); - xml->writeTextElement("deckname", name); - xml->writeStartElement("bannerCard"); - xml->writeAttribute("providerId", bannerCard.providerId); - xml->writeCharacters(bannerCard.name); - xml->writeEndElement(); - xml->writeTextElement("comments", comments); - // Write tags - xml->writeStartElement("tags"); - for (const QString &tag : tags) { - xml->writeTextElement("tag", tag); - } - xml->writeEndElement(); + 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); @@ -328,7 +343,7 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) const auto ¤t = inputs.at(index++); if (!current.contains(reEmpty)) { match = reComment.match(current); - name = match.captured(); + metadata.name = match.captured(); break; } } @@ -336,10 +351,10 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) const auto ¤t = inputs.at(index++); if (!current.contains(reEmpty)) { match = reComment.match(current); - comments += match.captured() + '\n'; + metadata.comments += match.captured() + '\n'; } } - comments.chop(1); + metadata.comments.chop(1); // Discard empty lines while (index < max_line && inputs.at(index).contains(reEmpty)) { @@ -431,32 +446,17 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) cardName.replace(diff.key(), diff.value()); } - // Resolve complete card name, this function does nothing if the name is not found - cardName = getCompleteCardName(cardName); - // Determine the zone (mainboard/sideboard) - QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN); + 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); @@ -501,186 +501,70 @@ QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappe */ void DeckList::cleanList(bool preserveMetadata) { - root->clearTree(); + tree.clear(); if (!preserveMetadata) { - setName(); - setComments(); - setTags(); + metadata = {}; } refreshDeckHash(); } -void DeckList::getCardListHelper(InnerDecklistNode *item, QSet &result) -{ - for (int i = 0; i < item->size(); ++i) { - auto *node = dynamic_cast(item->at(i)); - - if (node) { - result.insert(node->getName()); - } else { - getCardListHelper(dynamic_cast(item->at(i)), result); - } - } -} - -void DeckList::getCardRefListHelper(InnerDecklistNode *item, QList &result) -{ - for (int i = 0; i < item->size(); ++i) { - auto *node = dynamic_cast(item->at(i)); - - if (node) { - result.append(node->toCardRef()); - } else { - getCardRefListHelper(dynamic_cast(item->at(i)), result); - } - } -} - QStringList DeckList::getCardList() const { - QSet result; - getCardListHelper(root, result); - return result.values(); + auto nodes = tree.getCardNodes(); + + QStringList result; + std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), [](auto node) { return node->getName(); }); + + return result; } QList DeckList::getCardRefList() const { + auto nodes = tree.getCardNodes(); + QList result; - getCardRefListHelper(root, result); + std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), + [](auto node) { return node->toCardRef(); }); + return result; } -QList DeckList::getCardNodes(const QStringList &restrictToZones) const +QList DeckList::getCardNodes(const QSet &restrictToZones) const { - QList result; + return tree.getCardNodes(restrictToZones); +} - for (auto *node : *root) { - auto *zoneNode = dynamic_cast(node); - if (zoneNode == nullptr) { - continue; - } - if (!restrictToZones.isEmpty() && !restrictToZones.contains(node->getName())) { - continue; - } - for (auto *cardNode : *zoneNode) { - auto *cardCardNode = dynamic_cast(cardNode); - if (cardCardNode != nullptr) { - result.append(cardCardNode); - } - } - } - - return result; +QList DeckList::getZoneNodes() const +{ + return tree.getZoneNodes(); } int DeckList::getSideboardSize() const { - int size = 0; - for (int i = 0; i < root->size(); ++i) { - auto *node = dynamic_cast(root->at(i)); - if (node->getName() != DECK_ZONE_SIDE) { - continue; - } + auto cards = tree.getCardNodes({DECK_ZONE_SIDE}); - for (int j = 0; j < node->size(); j++) { - auto *card = dynamic_cast(node->at(j)); - size += card->getNumber(); - } + int size = 0; + for (auto card : cards) { + size += card->getNumber(); } + return size; } DecklistCardNode *DeckList::addCard(const QString &cardName, const QString &zoneName, - const int position, + int position, const QString &cardSetName, const QString &cardSetCollectorNumber, - const QString &cardProviderId) + const QString &cardProviderId, + 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); + 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 InnerDecklistNode *root) -{ - QStringList cardList; - QSet hashZones, optionalZones; - - hashZones << DECK_ZONE_MAIN << DECK_ZONE_SIDE; // Zones in deck to be included in hashing process - optionalZones << DECK_ZONE_TOKENS; // Optional zones in deck not included in hashing process - - for (int i = 0; i < root->size(); i++) { - auto *node = dynamic_cast(root->at(i)); - for (int j = 0; j < node->size(); j++) { - if (hashZones.contains(node->getName())) // Mainboard or Sideboard - { - auto *card = dynamic_cast(node->at(j)); - for (int k = 0; k < card->getNumber(); ++k) { - cardList.append((node->getName() == DECK_ZONE_SIDE ? "SB:" : "") + card->getName().toLower()); - } - } - } - } - 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,17 +577,16 @@ QString DeckList::getDeckHash() const return cachedDeckHash; } - cachedDeckHash = computeDeckHash(root); + cachedDeckHash = tree.computeDeckHash(); return cachedDeckHash; } /** - * Invalidates the cached deckHash and emits the deckHashChanged signal. + * Invalidates the cached deckHash. */ void DeckList::refreshDeckHash() { cachedDeckHash = QString(); - emit deckHashChanged(); } /** @@ -711,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 1be673fcf..e8c57be67 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -1,5 +1,5 @@ /** - * @file decklist.h + * @file deck_list.h * @brief Defines the DeckList class and supporting types for managing a full * deck structure including cards, zones, sideboard plans, and * serialization to/from multiple formats. This is a logic class which @@ -11,12 +11,12 @@ #define DECKLIST_H #include "deck_list_memento.h" -#include "inner_deck_list_node.h" +#include "deck_list_node_tree.h" +#include "tree/inner_deck_list_node.h" #include #include #include -#include #include #include @@ -94,7 +94,7 @@ public: * @brief Represents a complete deck, including metadata, zones, cards, * and sideboard plans. * - * A DeckList is a QObject wrapper around an `InnerDecklistNode` tree, + * A DeckList is a wrapper around an `InnerDecklistNode` tree, * enriched with metadata like deck name, comments, tags, banner card, * and multiple sideboard plans. * @@ -108,33 +108,40 @@ public: * - Provide hashing for deck identity (deck hash). * * ### Ownership: - * - Owns the root `InnerDecklistNode` tree. + * - Owns the `DecklistNodeTree`. * - Owns `SideboardPlan` instances stored in `sideboardPlans`. * - * ### Signals: - * - @c deckHashChanged() — emitted when the deck contents change. - * - @c deckTagsChanged() — emitted when tags are added/removed. - * * ### 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); * ``` */ -class DeckList : public QObject +class DeckList { - Q_OBJECT +public: + struct Metadata + { + QString name; ///< User-defined deck name. + QString comments; ///< Free-form comments or notes. + QString gameFormat; ///< The name of the game format this deck contains legal cards for + CardRef bannerCard; ///< Optional representative card for the deck. + QStringList tags; ///< User-defined tags for deck classification. + QString lastLoadedTimestamp; ///< Timestamp string of last load. + + /** + * @brief Checks if all values (except for lastLoadedTimestamp) in the metadata is empty. + */ + bool isEmpty() const; + }; + private: - QString name; ///< User-defined deck name. - QString comments; ///< Free-form comments or notes. - CardRef bannerCard; ///< Optional representative card for the deck. - QString lastLoadedTimestamp; ///< Timestamp string of last load. - QStringList tags; ///< User-defined tags for deck classification. + 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. @@ -142,114 +149,100 @@ private: */ mutable QString cachedDeckHash; - // Helpers for traversing the tree - static void getCardListHelper(InnerDecklistNode *node, QSet &result); - static void getCardRefListHelper(InnerDecklistNode *item, QList &result); - InnerDecklistNode *getZoneObjFromName(const QString &zoneName); - -protected: - /** - * @brief Map a card name to its zone. - * Override in subclasses for format-specific logic. - * @param cardName Card being placed. - * @param currentZoneName Zone candidate. - * @return Zone name to use. - */ - virtual QString getCardZoneFromName(const QString /*cardName*/, QString currentZoneName) - { - return currentZoneName; - }; - - /** - * @brief Produce the complete display name of a card. - * Override in subclasses to add set suffixes or annotations. - * @param cardName Base name. - * @return Full display name. - */ - virtual QString getCompleteCardName(const QString &cardName) const - { - return cardName; - }; - -signals: - /// Emitted when the deck hash changes. - void deckHashChanged(); - /// Emitted when the deck tags are modified. - void deckTagsChanged(); - -public slots: +public: /// @name Metadata setters ///@{ void setName(const QString &_name = QString()) { - name = _name; + metadata.name = _name; } void setComments(const QString &_comments = QString()) { - comments = _comments; + metadata.comments = _comments; } void setTags(const QStringList &_tags = QStringList()) { - tags = _tags; - emit deckTagsChanged(); + metadata.tags = _tags; } void addTag(const QString &_tag) { - tags.append(_tag); - emit deckTagsChanged(); + metadata.tags.append(_tag); } void clearTags() { - tags.clear(); - emit deckTagsChanged(); + metadata.tags.clear(); } void setBannerCard(const CardRef &_bannerCard = {}) { - bannerCard = _bannerCard; + metadata.bannerCard = _bannerCard; } void setLastLoadedTimestamp(const QString &_lastLoadedTimestamp = QString()) { - lastLoadedTimestamp = _lastLoadedTimestamp; + metadata.lastLoadedTimestamp = _lastLoadedTimestamp; + } + void setGameFormat(const QString &_gameFormat = QString()) + { + metadata.gameFormat = _gameFormat; } ///@} -public: /// @brief Construct an empty deck. explicit DeckList(); - /// @brief Delete copy constructor. - DeckList(const DeckList &) = delete; - DeckList &operator=(const DeckList &) = delete; /// @brief Construct from a serialized native-format string. explicit DeckList(const QString &nativeString); - ~DeckList() override; + /// @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. + const Metadata &getMetadata() const + { + return metadata; + } QString getName() const { - return name; + return metadata.name; } QString getComments() const { - return comments; + return metadata.comments; } QStringList getTags() const { - return tags; + return metadata.tags; } CardRef getBannerCard() const { - return bannerCard; + return metadata.bannerCard; } QString getLastLoadedTimestamp() const { - return lastLoadedTimestamp; + return metadata.lastLoadedTimestamp; + } + QString getGameFormat() const + { + return metadata.gameFormat; } ///@} bool isBlankDeck() const { - return name.isEmpty() && comments.isEmpty() && getCardList().isEmpty(); + return metadata.isEmpty() && getCardList().isEmpty(); } /// @name Sideboard plans @@ -287,23 +280,21 @@ public: void cleanList(bool preserveMetadata = false); bool isEmpty() const { - return root->isEmpty() && name.isEmpty() && comments.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()); - bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); + const QString &cardProviderId = QString(), + const bool formatLegal = true); ///@} /// @name Deck identity diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h index f30c0affe..e6bd27e2d 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h @@ -23,12 +23,12 @@ public: void clear(); - bool canUndo() const + [[nodiscard]] bool canUndo() const { return !undoStack.isEmpty(); } - bool canRedo() const + [[nodiscard]] bool canRedo() const { return !redoStack.isEmpty(); } @@ -37,11 +37,11 @@ public: void redo(DeckList *deck); - QStack getRedoStack() const + [[nodiscard]] QStack getRedoStack() const { return redoStack; } - QStack getUndoStack() const + [[nodiscard]] QStack getUndoStack() const { return undoStack; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h index d2e36b804..6ddf430c5 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h @@ -11,11 +11,11 @@ public: { } - QString getMemento() const + [[nodiscard]] QString getMemento() const { return memento; } - QString getReason() const + [[nodiscard]] QString getReason() const { return reason; } 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_deck_list/libcockatrice/deck_list/abstract_deck_list_card_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_card_node.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_card_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h similarity index 95% rename from libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_card_node.h rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h index 1d201a4c1..88d8b0930 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_card_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h @@ -88,6 +88,12 @@ public: /// @param _cardSetNumber Set the collector number. virtual void setCardCollectorNumber(const QString &_cardSetNumber) = 0; + /// @return The format legality of the card + virtual bool getFormatLegality() const = 0; + + /// @param _formatLegal If the card is considered legal + virtual void setFormatLegality(const bool _formatLegal) = 0; + /** * @brief Get the height of this node in the tree. * diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.cpp similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.cpp diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h similarity index 99% rename from libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.h rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h index 31e7b165e..877705705 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h @@ -17,7 +17,6 @@ #ifndef COCKATRICE_ABSTRACT_DECK_LIST_NODE_H #define COCKATRICE_ABSTRACT_DECK_LIST_NODE_H -#include #include /** diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.cpp similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.cpp diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h similarity index 85% rename from libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.h rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h index 6d61ed970..b3d42b89a 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h @@ -51,6 +51,7 @@ class DecklistCardNode : public AbstractDecklistCardNode QString cardSetShortName; ///< Short set code (e.g., "NEO"). QString cardSetNumber; ///< Collector number within the set. QString cardProviderId; ///< External provider identifier (e.g., UUID). + bool formatLegal; ///< Format legality public: /** @@ -63,6 +64,7 @@ public: * @param _cardSetShortName Short set code (e.g., "NEO"). * @param _cardSetNumber Collector number within the set. * @param _cardProviderId External provider ID (e.g., UUID). + * @param _formatLegality If the card is legal in the format * * On construction, if a parent is provided, this node is inserted into * the parent’s children list automatically. @@ -73,10 +75,11 @@ public: int position = -1, QString _cardSetShortName = QString(), QString _cardSetNumber = QString(), - QString _cardProviderId = QString()) + QString _cardProviderId = QString(), + bool _formatLegality = true) : AbstractDecklistCardNode(_parent, position), name(std::move(_name)), number(_number), cardSetShortName(std::move(_cardSetShortName)), cardSetNumber(std::move(_cardSetNumber)), - cardProviderId(std::move(_cardProviderId)) + cardProviderId(std::move(_cardProviderId)), formatLegal(_formatLegality) { } @@ -91,7 +94,7 @@ public: explicit DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent); /// @return The quantity of this card. - int getNumber() const override + [[nodiscard]] int getNumber() const override { return number; } @@ -103,7 +106,7 @@ public: } /// @return The display name of this card. - QString getName() const override + [[nodiscard]] QString getName() const override { return name; } @@ -115,7 +118,7 @@ public: } /// @return The provider identifier for this card. - QString getCardProviderId() const override + [[nodiscard]] QString getCardProviderId() const override { return cardProviderId; } @@ -127,7 +130,7 @@ public: } /// @return The short set code (e.g., "NEO"). - QString getCardSetShortName() const override + [[nodiscard]] QString getCardSetShortName() const override { return cardSetShortName; } @@ -139,7 +142,7 @@ public: } /// @return The collector number of this card within its set. - QString getCardCollectorNumber() const override + [[nodiscard]] QString getCardCollectorNumber() const override { return cardSetNumber; } @@ -150,6 +153,18 @@ public: cardSetNumber = _cardSetNumber; } + /// @return The format legality of the card + [[nodiscard]] bool getFormatLegality() const override + { + return formatLegal; + } + + /// @param _formatLegal If the card is considered legal + void setFormatLegality(const bool _formatLegal) override + { + formatLegal = _formatLegal; + } + /// @return Always false; card nodes are not deck headers. [[nodiscard]] bool isDeckHeader() const override { @@ -162,7 +177,7 @@ public: * @return A CardRef with the card’s name and provider ID, suitable * for database lookups or comparison with other card sources. */ - CardRef toCardRef() const + [[nodiscard]] CardRef toCardRef() const { return {name, cardProviderId}; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.h rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp index d49a1b9cb..5fdce7ae0 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp @@ -60,6 +60,8 @@ const QString CardFilter::attrName(Attr a) switch (a) { case AttrName: return tr("Name"); + case AttrNameExact: + return tr("Name (Exact)"); case AttrType: return tr("Type"); case AttrColor: diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.h b/libcockatrice_filters/libcockatrice/filters/filter_card.h index 58dbf954b..732e43a09 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.h @@ -9,7 +9,6 @@ #include #include -#include class CardFilter : public QObject { @@ -34,6 +33,7 @@ public: AttrLoyalty, AttrManaCost, AttrName, + AttrNameExact, AttrPow, AttrRarity, AttrSet, @@ -56,20 +56,20 @@ public: { } - Type type() const + [[nodiscard]] Type type() const { return t; } - const QString &term() const + [[nodiscard]] const QString &term() const { return trm; } - Attr attr() const + [[nodiscard]] Attr attr() const { return a; } - QJsonObject toJson() const; + [[nodiscard]] QJsonObject toJson() const; static CardFilter *fromJson(const QJsonObject &json); static const QString typeName(Type t); static const QString attrName(Attr a); diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.h b/libcockatrice_filters/libcockatrice/filters/filter_string.h index 4e6cb38bf..8fc41ce68 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.h @@ -35,7 +35,7 @@ class FilterString public: FilterString(); explicit FilterString(const QString &exp); - bool check(const CardData &card) const + [[nodiscard]] bool check(const CardData &card) const { if (card.isNull()) { static CardInfoPtr blankCard = CardInfo::newInstance(""); diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp index 89b772b8e..afb2d8563 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp @@ -153,6 +153,11 @@ bool FilterItem::acceptName(const CardInfoPtr info) const return info->getName().contains(term, Qt::CaseInsensitive); } +bool FilterItem::acceptNameExact(const CardInfoPtr info) const +{ + return info->getName() == term; +} + bool FilterItem::acceptType(const CardInfoPtr info) const { return info->getCardType().contains(term, Qt::CaseInsensitive); @@ -401,6 +406,8 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) c switch (attr) { case CardFilter::AttrName: return acceptName(info); + case CardFilter::AttrNameExact: + return acceptNameExact(info); case CardFilter::AttrType: return acceptType(info); case CardFilter::AttrColor: diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index 350fd2f14..75d4241a5 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h @@ -10,7 +10,6 @@ #include "filter_card.h" #include -#include #include #include #include @@ -24,7 +23,7 @@ public: FilterTreeNode() : enabled(true) { } - virtual bool isEnabled() const + [[nodiscard]] virtual bool isEnabled() const { return enabled; } @@ -38,18 +37,18 @@ public: enabled = false; nodeChanged(); } - virtual FilterTreeNode *parent() const + [[nodiscard]] virtual FilterTreeNode *parent() const { return nullptr; } - virtual FilterTreeNode *nodeAt(int /* i */) const + [[nodiscard]] virtual FilterTreeNode *nodeAt(int /* i */) const { return nullptr; } virtual void deleteAt(int /* i */) { } - virtual int childCount() const + [[nodiscard]] virtual int childCount() const { return 0; } @@ -57,15 +56,15 @@ public: { return -1; } - virtual int index() const + [[nodiscard]] virtual int index() const { return (parent() != nullptr) ? parent()->childIndex(this) : -1; } - virtual const QString text() const + [[nodiscard]] virtual const QString text() const { return QString(""); } - virtual bool isLeaf() const + [[nodiscard]] virtual bool isLeaf() const { return false; } @@ -104,9 +103,9 @@ protected: public: virtual ~FilterTreeBranch(); void removeFiltersByAttr(CardFilter::Attr filterType); - FilterTreeNode *nodeAt(int i) const override; + [[nodiscard]] FilterTreeNode *nodeAt(int i) const override; void deleteAt(int i) override; - int childCount() const override + [[nodiscard]] int childCount() const override { return childNodes.size(); } @@ -127,10 +126,10 @@ public: LogicMap(CardFilter::Attr a, FilterTree *parent) : p(parent), attr(a) { } - const FilterItemList *findTypeList(CardFilter::Type type) const; + [[nodiscard]] const FilterItemList *findTypeList(CardFilter::Type type) const; FilterItemList *typeList(CardFilter::Type type); - FilterTreeNode *parent() const override; - const QString text() const override + [[nodiscard]] FilterTreeNode *parent() const override; + [[nodiscard]] const QString text() const override { return CardFilter::attrName(attr); } @@ -148,25 +147,25 @@ public: FilterItemList(CardFilter::Type t, LogicMap *parent) : p(parent), type(t) { } - CardFilter::Attr attr() const + [[nodiscard]] CardFilter::Attr attr() const { return p->attr; } - FilterTreeNode *parent() const override + [[nodiscard]] FilterTreeNode *parent() const override { return p; } - int termIndex(const QString &term) const; + [[nodiscard]] int termIndex(const QString &term) const; FilterTreeNode *termNode(const QString &term); - const QString text() const override + [[nodiscard]] const QString text() const override { return CardFilter::typeName(type); } - bool testTypeAnd(CardInfoPtr info, CardFilter::Attr attr) const; - bool testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr) const; - bool testTypeOr(CardInfoPtr info, CardFilter::Attr attr) const; - bool testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool testTypeAnd(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool testTypeOr(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr) const; }; class FilterItem : public FilterTreeNode @@ -182,42 +181,43 @@ public: } virtual ~FilterItem() = default; - CardFilter::Attr attr() const + [[nodiscard]] CardFilter::Attr attr() const { return p->attr(); } - CardFilter::Type type() const + [[nodiscard]] CardFilter::Type type() const { return p->type; } - FilterTreeNode *parent() const override + [[nodiscard]] FilterTreeNode *parent() const override { return p; } - const QString text() const override + [[nodiscard]] const QString text() const override { return term; } - bool isLeaf() const override + [[nodiscard]] bool isLeaf() const override { return true; } - bool acceptName(CardInfoPtr info) const; - bool acceptType(CardInfoPtr info) const; - bool acceptMainType(CardInfoPtr info) const; - bool acceptSubType(CardInfoPtr info) const; - bool acceptColor(CardInfoPtr info) const; - bool acceptText(CardInfoPtr info) const; - bool acceptSet(CardInfoPtr info) const; - bool acceptManaCost(CardInfoPtr info) const; - bool acceptCmc(CardInfoPtr info) const; - bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const; - bool acceptLoyalty(CardInfoPtr info) const; - bool acceptRarity(CardInfoPtr info) const; - bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const; - bool acceptFormat(CardInfoPtr info) const; - bool relationCheck(int cardInfo) const; + [[nodiscard]] bool acceptName(CardInfoPtr info) const; + [[nodiscard]] bool acceptNameExact(CardInfoPtr info) const; + [[nodiscard]] bool acceptType(CardInfoPtr info) const; + [[nodiscard]] bool acceptMainType(CardInfoPtr info) const; + [[nodiscard]] bool acceptSubType(CardInfoPtr info) const; + [[nodiscard]] bool acceptColor(CardInfoPtr info) const; + [[nodiscard]] bool acceptText(CardInfoPtr info) const; + [[nodiscard]] bool acceptSet(CardInfoPtr info) const; + [[nodiscard]] bool acceptManaCost(CardInfoPtr info) const; + [[nodiscard]] bool acceptCmc(CardInfoPtr info) const; + [[nodiscard]] bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool acceptLoyalty(CardInfoPtr info) const; + [[nodiscard]] bool acceptRarity(CardInfoPtr info) const; + [[nodiscard]] bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool acceptFormat(CardInfoPtr info) const; + [[nodiscard]] bool relationCheck(int cardInfo) const; }; class FilterTree : public QObject, public FilterTreeBranch @@ -265,16 +265,16 @@ public: FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term); FilterTreeNode *termNode(const CardFilter *f); - const QString text() const override + [[nodiscard]] const QString text() const override { return QString("root"); } - int index() const override + [[nodiscard]] int index() const override { return 0; } - bool acceptsCard(CardInfoPtr info) const; + [[nodiscard]] bool acceptsCard(CardInfoPtr info) const; void removeFiltersByAttr(CardFilter::Attr filterType); void removeFilter(const CardFilter *toRemove); void clear(); diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_database_path_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_database_path_provider.h index f5e54a6f8..dd55ab789 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_database_path_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_database_path_provider.h @@ -9,10 +9,10 @@ class ICardDatabasePathProvider : public QObject public: virtual ~ICardDatabasePathProvider() = default; - virtual QString getCardDatabasePath() const = 0; - virtual QString getCustomCardDatabasePath() const = 0; - virtual QString getTokenDatabasePath() const = 0; - virtual QString getSpoilerCardDatabasePath() const = 0; + [[nodiscard]] virtual QString getCardDatabasePath() const = 0; + [[nodiscard]] virtual QString getCustomCardDatabasePath() const = 0; + [[nodiscard]] virtual QString getTokenDatabasePath() const = 0; + [[nodiscard]] virtual QString getSpoilerCardDatabasePath() const = 0; signals: void cardDatabasePathChanged(); diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_preference_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_preference_provider.h index 7c0944f29..a6cf941fb 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_preference_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_preference_provider.h @@ -7,8 +7,8 @@ class ICardPreferenceProvider { public: virtual ~ICardPreferenceProvider() = default; - virtual QString getCardPreferenceOverride(const QString &cardName) const = 0; - virtual bool getIncludeRebalancedCards() const = 0; + [[nodiscard]] virtual QString getCardPreferenceOverride(const QString &cardName) const = 0; + [[nodiscard]] virtual bool getIncludeRebalancedCards() const = 0; }; #endif // COCKATRICE_INTERFACE_CARD_PREFERENCE_PROVIDER_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 8f8b7e62e..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,5 +1,6 @@ #ifndef COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H #define COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H + #include class ICardSetPriorityController diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_database_path_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_database_path_provider.h index fb3ab0cc3..bacd7a2bb 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_database_path_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_database_path_provider.h @@ -5,19 +5,19 @@ class NoopCardDatabasePathProvider : public ICardDatabasePathProvider { public: - QString getCardDatabasePath() const override + [[nodiscard]] QString getCardDatabasePath() const override { return ""; } - QString getCustomCardDatabasePath() const override + [[nodiscard]] QString getCustomCardDatabasePath() const override { return ""; } - QString getTokenDatabasePath() const override + [[nodiscard]] QString getTokenDatabasePath() const override { return ""; } - QString getSpoilerCardDatabasePath() const override + [[nodiscard]] QString getSpoilerCardDatabasePath() const override { return ""; } diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_preference_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_preference_provider.h index 9fc7e5bb4..3781f4b63 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_preference_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_preference_provider.h @@ -5,12 +5,12 @@ class NoopCardPreferenceProvider : public ICardPreferenceProvider { public: - QString getCardPreferenceOverride(const QString &) const override + [[nodiscard]] QString getCardPreferenceOverride(const QString &) const override { return {}; } - bool getIncludeRebalancedCards() const override + [[nodiscard]] bool getIncludeRebalancedCards() const override { return true; } 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/card_completer_proxy_model.h b/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h index 7a4626059..afb6f1fcf 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h @@ -16,7 +16,7 @@ public: explicit CardCompleterProxyModel(QObject *parent = nullptr); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; #endif // CARD_COMPLETER_PROXY_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h index 98d66cb5e..18be2c55a 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h @@ -17,8 +17,8 @@ class CardSearchModel : public QAbstractListModel public: explicit CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; void updateSearchResults(const QString &query); // Update results based on input diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h index 467b7f0a5..0c5994a3a 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h @@ -8,8 +8,6 @@ #ifndef COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H #define COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H -#include -#include #include #include #include @@ -77,17 +75,17 @@ public: dirtyTimer.start(20); } void clearFilterAll(); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - bool canFetchMore(const QModelIndex &parent) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] bool canFetchMore(const QModelIndex &parent) const override; void fetchMore(const QModelIndex &parent) override; signals: void modelDirty(); protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + [[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; static int lessThanNumerically(const QString &left, const QString &right); - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - bool rowMatchesCardName(CardInfoPtr info) const; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool rowMatchesCardName(CardInfoPtr info) const; private slots: void filterTreeChanged(); diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_model.h index 7c48c4e6d..218cfff92 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_database_model.h @@ -31,15 +31,16 @@ public: }; CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = nullptr); ~CardDatabaseModel() override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - CardDatabase *getDatabase() const + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] CardDatabase *getDatabase() const { return db; } - CardInfoPtr getCard(int index) const + [[nodiscard]] CardInfoPtr getCard(int index) const { return cardList[index]; } 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/database/card_set/card_sets_model.h b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h index 6f5911b6e..0ffc5a847 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h @@ -25,11 +25,11 @@ public: SetsMimeData(int _oldRow) : oldRow(_oldRow) { } - int getOldRow() const + [[nodiscard]] int getOldRow() const { return oldRow; } - QStringList formats() const + [[nodiscard]] QStringList formats() const { return QStringList() << "application/x-cockatricecardset"; } @@ -64,22 +64,23 @@ public: explicit SetsModel(CardDatabase *_db, QObject *parent = nullptr); ~SetsModel() override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return NUM_COLS; } - QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - Qt::DropActions supportedDropActions() const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] Qt::DropActions supportedDropActions() const override; - QMimeData *mimeData(const QModelIndexList &indexes) const override; + [[nodiscard]] QMimeData *mimeData(const QModelIndexList &indexes) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - QStringList mimeTypes() const override; + [[nodiscard]] QStringList mimeTypes() const override; void swapRows(int oldRow, int newRow); void toggleRow(int row, bool enable); void toggleRow(int row); @@ -97,8 +98,8 @@ public: explicit SetsDisplayModel(QObject *parent = nullptr); protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; void fetchMore(const QModelIndex &index) override; }; diff --git a/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h b/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h index 5d3b63a36..f6b2fdfbb 100644 --- a/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h +++ b/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h @@ -14,10 +14,10 @@ class TokenDisplayModel : public CardDatabaseDisplayModel Q_OBJECT public: explicit TokenDisplayModel(QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; #endif // COCKATRICE_TOKEN_DISPLAY_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h b/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h index 50c7eb642..5e5843761 100644 --- a/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h +++ b/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h @@ -14,10 +14,10 @@ class TokenEditModel : public CardDatabaseDisplayModel Q_OBJECT public: explicit TokenEditModel(QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; #endif // COCKATRICE_TOKEN_EDIT_MODEL_H 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 e3e2de58d..130f05c8a 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -5,8 +5,11 @@ 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->setParent(this); root = new InnerDecklistNode; } @@ -38,7 +41,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)); @@ -165,6 +168,10 @@ QVariant DeckListModel::data(const QModelIndex &index, int role) const return card->depth(); } + case DeckRoles::IsLegalRole: { + return card->getFormatLegality(); + } + default: { return {}; } @@ -265,6 +272,7 @@ bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, con switch (index.column()) { case DeckListModelColumns::CARD_AMOUNT: node->setNumber(value.toInt()); + refreshCardFormatLegalities(); break; case DeckListModelColumns::CARD_NAME: node->setName(value.toString()); @@ -284,6 +292,7 @@ bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, con emitRecursiveUpdates(index); deckList->refreshDeckHash(); + emit deckHashChanged(); emit dataChanged(index, index); return true; @@ -304,7 +313,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; } @@ -410,8 +419,9 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam // Determine the correct index int insertRow = findSortedInsertRow(groupNode, cardInfo); - auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName, - printingInfo.getProperty("num"), printingInfo.getProperty("uuid")); + auto *decklistCard = + deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName, printingInfo.getProperty("num"), + printingInfo.getProperty("uuid"), isCardLegalForCurrentFormat(cardInfo)); beginInsertRows(parentIndex, insertRow, insertRow); cardNode = new DecklistModelCardNode(decklistCard, groupNode, insertRow); @@ -422,10 +432,45 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam cardNode->setCardCollectorNumber(printingInfo.getProperty("num")); cardNode->setCardProviderId(printingInfo.getProperty("uuid")); deckList->refreshDeckHash(); + emit deckHashChanged(); } sort(lastKnownColumn, lastKnownOrder); emitRecursiveUpdates(parentIndex); - return nodeToIndex(cardNode); + auto index = nodeToIndex(cardNode); + + emit cardAddedAt(index); + + return index; +} + +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(InnerDecklistNode *parent, CardInfoPtr cardInfo) const @@ -527,6 +572,13 @@ void DeckListModel::setActiveGroupCriteria(DeckListModelGroupCriteria::Type newC rebuildTree(); } +void DeckListModel::setActiveFormat(const QString &_format) +{ + deckList->setGameFormat(_format); + refreshCardFormatLegalities(); + emitBackgroundUpdates(QModelIndex()); // start from root +} + void DeckListModel::cleanList() { setDeckList(new DeckList); @@ -541,90 +593,156 @@ void DeckListModel::setDeckList(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 { - QList cards; - DeckList *decklist = getDeckList(); - if (!decklist) { - return cards; - } - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) - return cards; - - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - if (!currentZone) - continue; - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) - continue; - for (int k = 0; k < currentCard->getNumber(); ++k) { - ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef()); - if (card) { - cards.append(card); - } else { - qDebug() << "Card not found in database!"; - } - } - } - } - return cards; + auto nodes = deckList->getCardNodes(); + return cardNodesToExactCards(nodes); } QList DeckListModel::getCardsForZone(const QString &zoneName) const { - QList cards; - DeckList *decklist = getDeckList(); - if (!decklist) { - return cards; - } - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) - return cards; - - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - if (!currentZone) - continue; - if (currentZone->getName() == zoneName) { - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) - continue; - for (int k = 0; k < currentCard->getNumber(); ++k) { - ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef()); - if (card) { - cards.append(card); - } else { - qDebug() << "Card not found in database!"; - } - } - } - } - } - return cards; + auto nodes = deckList->getCardNodes({zoneName}); + return cardNodesToExactCards(nodes); } -QList *DeckListModel::getZones() const +QList DeckListModel::getCardNames() const { - QList *zones = new QList(); - DeckList *decklist = getDeckList(); - if (!decklist) { - return zones; + auto nodes = deckList->getCardNodes(); + + 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 +{ + auto zoneNodes = deckList->getZoneNodes(); + + QList zones; + std::transform(zoneNodes.cbegin(), zoneNodes.cend(), std::back_inserter(zones), + [](auto zoneNode) { return zoneNode->getName(); }); + + 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; } - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) - return zones; + return true; +} + +static int maxAllowedForLegality(const FormatRules &format, const QString &legality) +{ + for (const AllowedCount &c : format.allowedCounts) { + if (c.label == legality) { + return c.max; + } + } + return -1; // unknown legality → treat as illegal +} + +bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardInfo, int quantity) +{ + auto formatRules = CardDatabaseManager::query()->getFormat(deckList->getGameFormat()); + + if (!formatRules) { + return true; + } + + // Exceptions always win + if (cardHasAnyException(*cardInfo, *formatRules)) { + return true; + } + + const QString legalityProp = "format-" + deckList->getGameFormat(); + if (!cardInfo->getProperties().contains(legalityProp)) { + return false; + } + + const QString legality = cardInfo->getProperty(legalityProp); + + int maxAllowed = maxAllowedForLegality(*formatRules, legality); + + if (maxAllowed == -1) { + return false; + } + + if (maxAllowed < 0) { // unlimited + return true; + } + + return quantity <= maxAllowed; +} + +void DeckListModel::refreshCardFormatLegalities() +{ + InnerDecklistNode *listRoot = deckList->getTree()->getRoot(); for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - if (!currentZone) - continue; - zones->append(currentZone->getName()); + auto *currentZone = static_cast(listRoot->at(i)); + for (int j = 0; j < currentZone->size(); j++) { + auto *currentCard = static_cast(currentZone->at(j)); + + // TODO: better sanity checking + if (currentCard == nullptr) { + continue; + } + + ExactCard exactCard = CardDatabaseManager::query()->getCard(currentCard->toCardRef()); + if (!exactCard) { + continue; + } + + bool legal = isCardLegalForCurrentFormat(exactCard.getCardPtr()); + + if (legal) { + legal = isCardQuantityLegalForCurrentFormat(exactCard.getCardPtr(), currentCard->getNumber()); + } + + currentCard->setFormatLegality(legal); + } } - return zones; -} \ No newline at end of file +} 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 8beabd4ba..7bb504e60 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -1,12 +1,12 @@ #ifndef DECKLISTMODEL_H #define DECKLISTMODEL_H +#include <../../../../libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h> +#include <../../../../libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h> #include #include #include -#include #include -#include class CardDatabase; class QPrinter; @@ -74,6 +74,29 @@ enum Type MANA_COST, /**< Group cards by their total mana cost. */ COLOR /**< Group cards by their color identity. */ }; +static inline QString toString(Type t) +{ + switch (t) { + case MAIN_TYPE: + return "Main Type"; + case MANA_COST: + return "Mana Cost"; + case COLOR: + return "Colors"; + } + return {}; +} + +static inline Type fromString(const QString &s) +{ + if (s == "Main Type") + return MAIN_TYPE; + if (s == "Mana Cost") + return MANA_COST; + if (s == "Colors") + return COLOR; + return MAIN_TYPE; // default +} } // namespace DeckListModelGroupCriteria /** @@ -101,7 +124,7 @@ public: : AbstractDecklistCardNode(_parent, position), dataNode(_dataNode) { } - int getNumber() const override + [[nodiscard]] int getNumber() const override { return dataNode->getNumber(); } @@ -109,7 +132,7 @@ public: { dataNode->setNumber(_number); } - QString getName() const override + [[nodiscard]] QString getName() const override { return dataNode->getName(); } @@ -117,7 +140,7 @@ public: { dataNode->setName(_name); } - QString getCardProviderId() const override + [[nodiscard]] QString getCardProviderId() const override { return dataNode->getCardProviderId(); } @@ -125,7 +148,7 @@ public: { dataNode->setCardProviderId(_cardProviderId); } - QString getCardSetShortName() const override + [[nodiscard]] QString getCardSetShortName() const override { return dataNode->getCardSetShortName(); } @@ -133,7 +156,7 @@ public: { dataNode->setCardSetShortName(_cardSetShortName); } - QString getCardCollectorNumber() const override + [[nodiscard]] QString getCardCollectorNumber() const override { return dataNode->getCardCollectorNumber(); } @@ -141,12 +164,20 @@ public: { dataNode->setCardCollectorNumber(_cardSetNumber); } + bool getFormatLegality() const override + { + return dataNode->getFormatLegality(); + } + void setFormatLegality(const bool _formatLegal) override + { + dataNode->setFormatLegality(_formatLegal); + } /** * @brief Returns the underlying data node. * @return Pointer to the DecklistCardNode wrapped by this node. */ - DecklistCardNode *getDataNode() const + [[nodiscard]] DecklistCardNode *getDataNode() const { return dataNode; } @@ -171,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 { @@ -179,19 +210,34 @@ 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: + void setActiveFormat(const QString &_format); + signals: /** * @brief Emitted whenever the deck hash changes due to modifications in the model. */ 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); ~DeckListModel() override; @@ -200,30 +246,35 @@ public: * @brief Returns the root index of the model. * @return QModelIndex representing the root node. */ - QModelIndex getRoot() const + [[nodiscard]] QModelIndex getRoot() const { 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. */ - QString getGroupCriteriaForCard(CardInfoPtr info) const; + [[nodiscard]] QString getGroupCriteriaForCard(CardInfoPtr info) const; // Qt model overrides - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - void emitBackgroundUpdates(const QModelIndex &parent); - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - QModelIndex index(int row, int column, const QModelIndex &parent) const override; - QModelIndex parent(const QModelIndex &index) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + [[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; + [[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; + /** + * Recursively emits the dataChanged signal for all child nodes. + * @param parent The parent node + */ + void emitBackgroundUpdates(const QModelIndex &parent); + /** * @brief Finds a card by name, zone, and optional identifiers. * @param cardName The card's name. @@ -232,10 +283,10 @@ public: * @param cardNumber Optional collector number. * @return QModelIndex of the card, or invalid index if not found. */ - QModelIndex findCard(const QString &cardName, - const QString &zoneName, - const QString &providerId = "", - const QString &cardNumber = "") const; + [[nodiscard]] QModelIndex findCard(const QString &cardName, + const QString &zoneName, + const QString &providerId = "", + const QString &cardNumber = "") const; /** * @brief Adds a card using the preferred printing if available. @@ -255,6 +306,15 @@ public: */ QModelIndex addCard(const ExactCard &card, const QString &zoneName); + /** + * @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 + */ + bool offsetCountAtIndex(const QModelIndex &idx, int offset); + /** * @brief Determines the sorted insertion row for a card. * @param parent The parent node where the card will be inserted. @@ -269,15 +329,45 @@ public: * @brief Removes all cards and resets the model. */ void cleanList(); - DeckList *getDeckList() const + [[nodiscard]] DeckList *getDeckList() const { return deckList; } void setDeckList(DeckList *_deck); - QList getCards() const; - QList getCardsForZone(const QString &zoneName) const; - QList *getZones() const; + /** + * @brief Apply a function to every card in the deck tree. + * + * @param func Function taking (zone node, card node). + */ + 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; + + bool isCardLegalForCurrentFormat(CardInfoPtr cardInfo); + bool isCardQuantityLegalForCurrentFormat(CardInfoPtr cardInfo, int quantity); + void refreshCardFormatLegalities(); /** * @brief Sets the criteria used to group cards in the model. @@ -294,10 +384,11 @@ private: InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent); QModelIndex nodeToIndex(AbstractDecklistNode *node) const; - DecklistModelCardNode *findCardNode(const QString &cardName, - const QString &zoneName, - const QString &providerId = "", - const QString &cardNumber = "") const; + [[nodiscard]] DecklistModelCardNode *findCardNode(const QString &cardName, + const QString &zoneName, + const QString &providerId = "", + const QString &cardNumber = "") const; + void emitRecursiveUpdates(const QModelIndex &index); void sortHelper(InnerDecklistNode *node, Qt::SortOrder order); 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_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h index 0603beace..94742795d 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h @@ -25,7 +25,7 @@ public: } protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + [[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; private: QStringList sortCriteria; diff --git a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h index 165737e95..dc3be5a94 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h +++ b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h @@ -8,7 +8,6 @@ #define ABSTRACTCLIENT_H #include -#include #include #include #include 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/client/remote/remote_client.h b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h index 939e88dfc..15e3e8ef5 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h @@ -10,7 +10,6 @@ #include "../abstract/abstract_client.h" #include -#include #include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h b/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h index 1ddf5a85a..4410fd65c 100644 --- a/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h +++ b/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h @@ -25,7 +25,7 @@ public: QString getConnectionType() const override { return "local"; - }; + } void transmitProtocolItem(const ServerMessage &item) override; signals: void itemToClient(const ServerMessage &item); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp index c6a29b275..493b8e966 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp @@ -4,18 +4,10 @@ #include "../server_abstractuserinterface.h" #include "../server_database_interface.h" #include "../server_room.h" -#include "server_arrow.h" #include "server_card.h" -#include "server_cardzone.h" -#include "server_counter.h" #include "server_game.h" #include "server_player.h" -#include -#include -#include -#include -#include #include #include #include @@ -49,42 +41,13 @@ #include #include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include #include -#include -#include -#include -#include -#include #include -#include -#include #include #include -#include -#include #include Server_AbstractParticipant::Server_AbstractParticipant(Server_Game *_game, diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h index 10fbb3349..f60d56dcf 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h @@ -76,7 +76,7 @@ public: virtual void prepareDestroy() { removeFromGame(); - }; + } void removeFromGame(); Server_AbstractUserInterface *getUserInterface() const { diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h index 4a32b6fef..40fe84aa1 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h @@ -4,7 +4,6 @@ #include "../serverinfo_user_container.h" #include "server_abstract_participant.h" -#include #include #include 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_cardzone.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.h index 0dc368a80..77fea54ca 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.h @@ -20,7 +20,6 @@ #ifndef SERVER_CARDZONE_H #define SERVER_CARDZONE_H -#include #include #include #include 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 b0dc7fc5b..5cd6c8bf8 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -50,7 +49,6 @@ #include #include #include -#include Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, int _gameId, @@ -75,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()); @@ -503,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_network/libcockatrice/network/server/remote/game/server_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp index b840a172c..e62f861a9 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp @@ -4,7 +4,6 @@ #include "../server_abstractuserinterface.h" #include "../server_database_interface.h" #include "../server_room.h" -#include "server_arrow.h" #include "server_card.h" #include "server_cardzone.h" #include "server_counter.h" @@ -15,67 +14,29 @@ #include #include #include -#include -#include +#include #include #include #include -#include #include -#include #include #include -#include #include -#include -#include -#include -#include #include -#include -#include #include #include -#include -#include -#include -#include -#include #include -#include -#include #include #include #include #include -#include -#include -#include #include -#include #include -#include #include -#include -#include -#include -#include #include -#include #include -#include -#include #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include #include #include #include @@ -141,28 +102,16 @@ void Server_Player::setupZones() // ------------------------------------------------------------------ // Assign card ids and create deck from deck list - InnerDecklistNode *listRoot = deck->getRoot(); - for (int i = 0; i < listRoot->size(); ++i) { - auto *currentZone = dynamic_cast(listRoot->at(i)); - Server_CardZone *z; - if (currentZone->getName() == DECK_ZONE_MAIN) { - z = deckZone; - } else if (currentZone->getName() == DECK_ZONE_SIDE) { - z = sbZone; - } else { - continue; + auto insertCardsIntoZone = [this](auto cards, auto *zone) { + for (auto card : cards) { + for (int k = 0; k < card->getNumber(); ++k) { + zone->insertCard(new Server_Card(card->toCardRef(), nextCardId++, 0, 0, zone), -1, 0); + } } + }; - for (int j = 0; j < currentZone->size(); ++j) { - auto *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) { - continue; - } - for (int k = 0; k < currentCard->getNumber(); ++k) { - z->insertCard(new Server_Card(currentCard->toCardRef(), nextCardId++, 0, 0, z), -1, 0); - } - } - } + insertCardsIntoZone(deck->getCardNodes({DECK_ZONE_MAIN}), deckZone); + insertCardsIntoZone(deck->getCardNodes({DECK_ZONE_SIDE}), sbZone); const QList &sideboardPlan = deck->getCurrentSideboardPlan(); for (const auto &m : sideboardPlan) { diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server.cpp index 2f1232b8f..a5a74c54c 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server.h b/libcockatrice_network/libcockatrice/network/server/remote/server.h index f9dada801..ab57fac4e 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server.h @@ -3,17 +3,13 @@ #include "server_player_reference.h" -#include #include #include #include #include -#include #include #include -#include #include -#include class Server_DatabaseInterface; class Server_Game; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp index e228f0128..f9b61ab48 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp @@ -9,10 +9,8 @@ #include #include -#include #include #include -#include void Server_AbstractUserInterface::sendProtocolItemByType(ServerMessage::MessageType type, const ::google::protobuf::Message &item) diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h index 10a2aee62..b11260003 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h @@ -5,7 +5,6 @@ #include #include -#include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h index d2577a99b..b43dbde42 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h @@ -83,7 +83,7 @@ public: virtual bool usernameIsValid(const QString & /*userName */, QString & /* error */) { return true; - }; + } public slots: virtual void endSession(qint64 /* sessionId */) { @@ -154,16 +154,16 @@ public: int & /* banSecondsRemaining */) { return false; - }; + } virtual int checkNumberOfUserAccounts(const QString & /* email */) { return 0; - }; + } virtual bool changeUserPassword(const QString & /* user */, const QString & /* password */, bool /* passwordNeedsHash */) { return false; - }; + } virtual bool changeUserPassword(const QString & /* user */, const QString & /* oldPassword */, bool /* oldPasswordNeedsHash */, @@ -171,7 +171,7 @@ public: bool /* newPasswordNeedsHash */) { return false; - }; + } }; #endif diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h index 9f3b1f4e7..0d05b91c8 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h @@ -5,7 +5,6 @@ #include "server_abstractuserinterface.h" #include -#include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_room.h b/libcockatrice_network/libcockatrice/network/server/remote/server_room.h index 34b9063e9..3d9988f20 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_room.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_room.h @@ -3,12 +3,10 @@ #include "serverinfo_user_container.h" -#include #include #include #include #include -#include #include #include diff --git a/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp b/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp index d9f253ab9..1b08c4040 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp +++ b/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp @@ -1,6 +1,5 @@ #include "featureset.h" -#include #include FeatureSet::FeatureSet() diff --git a/libcockatrice_protocol/libcockatrice/protocol/featureset.h b/libcockatrice_protocol/libcockatrice/protocol/featureset.h index 424d0b975..32625d5f8 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/featureset.h +++ b/libcockatrice_protocol/libcockatrice/protocol/featureset.h @@ -3,7 +3,6 @@ #include #include -#include #include class FeatureSet : public QObject diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt index 32e7f3238..212ab69dd 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt @@ -165,6 +165,12 @@ set(PROTO_FILES session_event.proto ) +if(MSVC) + set(unused_warning /wd4100) +else() + set(unused_warning -Wno-unused-parameter) +endif() + if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0") message(STATUS "Using Protobuf Legacy Mode") include_directories(${PROTOBUF_INCLUDE_DIRS}) @@ -172,6 +178,7 @@ if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0") protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILES}) add_library(libcockatrice_protocol_pb ${PROTO_SRCS} ${PROTO_HDRS}) + target_compile_options(libcockatrice_protocol_pb PRIVATE ${unused_warning}) set(libcockatrice_protocol_pb_LIBS ${PROTOBUF_LIBRARIES}) if(UNIX) set(libcockatrice_protocol_pb_LIBS ${libcockatrice_protocol_pb_LIBS} -lpthread) @@ -189,6 +196,7 @@ if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0") endif() else() add_library(libcockatrice_protocol_pb ${PROTO_FILES}) + target_compile_options(libcockatrice_protocol_pb PRIVATE ${unused_warning}) target_link_libraries(libcockatrice_protocol_pb PUBLIC protobuf::libprotobuf) set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}") target_include_directories(libcockatrice_protocol_pb PUBLIC "${PROTOBUF_INCLUDE_DIRS}") diff --git a/libcockatrice_rng/libcockatrice/rng/rng_abstract.h b/libcockatrice_rng/libcockatrice/rng/rng_abstract.h index ffd45b9d7..903e6ef1a 100644 --- a/libcockatrice_rng/libcockatrice/rng/rng_abstract.h +++ b/libcockatrice_rng/libcockatrice/rng/rng_abstract.h @@ -13,7 +13,7 @@ public: } virtual unsigned int rand(int min, int max) = 0; QVector makeNumbersVector(int n, int min, int max); - double testRandom(const QVector &numbers) const; + [[nodiscard]] double testRandom(const QVector &numbers) const; }; extern RNG_Abstract *rng; diff --git a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h index 8d45efbec..9a176a99b 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h @@ -10,9 +10,6 @@ #include "settings_manager.h" -#include -#include -#include #include class CardDatabaseSettings : public SettingsManager, public ICardSetPriorityController diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.h b/libcockatrice_settings/libcockatrice/settings/download_settings.h index a67a7f9c7..ed3634ea1 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.h @@ -9,8 +9,6 @@ #include "settings_manager.h" -#include - class DownloadSettings : public SettingsManager { Q_OBJECT diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h index d62fea03d..45e9b7441 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h @@ -57,7 +57,7 @@ private: explicit GameFiltersSettings(const QString &settingPath, QObject *parent = nullptr); GameFiltersSettings(const GameFiltersSettings & /*other*/); - QString hashGameType(const QString &gameType) const; + [[nodiscard]] QString hashGameType(const QString &gameType) const; }; #endif // GAMEFILTERSSETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.h b/libcockatrice_settings/libcockatrice/settings/settings_manager.h index a72094b4e..3592d8f8c 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.h @@ -7,7 +7,6 @@ #ifndef SETTINGSMANAGER_H #define SETTINGSMANAGER_H -#include #include #include #include diff --git a/libcockatrice_utility/libcockatrice/utility/card_ref.h b/libcockatrice_utility/libcockatrice/utility/card_ref.h index d29eee0bb..d89fe590b 100644 --- a/libcockatrice_utility/libcockatrice/utility/card_ref.h +++ b/libcockatrice_utility/libcockatrice/utility/card_ref.h @@ -19,6 +19,11 @@ struct CardRef { return name == other.name && providerId == other.providerId; } + + bool isEmpty() const + { + return name.isEmpty() && providerId.isEmpty(); + } }; #endif // CARD_REF_H 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/libcockatrice_utility/libcockatrice/utility/qt_utils.h b/libcockatrice_utility/libcockatrice/utility/qt_utils.h new file mode 100644 index 000000000..855cc8b18 --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/qt_utils.h @@ -0,0 +1,20 @@ +#ifndef COCKATRICE_QT_UTILS_H +#define COCKATRICE_QT_UTILS_H +#include + +namespace QtUtils +{ +template T *findParentOfType(const QObject *obj) +{ + const QObject *p = obj ? obj->parent() : nullptr; + while (p) { + if (auto casted = qobject_cast(const_cast(p))) { + return casted; + } + p = p->parent(); + } + return nullptr; +} +} // namespace QtUtils + +#endif // COCKATRICE_QT_UTILS_H diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index f316e439d..4b089846f 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -13,6 +13,10 @@ #include #include +static const QList kConstructedCounts = {{4, "legal"}, {0, "banned"}}; + +static const QList kSingletonCounts = {{1, "legal"}, {0, "banned"}}; + SplitCardPart::SplitCardPart(const QString &_name, const QString &_text, const QVariantHash &_properties, @@ -463,6 +467,71 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList return numCards; } +FormatRulesNameMap OracleImporter::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; +} + int OracleImporter::startImport() { static ICardSetPriorityController *noOpController = new NoopCardSetPriorityController(); @@ -496,8 +565,9 @@ int OracleImporter::startImport() bool OracleImporter::saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion) { - CockatriceXml4Parser parser(new NoopCardPreferenceProvider()); - return parser.saveToFile(sets, cards, fileName, sourceUrl, sourceVersion); + CockatriceXml4Parser parser(new NoopCardPreferenceProvider(), new NoopCardSetPriorityController()); + + return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName, sourceUrl, sourceVersion); } void OracleImporter::clear() diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index 3ec6da6e1..e83958d73 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -154,6 +154,7 @@ public: int startImport(); bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion); int importCardsFromSet(const CardSetPtr ¤tSet, const QList &cardsList); + FormatRulesNameMap createDefaultMagicFormats(); const CardNameMap &getCardList() const { return cards; diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp index 9b2f5ce7c..7629a291b 100644 --- a/oracle/src/pages.cpp +++ b/oracle/src/pages.cpp @@ -302,7 +302,9 @@ void LoadSetsPage::downloadSetsFile(const QUrl &url) const auto urlString = url.toString(); if (urlString == ALLSETS_URL || urlString == ALLSETS_URL_FALLBACK) { const auto versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL); - auto *versionReply = wizard()->nam->get(QNetworkRequest(versionUrl)); + QNetworkRequest request = QNetworkRequest(versionUrl); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + auto *versionReply = wizard()->nam->get(request); connect(versionReply, &QNetworkReply::finished, [this, versionReply]() { if (versionReply->error() == QNetworkReply::NoError) { auto data = versionReply->readAll(); @@ -326,7 +328,9 @@ void LoadSetsPage::downloadSetsFile(const QUrl &url) wizard()->setCardSourceUrl(url.toString()); - auto *reply = wizard()->nam->get(QNetworkRequest(url)); + QNetworkRequest request = QNetworkRequest(url); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + auto *reply = wizard()->nam->get(request); connect(reply, &QNetworkReply::finished, this, &LoadSetsPage::actDownloadFinishedSetsFile); connect(reply, &QNetworkReply::downloadProgress, this, &LoadSetsPage::actDownloadProgressSetsFile); @@ -672,13 +676,21 @@ QString LoadTokensPage::getFileType() return tr("XML; token database (*.xml)"); } +QString LoadTokensPage::getFilePromptName() +{ + return tr("tokens"); +} + void LoadTokensPage::retranslateUi() { setTitle(tr("Tokens import")); setSubTitle(tr("Please specify a compatible source for token data.")); - urlLabel->setText(tr("Download URL:")); + urlRadioButton->setText(tr("Download URL:")); + fileRadioButton->setText(tr("Local file:")); urlButton->setText(tr("Restore default URL")); + fileButton->setText(tr("Choose file...")); + pathLabel->setText(tr("The token database will be saved at the following location:") + "
    " + SettingsCache::instance().getTokenDatabasePath()); defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)")); @@ -709,13 +721,21 @@ QString LoadSpoilersPage::getFileType() return tr("XML; spoiler database (*.xml)"); } +QString LoadSpoilersPage::getFilePromptName() +{ + return tr("spoiler"); +} + void LoadSpoilersPage::retranslateUi() { setTitle(tr("Spoilers import")); setSubTitle(tr("Please specify a compatible source for spoiler data.")); - urlLabel->setText(tr("Download URL:")); + urlRadioButton->setText(tr("Download URL:")); + fileRadioButton->setText(tr("Local file:")); urlButton->setText(tr("Restore default URL")); + fileButton->setText(tr("Choose file...")); + pathLabel->setText(tr("The spoiler database will be saved at the following location:") + "
    " + SettingsCache::instance().getSpoilerCardDatabasePath()); defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)")); diff --git a/oracle/src/pages.h b/oracle/src/pages.h index 6a88b6099..066cc2e1b 100644 --- a/oracle/src/pages.h +++ b/oracle/src/pages.h @@ -131,6 +131,7 @@ protected: QString getDefaultSavePath() override; QString getWindowTitle() override; QString getFileType() override; + QString getFilePromptName() override; }; class LoadTokensPage : public SimpleDownloadFilePage @@ -148,6 +149,7 @@ protected: QString getDefaultSavePath() override; QString getWindowTitle() override; QString getFileType() override; + QString getFilePromptName() override; void initializePage() override; }; diff --git a/oracle/src/pagetemplates.cpp b/oracle/src/pagetemplates.cpp index d9ce3300f..4a27fef30 100644 --- a/oracle/src/pagetemplates.cpp +++ b/oracle/src/pagetemplates.cpp @@ -12,32 +12,43 @@ #include #include #include +#include #include SimpleDownloadFilePage::SimpleDownloadFilePage(QWidget *parent) : OracleWizardPage(parent) { - urlLabel = new QLabel(this); + urlRadioButton = new QRadioButton(this); + fileRadioButton = new QRadioButton(this); + urlLineEdit = new QLineEdit(this); + fileLineEdit = new QLineEdit(this); progressLabel = new QLabel(this); progressBar = new QProgressBar(this); + urlRadioButton->setChecked(true); + urlButton = new QPushButton(this); connect(urlButton, &QPushButton::clicked, this, &SimpleDownloadFilePage::actRestoreDefaultUrl); - defaultPathCheckBox = new QCheckBox(this); + fileButton = new QPushButton(this); + connect(fileButton, &QPushButton::clicked, this, &SimpleDownloadFilePage::actLoadCardFile); + defaultPathCheckBox = new QCheckBox(this); pathLabel = new QLabel(this); pathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); auto *layout = new QGridLayout(this); - layout->addWidget(urlLabel, 0, 0); + layout->addWidget(urlRadioButton, 0, 0); layout->addWidget(urlLineEdit, 0, 1); layout->addWidget(urlButton, 1, 1, Qt::AlignRight); - layout->addWidget(pathLabel, 2, 0, 1, 2); - layout->addWidget(defaultPathCheckBox, 3, 0, 1, 2); - layout->addWidget(progressLabel, 4, 0); - layout->addWidget(progressBar, 4, 1); + layout->addWidget(fileRadioButton, 2, 0); + layout->addWidget(fileLineEdit, 2, 1); + layout->addWidget(fileButton, 3, 1, Qt::AlignRight); + layout->addWidget(pathLabel, 4, 0, 1, 2); + layout->addWidget(defaultPathCheckBox, 5, 0, 1, 2); + layout->addWidget(progressLabel, 6, 0); + layout->addWidget(progressBar, 6, 1); setLayout(layout); } @@ -56,6 +67,31 @@ void SimpleDownloadFilePage::actRestoreDefaultUrl() urlLineEdit->setText(getDefaultUrl()); } +void SimpleDownloadFilePage::actLoadCardFile() +{ + QFileDialog dialog(this, tr("Load %1 file").arg(getFilePromptName())); + dialog.setFileMode(QFileDialog::ExistingFile); + + QString extensions = "*.json *.xml"; +#ifdef HAS_ZLIB + extensions += " *.zip"; +#endif +#ifdef HAS_LZMA + extensions += " *.xz"; +#endif + dialog.setNameFilter(tr("%1 file (%1)").arg(getFilePromptName(), extensions)); + + if (!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) { + dialog.selectFile(fileLineEdit->text()); + } + + if (!dialog.exec()) { + return; + } + + fileLineEdit->setText(dialog.selectedFiles().at(0)); +} + bool SimpleDownloadFilePage::validatePage() { // if data has already been downloaded, pass directly to the "save" step @@ -68,22 +104,41 @@ bool SimpleDownloadFilePage::validatePage() } } - QUrl url = QUrl::fromUserInput(urlLineEdit->text()); - if (!url.isValid()) { - QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid: ") + url.toString()); - return false; + // else, try to import sets + if (urlRadioButton->isChecked()) { + QUrl url = QUrl::fromUserInput(urlLineEdit->text()); + if (!url.isValid()) { + QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid: ") + url.toString()); + return false; + } + + progressLabel->setText(tr("Downloading (0MB)")); + // show an infinite progressbar + progressBar->setMaximum(0); + progressBar->setMinimum(0); + progressBar->setValue(0); + progressLabel->show(); + progressBar->show(); + + wizard()->disableButtons(); + downloadFile(url); + + } else if (fileRadioButton->isChecked()) { + QFile cardFile(fileLineEdit->text()); + if (!cardFile.exists()) { + QMessageBox::critical(this, tr("Error"), tr("Please choose a file.")); + return false; + } + + if (!cardFile.open(QIODevice::ReadOnly)) { + QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text())); + return false; + } + + downloadData = cardFile.readAll(); + wizard()->next(); } - progressLabel->setText(tr("Downloading (0MB)")); - // show an infinite progressbar - progressBar->setMaximum(0); - progressBar->setMinimum(0); - progressBar->setValue(0); - progressLabel->show(); - progressBar->show(); - - wizard()->disableButtons(); - downloadFile(url); return false; } diff --git a/oracle/src/pagetemplates.h b/oracle/src/pagetemplates.h index 372aa2fef..79dcdd632 100644 --- a/oracle/src/pagetemplates.h +++ b/oracle/src/pagetemplates.h @@ -3,6 +3,8 @@ #include +class QFile; +class QRadioButton; class OracleWizard; class QCheckBox; class QLabel; @@ -43,15 +45,19 @@ protected: virtual QString getDefaultSavePath() = 0; virtual QString getWindowTitle() = 0; virtual QString getFileType() = 0; + virtual QString getFilePromptName() = 0; bool saveToFile(); bool internalSaveToFile(const QString &fileName); protected: QByteArray downloadData; - QLabel *urlLabel; - QLabel *pathLabel; + QRadioButton *urlRadioButton; + QRadioButton *fileRadioButton; QLineEdit *urlLineEdit; + QLineEdit *fileLineEdit; QPushButton *urlButton; + QPushButton *fileButton; + QLabel *pathLabel; QLabel *progressLabel; QProgressBar *progressBar; QCheckBox *defaultPathCheckBox; @@ -60,6 +66,7 @@ signals: void parsedDataReady(); private slots: void actRestoreDefaultUrl(); + void actLoadCardFile(); void actDownloadProgress(qint64 received, qint64 total); void actDownloadFinished(); }; 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/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index be1d1ff19..68080404c 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include 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/serversocketinterface.h b/servatrice/src/serversocketinterface.h index e522ea6c1..e10aa0dde 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -177,7 +177,7 @@ public: QString getConnectionType() const { return "tcp"; - }; + } private: QTcpSocket *socket; @@ -190,11 +190,11 @@ protected: void writeToSocket(QByteArray &data) { socket->write(data); - }; + } void flushSocket() { socket->flush(); - }; + } void initSessionDeprecated(); bool initTcpSession(); protected slots: @@ -224,7 +224,7 @@ public: QString getConnectionType() const { return "websocket"; - }; + } private: QWebSocket *socket; @@ -234,11 +234,11 @@ protected: void writeToSocket(QByteArray &data) { socket->sendBinaryMessage(data); - }; + } void flushSocket() { socket->flush(); - }; + } bool initWebsocketSession(); protected slots: void binaryMessageReceived(const QByteArray &message); 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 df2806585..29535c1dc 100644 --- a/tests/loading_from_clipboard/clipboard_testing.cpp +++ b/tests/loading_from_clipboard/clipboard_testing.cpp @@ -1,24 +1,34 @@ #include "clipboard_testing.h" #include -#include +#include + +DeckList getDeckList(const QString &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) { - QString cp(clipboard); - DeckList deckList; - QTextStream stream(&cp); // text stream requires local copy - deckList.loadFromStream_Plain(stream, false); + 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);