diff --git a/.ci/release_template.md b/.ci/release_template.md index 6e53a3ee1..c23e285c3 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -41,7 +41,7 @@ If you ever encounter a bug, have a suggestion or idea, or feel a need for a dev For basic information related to the app and getting started, please take a look at our official site: **https://cockatrice.github.io** -If you'd like to help and contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-). +If you'd like to help and contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#contribute). We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3ab3b7730..4744d611a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,8 @@ name: "🐛 Bug Report" about: Report an issue encountered while using Cockatrice title: '' -labels: 'Bug' +type: 'Bug' +labels: '' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 91f2edea6..d366d4ca8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,8 +1,9 @@ --- name: "💡 Feature Request" -about: Request a new feature +about: Request a new feature for Cockatrice title: '' -labels: 'Feature Request' +type: 'Feature' +labels: '' assignees: '' --- diff --git a/README.md b/README.md index e2433e695..48cacff78 100644 --- a/README.md +++ b/README.md @@ -5,159 +5,154 @@

Cockatrice | Download | - Get Involved | - Community | - Translations | + Related | + Community | + Contribute | Build | - Run | - License + Run

--- -
-To get started, ⇢ [view our webpage](https://cockatrice.github.io/)
-To get support or suggest changes ⇢ [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice)) -To help with development, see how to [get involved](#get-involved-) -

+

+To get started with Cockatrice ⇢ [view our webpage](https://cockatrice.github.io/)
+To get support, or suggest changes to the app ⇢ [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice)) +To help with development ⇢ learn [how to contribute](#contribute-) +


# Cockatrice -Cockatrice is an open-source, multiplatform program for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline. This project uses C++ and the Qt5 libraries.
+Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.

+This project uses C++ and the Qt libraries.
+First work on a webclient with Typescript was started as well.
-# Download [![Cockatrice Eternal Download Count](https://img.shields.io/github/downloads/cockatrice/cockatrice/total.svg)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice) +# Download [![Cockatrice Eternal Download Count](https://img.shields.io/github/downloads/cockatrice/cockatrice/total.svg)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0) -Downloads are available for full releases and the current beta version in development. There is no strict release schedule for either of them. +Downloads are available for **stable releases** and the current **beta version** in development. +There is no strict release schedule for either of them. +

+Latest stable release:
+[![Download Stable Release](https://img.shields.io/github/release/cockatrice/cockatrice.svg?label=version&colorB=0d7ebf "Download Latest Stable Release")](https://github.com/cockatrice/cockatrice/releases/latest) ![](https://img.shields.io/github/release-date/Cockatrice/Cockatrice.svg?label=released&colorB=0d7ebf "Release Date") [![](https://img.shields.io/github/downloads/cockatrice/cockatrice/latest/total.svg?label=downloads&colorB=0d7ebf "Number of Downloads")](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0)
+
+Latest beta version:
+[![Download Beta Release](https://img.shields.io/github/release/cockatrice/cockatrice/all.svg?label=version&colorB=f37f40 "Download Latest Beta Release")](https://github.com/cockatrice/cockatrice/releases) ![](https://img.shields.io/github/release-date-pre/Cockatrice/Cockatrice.svg?label=released&colorB=f37f40 "Release Date") [![](https://img.shields.io/github/downloads-pre/cockatrice/cockatrice/latest/total.svg?label=downloads&colorB=f37f40 "Number of Downloads")](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0) [![](https://img.shields.io/github/commits-since/Cockatrice/Cockatrice/latest.svg?label=changes&colorB=f37f40 "Changes over Stable Release")](https://github.com/Cockatrice/Cockatrice/pulls?q=is%3Apr+is%3Aclosed)
+While incorporating the latest fixes and features, beta builds may not be stable and/or contain new bugs!
+Please report any findings when testing them!
+
-- Latest `stable` release: [![Download from GitHub Releases](https://img.shields.io/github/release/cockatrice/cockatrice.svg)](https://github.com/cockatrice/cockatrice/releases/latest) [![DL Count on Latest Release](https://img.shields.io/github/downloads/cockatrice/cockatrice/latest/total.svg?label=downloads)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice)
- - Stable versions are checkpoints featuring major feature and UI enhancements. - - **Recommended for most users!** +# Related Repositories -- Latest `beta` release: [![Download from GitHub Pre-Releases](https://img.shields.io/github/release/cockatrice/cockatrice/all.svg)](https://github.com/cockatrice/cockatrice/releases) [![DL Count on Latest Pre-Release](https://img.shields.io/github/downloads-pre/cockatrice/cockatrice/latest/total.svg?label=downloads)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice) - - Beta versions include the most recently added features and bugfixes, but can be unstable. - - To be a Cockatrice Beta Tester, use this version. Find more information [here](https://github.com/Cockatrice/Cockatrice/wiki/Release-Channels)! - - -# Get Involved [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) - -Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with the project, contributors or fellow users of the app. Come here to talk about the application, features, or just to hang out.
-For support regarding specific servers, please contact that server's admin or forum for support rather than asking here.
- -To contribute code to the project, please review [the guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md). -We maintain two tags for contributors to find issues to work on: -- [Good first issue](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Good%20first%20issue%22%20): issues tagged in this way provide a simple way to get started. They don't require much experience to be worked on. -- [Help wanted](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Help%20Wanted%22%20): This tag is used for issues that we are looking for a contributor to work on. Often this is for feature suggestions we are willing to accept, but don't have the time to work on ourselves. - -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 review for changes they submit. - -We try to be responsive to new issues. We'll provide advice on how best to implement a feature; alternately, we can show you where the codebase is doing something similar before you get too far along. - -Cockatrice uses 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. +- [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 -# Community Resources +# Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) -- [Cockatrice Official Site](https://cockatrice.github.io) -- [Cockatrice Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) -- [Cockatrice Official Discord](https://discord.gg/3Z9yzmA) +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 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. -# Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://transifex.com/cockatrice/cockatrice/) -Cockatrice uses Transifex for translations. You can help us bring Cockatrice, Oracle and Webatrice to your language or just adjust single wordings right from within your browser by visiting our [Transifex project page](https://transifex.com/cockatrice/cockatrice/).
+# Contribute -Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about contributing!
+### Code + +To contribute code to the project, please review our [guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md) first.
+We maintain two tags for contributors to easier find issues to potentially work on: +- [![Good first issue](https://img.shields.io/github/issues/cockatrice/cockatrice/Good%20First%20Issue)](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Good%20first%20issue%22%20)
+Issues tagged in this way provide a simple way to get started. They don't require much experience to be worked on. +- [![Help wanted](https://img.shields.io/github/issues/cockatrice/cockatrice/Help%20Wanted)](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Help%20Wanted%22%20)
+This tag is used for issues that we are looking for somebody to pick up. Often this is for feature suggestions we support, but don't have the time to work on ourselves. + +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! + +Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide. + +
+Kudos to our amazing contributors ❤️ + + +
+ Made with contrib.rocks. +
+ +### Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://transifex.com/cockatrice/cockatrice/) + +Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice, Oracle and Webatrice to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://transifex.com/cockatrice/cockatrice/).
+ +Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting invovled, and join a group of hundreds of others!
# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Web](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush) -**Detailed compiling instructions can be found on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)** - -Dependencies: *(for minimum requirements search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))* +Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))* - [Qt](https://www.qt.io/developers/) - [protobuf](https://github.com/protocolbuffers/protobuf) - [CMake](https://www.cmake.org/) -Oracle can optionally use zlib and xz to load compressed files: +Oracle can *optionally* use some packages to load compressed card files: - [xz](https://tukaani.org/xz/) - [zlib](https://www.zlib.net/) -To compile: +
- mkdir build - cd build - cmake .. - make - -You can then run +**Basic compilation steps:** +```bash +mkdir build +cd build +cmake .. +make +``` +You can then + - Create a Cockatrice installation inside the `release` folder: + ```bash make install - -to get a cockatrice installation inside the `release` folder, or: - + ``` + - Or make an installation package specific to your system: + ```bash make package + ``` -to create a system-specific installation package. +>[!NOTE] +>Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice) -The following flags can be passed to `cmake`: +
-- `-DWITH_SERVER=1` Whether to build the server (default 0 = no). -- `-DWITH_CLIENT=0` Whether to build the client (default 1 = yes). -- `-DWITH_ORACLE=0` Whether to build oracle (default 1 = yes). -- `-DCMAKE_BUILD_TYPE=Debug` Compile in debug mode. Enables extra logging output, debug symbols, and much more verbose compiler warnings (default `Release`). -- `-DWARNING_AS_ERROR=0` Whether to treat compilation warnings as errors in debug mode (default 1 = yes). -- `-DUPDATE_TRANSLATIONS=1` Configure `make` to update the translation .ts files for new strings in the source code. Note: Running `make clean` will remove the .ts files (default 0 = no). -- `-DTEST=1` Enable regression tests (default 0 = no). Note: needs googletest, will be downloaded on the fly if unavailable. To run tests: ```make test```. -- `-DFORCE_USE_QT5=1` Skip looking for Qt6 before trying to find Qt5 +The following flags (with their non-default values) can be passed to `cmake`: +| Flag | Description | +| --- | --- | +| `-DWITH_SERVER=1` | Build Servatrice server | +| `-DWITH_CLIENT=0` | Don't build Cockatrice client | +| `-DWITH_ORACLE=0` | Don't build Oracle card database tool | +| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode
Enables extra logging output, debug symbols, and much more verbose compiler warnings | +| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode | +| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code
**Note:** `make clean` will remove the .ts files | +| `-DTEST=1` | Enable regression tests
**Note:** `make test` to run tests, *googletest* will be downloaded if not available | +| `-DFORCE_USE_QT5=1` | Skip looking for Qt6 before trying to find Qt5 | # Run -`Cockatrice` is the game client
-`Oracle` fetches card data
-`Servatrice` is the server
+Cockatrice is the game client
+Oracle fetches card data
+Servatrice is the server
-**Servatrice Docker container** +#### Docker -You can run an instance of Servatrice (the Cockatrice server) using [Docker](https://www.docker.com/what-docker) and the Cockatrice Dockerfile.
+You can run an instance of Servatrice (the Cockatrice server) using [Docker](https://www.docker.com/resources/what-container/) and our Dockerfile.
-First, create an image from the Dockerfile
-`cd /path/to/Cockatrice-Repo/` -`docker build -t servatrice .`
-And then run it
-`docker run -i -p 4748:4748 -t servatrice:latest`
- -For versions after 2.10.2, there is also an official image available on the GitHub container registry. You can pull it using
-`docker pull ghcr.io/cockatrice/servatrice:latest`
-And then run it
-`docker run -i -p 4748:4748 -t ghcr.io/cockatrice/servatrice:latest`
- ->Note: Running this command exposes the port 4748 of the docker container
-to permit connections to the server. - -Find more information on how to use Servatrice with Docker in our [wiki](https://github.com/Cockatrice/Cockatrice/wiki/Setting-up-Servatrice#using-docker). - -**Docker compose** - -There is also a docker-compose file available which will configure and run both a MySQL server and Servatrice. The docker-compose setup scripts can be found in the `servatrice/docker` folder and vary only slightly from the default sql and server .ini files. The setup scripts can either be modified in place, or docker-compose can mount alternative files into the images, as you prefer. - -To run Servatrice via docker-compose, first install docker-compose following the [install instructions](https://docs.docker.com/compose/install/). Once installed, run the following from the root of the repository: -```bash -docker-compose build # Build the Servatrice image using the same Dockerfile as above. -docker-compose up # Setup and run both the MySQL server and Servatrice. -``` - ->Note: Similar to the above Docker setup, this will expose port 4748. - ->Note: The first time running the docker-compose setup, the MySQL server will take a little time to run the initial setup scripts. Due to this, the Servatrice instance may fail the first few attempts to connect to the database. Servatrice is set to `restart: always` in the docker-compose.yml, which will allow it to continue attempting to start up. Once the MySQL scripts have completed, Servatrice should then connect automatically on the next attempt. - -**Docker compose in Windows** -A out of box working docker-compose file has been added to help setup in Windows. - -Docker in Windows requires additional steps in form of using Docker Desktop to allow resource sharing from the drive the volumes are mapped from, as well as potential workarounds needed to get file sharing working in Windows. This [StackOverflow discussion sheds some light on it](https://stackoverflow.com/questions/42203488/settings-to-windows-firewall-to-allow-docker-for-windows-to-share-drive) +For more information, have a look in our wiki section on [Setting up Servatrice](https://github.com/Cockatrice/Cockatrice/wiki/Setting-up-Servatrice#using-docker).
+There, you'll also find more hints on our **docker-compose** file which will configure and run both a MySQL server and Servatrice. # License [![GPLv2 License](https://img.shields.io/github/license/Cockatrice/Cockatrice.svg)](https://github.com/Cockatrice/Cockatrice/blob/master/LICENSE) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 9b50c9469..cbd0ed1d7 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -4,9 +4,245 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") -file(GLOB_RECURSE cockatrice_CPP_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp) - -set(cockatrice_SOURCES ${cockatrice_CPP_FILES} ${VERSION_STRING_CPP}) +set(cockatrice_SOURCES + ${VERSION_STRING_CPP} + # sort by alphabetical order, so that there is no debate about where to add new sources to the list + src/client/game_logic/abstract_client.cpp + src/client/game_logic/key_signals.cpp + src/client/get_text_with_max.cpp + src/client/menus/deck_editor/deck_editor_menu.cpp + src/client/network/client_update_checker.cpp + src/client/network/release_channel.cpp + src/client/network/replay_timeline_widget.cpp + src/client/network/sets_model.cpp + src/client/network/spoiler_background_updater.cpp + src/client/sound_engine.cpp + src/client/tabs/abstract_tab_deck_editor.cpp + src/client/tabs/api/edhrec/tab_edhrec.cpp + src/client/tabs/api/edhrec/tab_edhrec_main.cpp + src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp + src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp + src/client/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.cpp + src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp + src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.cpp + src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp + src/client/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp + src/client/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp + src/client/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp + src/client/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp + src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response_average_deck_statistics.cpp + src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.cpp + src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.cpp + src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.cpp + src/client/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.cpp + src/client/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.cpp + src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.cpp + src/client/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp + src/client/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp + src/client/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.cpp + src/client/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.cpp + src/client/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.cpp + src/client/tabs/tab.cpp + src/client/tabs/tab_account.cpp + src/client/tabs/tab_admin.cpp + src/client/tabs/tab_deck_editor.cpp + src/client/tabs/tab_deck_storage.cpp + src/client/tabs/tab_game.cpp + src/client/tabs/tab_logs.cpp + src/client/tabs/tab_message.cpp + src/client/tabs/tab_replays.cpp + src/client/tabs/tab_room.cpp + src/client/tabs/tab_server.cpp + src/client/tabs/tab_supervisor.cpp + src/client/tabs/tab_visual_database_display.cpp + src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp + src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp + src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp + src/client/tapped_out_interface.cpp + src/client/translate_counter_name.cpp + src/client/ui/layouts/flow_layout.cpp + src/client/ui/layouts/overlap_layout.cpp + src/client/ui/line_edit_completer.cpp + src/client/ui/phases_toolbar.cpp + src/client/ui/picture_loader/picture_loader.cpp + src/client/ui/picture_loader/picture_loader_worker.cpp + src/client/ui/picture_loader/picture_to_load.cpp + src/client/ui/pixel_map_generator.cpp + src/client/ui/theme_manager.cpp + src/client/ui/tip_of_the_day.cpp + src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp + src/client/ui/widgets/cards/additional_info/mana_cost_widget.cpp + src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp + src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp + src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp + src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp + src/client/ui/widgets/cards/card_info_display_widget.cpp + src/client/ui/widgets/cards/card_info_frame_widget.cpp + src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp + src/client/ui/widgets/cards/card_info_picture_widget.cpp + src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp + src/client/ui/widgets/cards/card_info_text_widget.cpp + src/client/ui/widgets/cards/card_size_widget.cpp + src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp + src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp + src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp + src/client/ui/widgets/deck_analytics/mana_base_widget.cpp + src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp + src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp + src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp + src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp + src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp + src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp + src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp + src/client/ui/widgets/general/display/banner_widget.cpp + src/client/ui/widgets/general/display/bar_widget.cpp + src/client/ui/widgets/general/display/dynamic_font_size_label.cpp + src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp + src/client/ui/widgets/general/display/labeled_input.cpp + src/client/ui/widgets/general/display/shadow_background_label.cpp + src/client/ui/widgets/general/layout_containers/flow_widget.cpp + src/client/ui/widgets/general/layout_containers/overlap_control_widget.cpp + src/client/ui/widgets/general/layout_containers/overlap_widget.cpp + src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp + src/client/ui/widgets/printing_selector/card_amount_widget.cpp + src/client/ui/widgets/printing_selector/printing_selector.cpp + src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp + src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp + src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp + src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp + src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp + src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp + src/client/ui/widgets/quick_settings/settings_button_widget.cpp + src/client/ui/widgets/quick_settings/settings_popup_widget.cpp + src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp + src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp + src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp + src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp + src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp + src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp + src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp + src/client/ui/widgets/visual_database_display/visual_database_filter_display_widget.cpp + src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp + src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp + src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp + src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp + src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp + src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp + src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp + src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp + src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp + src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp + src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp + src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp + src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp + src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp + src/client/ui/window_main.cpp + src/client/update_downloader.cpp + src/deck/custom_line_edit.cpp + src/deck/deck_list_model.cpp + src/deck/deck_loader.cpp + src/deck/deck_stats_interface.cpp + src/dialogs/dlg_connect.cpp + src/dialogs/dlg_convert_deck_to_cod_format.cpp + src/dialogs/dlg_create_game.cpp + src/dialogs/dlg_create_token.cpp + src/dialogs/dlg_edit_avatar.cpp + src/dialogs/dlg_edit_password.cpp + src/dialogs/dlg_edit_tokens.cpp + src/dialogs/dlg_edit_user.cpp + src/dialogs/dlg_filter_games.cpp + src/dialogs/dlg_forgot_password_challenge.cpp + src/dialogs/dlg_forgot_password_request.cpp + src/dialogs/dlg_forgot_password_reset.cpp + src/dialogs/dlg_load_deck.cpp + src/dialogs/dlg_load_deck_from_clipboard.cpp + src/dialogs/dlg_load_remote_deck.cpp + src/dialogs/dlg_manage_sets.cpp + src/dialogs/dlg_move_top_cards_until.cpp + src/dialogs/dlg_register.cpp + src/dialogs/dlg_roll_dice.cpp + src/dialogs/dlg_settings.cpp + src/dialogs/dlg_tip_of_the_day.cpp + src/dialogs/dlg_update.cpp + src/dialogs/dlg_view_log.cpp + src/game/board/abstract_counter.cpp + src/game/board/abstract_graphics_item.cpp + src/game/board/arrow_item.cpp + src/game/board/arrow_target.cpp + src/game/board/counter_general.cpp + src/game/cards/abstract_card_drag_item.cpp + src/game/cards/abstract_card_item.cpp + src/game/cards/card_completer_proxy_model.cpp + src/game/cards/card_database.cpp + src/game/cards/card_database_manager.cpp + src/game/cards/card_database_model.cpp + src/game/cards/card_database_parser/card_database_parser.cpp + src/game/cards/card_database_parser/cockatrice_xml_3.cpp + src/game/cards/card_database_parser/cockatrice_xml_4.cpp + src/game/cards/card_drag_item.cpp + src/game/cards/card_info.cpp + src/game/cards/card_item.cpp + src/game/cards/card_list.cpp + src/game/cards/card_search_model.cpp + src/game/deckview/deck_view.cpp + src/game/deckview/deck_view_container.cpp + src/game/filters/filter_builder.cpp + src/game/filters/filter_card.cpp + src/game/filters/filter_string.cpp + src/game/filters/filter_tree.cpp + src/game/filters/filter_tree_model.cpp + src/game/filters/syntax_help.cpp + src/game/game_scene.cpp + src/game/game_selector.cpp + src/game/game_view.cpp + src/game/games_model.cpp + src/game/hand_counter.cpp + src/game/phase.cpp + src/game/player/player.cpp + src/game/player/player_list_widget.cpp + src/game/player/player_target.cpp + src/game/zones/card_zone.cpp + src/game/zones/hand_zone.cpp + src/game/zones/pile_zone.cpp + src/game/zones/select_zone.cpp + src/game/zones/stack_zone.cpp + src/game/zones/table_zone.cpp + src/game/zones/view_zone.cpp + src/game/zones/view_zone_widget.cpp + src/main.cpp + src/server/chat_view/chat_view.cpp + src/server/handle_public_servers.cpp + src/server/local_client.cpp + src/server/local_server.cpp + src/server/local_server_interface.cpp + src/server/message_log_widget.cpp + src/server/pending_command.cpp + src/server/remote/remote_client.cpp + src/server/remote/remote_decklist_tree_widget.cpp + src/server/remote/remote_replay_list_tree_widget.cpp + src/server/user/user_context_menu.cpp + src/server/user/user_info_box.cpp + src/server/user/user_info_connection.cpp + src/server/user/user_list_manager.cpp + src/server/user/user_list_widget.cpp + src/settings/cache_settings.cpp + src/settings/card_database_settings.cpp + src/settings/card_override_settings.cpp + src/settings/debug_settings.cpp + src/settings/download_settings.cpp + src/settings/game_filters_settings.cpp + src/settings/layouts_settings.cpp + src/settings/message_settings.cpp + src/settings/recents_settings.cpp + src/settings/servers_settings.cpp + src/settings/settings_manager.cpp + src/settings/shortcut_treeview.cpp + src/settings/shortcuts_settings.cpp + src/utility/card_info_comparator.cpp + src/utility/levenshtein.cpp + src/utility/logger.cpp + src/utility/sequence_edit.cpp +) add_subdirectory(sounds) add_subdirectory(themes) diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index 5404c96fb..eeae47b7f 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -27,6 +27,7 @@ # servers_settings = false # shortcuts_settings = false +# local_client = false # remote_client = false # player = false diff --git a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp index 2d8c613a5..a01ccc3cf 100644 --- a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp +++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp @@ -3,14 +3,13 @@ #include "../../../settings/cache_settings.h" #include "../../../settings/shortcuts_settings.h" -DeckEditorMenu::DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *_deckEditor) - : QMenu(parent), deckEditor(_deckEditor) +DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), deckEditor(parent) { aNewDeck = new QAction(QString(), this); - connect(aNewDeck, SIGNAL(triggered()), deckEditor, SLOT(actNewDeck())); + connect(aNewDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actNewDeck); aLoadDeck = new QAction(QString(), this); - connect(aLoadDeck, SIGNAL(triggered()), deckEditor, SLOT(actLoadDeck())); + connect(aLoadDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actLoadDeck); loadRecentDeckMenu = new QMenu(this); connect(&SettingsCache::instance().recents(), &RecentsSettings::recentlyOpenedDeckPathsChanged, this, @@ -22,47 +21,50 @@ DeckEditorMenu::DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *_deckEdit updateRecentlyOpened(); aSaveDeck = new QAction(QString(), this); - connect(aSaveDeck, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeck())); + connect(aSaveDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actSaveDeck); aSaveDeckAs = new QAction(QString(), this); - connect(aSaveDeckAs, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeckAs())); + connect(aSaveDeckAs, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actSaveDeckAs); aLoadDeckFromClipboard = new QAction(QString(), this); - connect(aLoadDeckFromClipboard, SIGNAL(triggered()), deckEditor, SLOT(actLoadDeckFromClipboard())); + connect(aLoadDeckFromClipboard, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actLoadDeckFromClipboard); aEditDeckInClipboard = new QAction(QString(), this); - connect(aEditDeckInClipboard, SIGNAL(triggered()), deckEditor, SLOT(actEditDeckInClipboard())); + connect(aEditDeckInClipboard, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actEditDeckInClipboard); aEditDeckInClipboardRaw = new QAction(QString(), this); - connect(aEditDeckInClipboardRaw, SIGNAL(triggered()), deckEditor, SLOT(actEditDeckInClipboardRaw())); + connect(aEditDeckInClipboardRaw, &QAction::triggered, deckEditor, + &AbstractTabDeckEditor::actEditDeckInClipboardRaw); aSaveDeckToClipboard = new QAction(QString(), this); - connect(aSaveDeckToClipboard, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeckToClipboard())); + connect(aSaveDeckToClipboard, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actSaveDeckToClipboard); aSaveDeckToClipboardNoSetInfo = new QAction(QString(), this); - connect(aSaveDeckToClipboardNoSetInfo, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeckToClipboardNoSetInfo())); + connect(aSaveDeckToClipboardNoSetInfo, &QAction::triggered, deckEditor, + &AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo); aSaveDeckToClipboardRaw = new QAction(QString(), this); - connect(aSaveDeckToClipboardRaw, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeckToClipboardRaw())); + connect(aSaveDeckToClipboardRaw, &QAction::triggered, deckEditor, + &AbstractTabDeckEditor::actSaveDeckToClipboardRaw); aSaveDeckToClipboardRawNoSetInfo = new QAction(QString(), this); - connect(aSaveDeckToClipboardRawNoSetInfo, SIGNAL(triggered()), deckEditor, - SLOT(actSaveDeckToClipboardRawNoSetInfo())); + connect(aSaveDeckToClipboardRawNoSetInfo, &QAction::triggered, deckEditor, + &AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo); aPrintDeck = new QAction(QString(), this); - connect(aPrintDeck, SIGNAL(triggered()), deckEditor, SLOT(actPrintDeck())); + connect(aPrintDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actPrintDeck); aExportDeckDecklist = new QAction(QString(), this); - connect(aExportDeckDecklist, SIGNAL(triggered()), deckEditor, SLOT(actExportDeckDecklist())); + connect(aExportDeckDecklist, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actExportDeckDecklist); aExportDeckDecklistXyz = new QAction(QString(), this); - connect(aExportDeckDecklistXyz, SIGNAL(triggered()), deckEditor, SLOT(actExportDeckDecklistXyz())); + connect(aExportDeckDecklistXyz, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actExportDeckDecklistXyz); aAnalyzeDeckDeckstats = new QAction(QString(), this); - connect(aAnalyzeDeckDeckstats, SIGNAL(triggered()), deckEditor, SLOT(actAnalyzeDeckDeckstats())); + connect(aAnalyzeDeckDeckstats, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actAnalyzeDeckDeckstats); aAnalyzeDeckTappedout = new QAction(QString(), this); - connect(aAnalyzeDeckTappedout, SIGNAL(triggered()), deckEditor, SLOT(actAnalyzeDeckTappedout())); + connect(aAnalyzeDeckTappedout, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actAnalyzeDeckTappedout); analyzeDeckMenu = new QMenu(this); analyzeDeckMenu->addAction(aExportDeckDecklist); @@ -103,7 +105,8 @@ DeckEditorMenu::DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *_deckEdit addAction(aClose); retranslateUi(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &DeckEditorMenu::refreshShortcuts); refreshShortcuts(); } diff --git a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h index a713b009f..7bfc58c4b 100644 --- a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h +++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h @@ -10,7 +10,7 @@ class DeckEditorMenu : public QMenu { Q_OBJECT public: - explicit DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *deckEditor); + explicit DeckEditorMenu(AbstractTabDeckEditor *parent); AbstractTabDeckEditor *deckEditor; diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp index fe2148293..1c599034b 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -65,7 +66,8 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta connect(filterDockWidget, &DeckEditorFilterDockWidget::clearAllDatabaseFilters, databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &AbstractTabDeckEditor::refreshShortcuts); } void AbstractTabDeckEditor::updateCard(CardInfoPtr _card) @@ -124,8 +126,13 @@ void AbstractTabDeckEditor::actSwapCard(CardInfoPtr info, QString zoneName) { QString providerId = CardDatabaseManager::getInstance()->getSetInfoForCard(info).getProperty("uuid"); QString collectorNumber = CardDatabaseManager::getInstance()->getSetInfoForCard(info).getProperty("num"); - deckDockWidget->swapCard( - deckDockWidget->deckModel->findCard(info->getName(), zoneName, providerId, collectorNumber)); + + QModelIndex foundCard = deckDockWidget->deckModel->findCard(info->getName(), zoneName, providerId, collectorNumber); + if (!foundCard.isValid()) { + foundCard = deckDockWidget->deckModel->findCard(info->getName(), zoneName); + } + + deckDockWidget->swapCard(foundCard); } /** @@ -332,8 +339,7 @@ bool AbstractTabDeckEditor::actSaveDeck() cmd.set_deck_list(deckString.toStdString()); PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(saveDeckRemoteFinished(Response))); + connect(pend, &PendingCommand::finished, this, &AbstractTabDeckEditor::saveDeckRemoteFinished); tabSupervisor->getClient()->sendCommand(pend); return true; @@ -357,6 +363,7 @@ bool AbstractTabDeckEditor::actSaveDeckAs() dialog.setDefaultSuffix("cod"); dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); dialog.selectFile(getDeckList()->getName().trimmed()); + if (!dialog.exec()) return false; @@ -451,7 +458,7 @@ void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo() void AbstractTabDeckEditor::actPrintDeck() { auto *dlg = new QPrintPreviewDialog(this); - connect(dlg, SIGNAL(paintRequested(QPrinter *)), deckDockWidget->deckModel, SLOT(printDeckList(QPrinter *))); + connect(dlg, &QPrintPreviewDialog::paintRequested, deckDockWidget->deckModel, &DeckListModel::printDeckList); dlg->exec(); } diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h index 0a5f97f56..796213d27 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h @@ -1,7 +1,7 @@ #ifndef TAB_GENERIC_DECK_EDITOR_H #define TAB_GENERIC_DECK_EDITOR_H -#include "../../game/cards/card_database.h" +#include "../../game/cards/card_info.h" #include "../menus/deck_editor/deck_editor_menu.h" #include "../ui/widgets/deck_editor/deck_editor_card_info_dock_widget.h" #include "../ui/widgets/deck_editor/deck_editor_database_display_widget.h" @@ -44,6 +44,8 @@ class AbstractTabDeckEditor : public Tab { Q_OBJECT + friend class DeckEditorMenu; + public: explicit AbstractTabDeckEditor(TabSupervisor *_tabSupervisor); @@ -67,7 +69,7 @@ public: DeckEditorPrintingSelectorDockWidget *printingSelectorDockWidget; public slots: - void onDeckChanged(); + virtual void onDeckChanged(); void updateCard(CardInfoPtr _card); void actAddCard(CardInfoPtr info); void actAddCardToSideboard(CardInfoPtr info); @@ -90,7 +92,7 @@ protected slots: void cleanDeckAndResetModified(); virtual void actLoadDeck(); bool actSaveDeck(); - bool actSaveDeckAs(); + virtual bool actSaveDeckAs(); virtual void actLoadDeckFromClipboard(); void actEditDeckInClipboard(); void actEditDeckInClipboardRaw(); diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp similarity index 76% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp rename to cockatrice/src/client/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp index aac540f90..f071949f2 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp @@ -1,11 +1,11 @@ -#include "edhrec_commander_api_response_archidekt_links.h" +#include "edhrec_api_response_archidekt_links.h" #include #include #include #include -void EdhrecCommanderApiResponseArchidektLink::fromJson(const QJsonObject &json) +void EdhrecApiResponseArchidektLink::fromJson(const QJsonObject &json) { c = json.value("c").toString(); f = json.value("f").toInt(0); @@ -13,7 +13,7 @@ void EdhrecCommanderApiResponseArchidektLink::fromJson(const QJsonObject &json) u = json.value("u").toString(); } -void EdhrecCommanderApiResponseArchidektLink::debugPrint() const +void EdhrecApiResponseArchidektLink::debugPrint() const { qDebug() << " C:" << c; qDebug() << " F:" << f; @@ -27,7 +27,7 @@ void EdhrecCommanderApiResponseArchidektLinks::fromJson(const QJsonArray &json) for (const QJsonValue &value : json) { if (value.isObject()) { QJsonObject entryJson = value.toObject(); - EdhrecCommanderApiResponseArchidektLink entry; + EdhrecApiResponseArchidektLink entry; entry.fromJson(entryJson); entries.append(entry); } diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.h b/cockatrice/src/client/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h similarity index 83% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.h rename to cockatrice/src/client/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h index 537dbb1fc..b47101e78 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.h +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h @@ -7,7 +7,7 @@ #include // Represents a single Archidekt entry -class EdhrecCommanderApiResponseArchidektLink +class EdhrecApiResponseArchidektLink { public: QString c; @@ -23,7 +23,7 @@ public: class EdhrecCommanderApiResponseArchidektLinks { public: - QVector entries; + QVector entries; void fromJson(const QJsonArray &json); void debugPrint() const; diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp new file mode 100644 index 000000000..1297fc157 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp @@ -0,0 +1,47 @@ +#include "edhrec_average_deck_api_response.h" + +#include +#include + +void EdhrecAverageDeckApiResponse::fromJson(const QJsonObject &json) +{ + // Parse the collapsed DeckStatistics + deckStats.fromJson(json); + + // Parse Archidekt section + QJsonArray archidektJson = json.value("archidekt").toArray(); + archidekt.fromJson(archidektJson); + + // Parse other fields + similar = json.value("similar").toObject(); + header = json.value("header").toString(); + panels = json.value("panels").toObject(); + description = json.value("description").toString(); + QJsonObject containerJson = json.value("container").toObject(); + container.fromJson(containerJson); + QJsonArray cardsJson = json.value("deck").toArray(); + deck.fromJson(cardsJson); +} + +void EdhrecAverageDeckApiResponse::debugPrint() const +{ + qDebug() << "Deck Statistics:"; + qDebug() << " Creature:" << deckStats.creature; + qDebug() << " Instant:" << deckStats.instant; + qDebug() << " Sorcery:" << deckStats.sorcery; + qDebug() << " Artifact:" << deckStats.artifact; + qDebug() << " Enchantment:" << deckStats.enchantment; + qDebug() << " Battle:" << deckStats.battle; + qDebug() << " Planeswalker:" << deckStats.planeswalker; + qDebug() << " Land:" << deckStats.land; + qDebug() << " Basic:" << deckStats.basic; + qDebug() << " Nonbasic:" << deckStats.nonbasic; + + archidekt.debugPrint(); + + qDebug() << "Similar:" << similar; + qDebug() << "Header:" << header; + qDebug() << "Panels:" << panels; + qDebug() << "Description:" << description; + container.debugPrint(); +} diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h b/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h new file mode 100644 index 000000000..2d330e3e8 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h @@ -0,0 +1,30 @@ +#ifndef EDHREC_AVERAGE_DECK_API_RESPONSE_H +#define EDHREC_AVERAGE_DECK_API_RESPONSE_H + +#include "../archidekt_links/edhrec_api_response_archidekt_links.h" +#include "../cards/edhrec_api_response_card_container.h" +#include "../commander/edhrec_commander_api_response_average_deck_statistics.h" +#include "edhrec_deck_api_response.h" + +#include +#include +#include + +// Represents the main structure of the JSON +class EdhrecAverageDeckApiResponse +{ +public: + EdhrecCommanderApiResponseAverageDeckStatistics deckStats; + EdhrecCommanderApiResponseArchidektLinks archidekt; + QJsonObject similar; + QString header; + QJsonObject panels; + QString description; + EdhrecApiResponseCardContainer container; + EdhrecDeckApiResponse deck; + + void fromJson(const QJsonObject &json); + void debugPrint() const; +}; + +#endif // EDHREC_AVERAGE_DECK_API_RESPONSE_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp new file mode 100644 index 000000000..a2a015f72 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp @@ -0,0 +1,27 @@ +#include "edhrec_deck_api_response.h" + +#include "../../../../../../deck/deck_loader.h" + +#include +#include +#include +#include +#include + +void EdhrecDeckApiResponse::fromJson(const QJsonArray &json) +{ + QString deckList; + for (const QJsonValue &cardlistValue : json) { + deckList += cardlistValue.toString() + "\n"; + } + + deckLoader = new DeckLoader(); + + QTextStream stream(&deckList); + deckLoader->loadFromStream_Plain(stream, true); +} + +void EdhrecDeckApiResponse::debugPrint() const +{ + qDebug() << "Breadcrumb:"; +} diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h b/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h new file mode 100644 index 000000000..b888cb134 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h @@ -0,0 +1,27 @@ +#ifndef EDHREC_DECK_API_RESPONSE_H +#define EDHREC_DECK_API_RESPONSE_H + +#include "../../../../../../deck/deck_loader.h" + +#include +#include +#include +#include +#include + +class EdhrecDeckApiResponse +{ +public: + // Constructor + EdhrecDeckApiResponse() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonArray &json); + + // Debug method for logging + void debugPrint() const; + + DeckLoader *deckLoader; +}; + +#endif // EDHREC_DECK_API_RESPONSE_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.cpp similarity index 57% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp rename to cockatrice/src/client/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.cpp index 6454e5500..1231d147a 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.cpp @@ -1,4 +1,4 @@ -#include "edhrec_commander_api_response_card_prices.h" +#include "edhrec_api_response_card_prices.h" #include @@ -18,14 +18,14 @@ void CardPrices::fromJson(const QJsonObject &json) void CardPrices::debugPrint() const { - qDebug() << "Card Prices:"; - qDebug() << "Cardhoarder:" << cardhoarder; - qDebug() << "Cardkingdom:" << cardkingdom; - qDebug() << "Cardmarket:" << cardmarket; - qDebug() << "Face2Face:" << face2face; - qDebug() << "Manapool:" << manapool; - qDebug() << "Mtgstocks:" << mtgstocks; - qDebug() << "SCG:" << scg; - qDebug() << "TCGL:" << tcgl; - qDebug() << "Tcgplayer:" << tcgplayer; + qInfo() << "Card Prices:"; + qInfo() << "Cardhoarder:" << cardhoarder; + qInfo() << "Cardkingdom:" << cardkingdom; + qInfo() << "Cardmarket:" << cardmarket; + qInfo() << "Face2Face:" << face2face; + qInfo() << "Manapool:" << manapool; + qInfo() << "Mtgstocks:" << mtgstocks; + qInfo() << "SCG:" << scg; + qInfo() << "TCGL:" << tcgl; + qInfo() << "Tcgplayer:" << tcgplayer; } diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.h b/cockatrice/src/client/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.h similarity index 100% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.h rename to cockatrice/src/client/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.h diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.cpp similarity index 83% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp rename to cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.cpp index f8ffab787..3fef8be34 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.cpp @@ -1,10 +1,10 @@ -#include "edhrec_commander_api_response_card_container.h" +#include "edhrec_api_response_card_container.h" #include #include #include -void EdhrecCommanderApiResponseCardContainer::fromJson(const QJsonObject &json) +void EdhrecApiResponseCardContainer::fromJson(const QJsonObject &json) { // Parse breadcrumb QJsonArray breadcrumbArray = json.value("breadcrumb").toArray(); @@ -20,7 +20,7 @@ void EdhrecCommanderApiResponseCardContainer::fromJson(const QJsonObject &json) for (const QJsonValue &cardlistValue : cardlistsArray) { QJsonObject cardlistObj = cardlistValue.toObject(); QJsonArray cardviewsArray = cardlistObj.value("cardviews").toArray(); - EdhrecCommanderApiResponseCardList cardView; + EdhrecApiResponseCardList cardView; cardView.fromJson(cardlistValue.toObject()); cardlists.push_back(cardView); } @@ -29,7 +29,7 @@ void EdhrecCommanderApiResponseCardContainer::fromJson(const QJsonObject &json) title = json.value("title").toString(); } -void EdhrecCommanderApiResponseCardContainer::debugPrint() const +void EdhrecApiResponseCardContainer::debugPrint() const { qDebug() << "Breadcrumb:"; for (const auto &breadcrumbEntry : breadcrumb) { diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.h b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h similarity index 79% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.h rename to cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h index d2ea05874..94650785f 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.h +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h @@ -1,7 +1,7 @@ #ifndef CONTAINER_ENTRY_H #define CONTAINER_ENTRY_H -#include "edhrec_commander_api_response_card_list.h" +#include "edhrec_api_response_card_list.h" #include "edhrec_commander_api_response_commander_details.h" #include @@ -10,11 +10,11 @@ #include #include -class EdhrecCommanderApiResponseCardContainer +class EdhrecApiResponseCardContainer { public: // Constructor - EdhrecCommanderApiResponseCardContainer() = default; + EdhrecApiResponseCardContainer() = default; // Parse deck-related data from JSON void fromJson(const QJsonObject &json); @@ -35,7 +35,7 @@ public: { return card; } - const QVector &getCardlists() const + const QVector &getCardlists() const { return cardlists; } @@ -52,7 +52,7 @@ private: QString description; QVector breadcrumb; EdhrecCommanderApiResponseCommanderDetails card; - QVector cardlists; + QVector cardlists; QString keywords; QString title; }; diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.cpp similarity index 78% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp rename to cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.cpp index 2f2791de1..a4a746c61 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.cpp @@ -1,13 +1,13 @@ -#include "edhrec_commander_api_response_card_details.h" +#include "edhrec_api_response_card_details.h" #include -EdhrecCommanderApiResponseCardDetails::EdhrecCommanderApiResponseCardDetails() +EdhrecApiResponseCardDetails::EdhrecApiResponseCardDetails() : synergy(0.0), inclusion(0), numDecks(0), potentialDecks(0) { } -void EdhrecCommanderApiResponseCardDetails::fromJson(const QJsonObject &json) +void EdhrecApiResponseCardDetails::fromJson(const QJsonObject &json) { // Parse the fields from the JSON object name = json.value("name").toString(); @@ -21,7 +21,7 @@ void EdhrecCommanderApiResponseCardDetails::fromJson(const QJsonObject &json) potentialDecks = json.value("potential_decks").toInt(0); } -void EdhrecCommanderApiResponseCardDetails::debugPrint() const +void EdhrecApiResponseCardDetails::debugPrint() const { // Print out all the fields for debugging qDebug() << "Name:" << name; diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.h b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.h similarity index 84% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.h rename to cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.h index 82d85824b..9f75aac72 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.h +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.h @@ -4,7 +4,7 @@ #include #include -class EdhrecCommanderApiResponseCardDetails +class EdhrecApiResponseCardDetails { public: QString name; @@ -17,7 +17,7 @@ public: int numDecks; int potentialDecks; - EdhrecCommanderApiResponseCardDetails(); + EdhrecApiResponseCardDetails(); // Method to populate the object from a JSON object void fromJson(const QJsonObject &json); diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.cpp similarity index 59% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp rename to cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.cpp index 5d3eebedd..72d6fe2cc 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.cpp @@ -1,12 +1,12 @@ -#include "edhrec_commander_api_response_card_list.h" +#include "edhrec_api_response_card_list.h" #include -EdhrecCommanderApiResponseCardList::EdhrecCommanderApiResponseCardList() +EdhrecApiResponseCardList::EdhrecApiResponseCardList() { } -void EdhrecCommanderApiResponseCardList::fromJson(const QJsonObject &json) +void EdhrecApiResponseCardList::fromJson(const QJsonObject &json) { // Parse the header from the JSON object header = json.value("header").toString(); @@ -15,19 +15,19 @@ void EdhrecCommanderApiResponseCardList::fromJson(const QJsonObject &json) QJsonArray cardviewsArray = json.value("cardviews").toArray(); for (const QJsonValue &value : cardviewsArray) { QJsonObject cardviewObj = value.toObject(); - EdhrecCommanderApiResponseCardDetails cardView; + EdhrecApiResponseCardDetails cardView; cardView.fromJson(cardviewObj); cardViews.append(cardView); } } -void EdhrecCommanderApiResponseCardList::debugPrint() const +void EdhrecApiResponseCardList::debugPrint() const { // Print out the header qDebug() << "Header:" << header; // Print out all the CardView objects - for (const EdhrecCommanderApiResponseCardDetails &cardView : cardViews) { + for (const EdhrecApiResponseCardDetails &cardView : cardViews) { cardView.debugPrint(); } } diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.h b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h similarity index 65% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.h rename to cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h index e0ddc3466..9fcacfffe 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.h +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h @@ -1,21 +1,21 @@ #ifndef CARD_LIST_H #define CARD_LIST_H -#include "edhrec_commander_api_response_card_details.h" +#include "edhrec_api_response_card_details.h" #include #include #include #include -class EdhrecCommanderApiResponseCardList +class EdhrecApiResponseCardList { public: QString header; - QList cardViews; + QList cardViews; // Default constructor - EdhrecCommanderApiResponseCardList(); + EdhrecApiResponseCardList(); // Method to populate the object from a JSON object void fromJson(const QJsonObject &json); diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.cpp similarity index 100% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp rename to cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.cpp diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.h b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h similarity index 98% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.h rename to cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h index 78ccfc23a..7b754c9b2 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.h +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h @@ -1,7 +1,7 @@ #ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H #define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H -#include "edhrec_commander_api_response_card_prices.h" +#include "../card_prices/edhrec_api_response_card_prices.h" #include #include diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.cpp similarity index 100% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp rename to cockatrice/src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.cpp diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.h b/cockatrice/src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h similarity index 76% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.h rename to cockatrice/src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h index 91cf3afbc..f1141a113 100644 --- a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.h +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h @@ -1,9 +1,9 @@ #ifndef DECKDATA_H #define DECKDATA_H -#include "edhrec_commander_api_response_archidekt_links.h" +#include "../archidekt_links//edhrec_api_response_archidekt_links.h" +#include "../cards/edhrec_api_response_card_container.h" #include "edhrec_commander_api_response_average_deck_statistics.h" -#include "edhrec_commander_api_response_card_container.h" #include #include @@ -19,7 +19,7 @@ public: QString header; QJsonObject panels; QString description; - EdhrecCommanderApiResponseCardContainer container; + EdhrecApiResponseCardContainer container; void fromJson(const QJsonObject &json); void debugPrint() const; diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response_average_deck_statistics.cpp similarity index 100% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp rename to cockatrice/src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response_average_deck_statistics.cpp diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.h b/cockatrice/src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response_average_deck_statistics.h similarity index 100% rename from cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.h rename to cockatrice/src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response_average_deck_statistics.h diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.cpp new file mode 100644 index 000000000..26122ce89 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.cpp @@ -0,0 +1,19 @@ +#include "edhrec_top_cards_api_response.h" + +#include +#include + +void EdhrecTopCardsApiResponse::fromJson(const QJsonObject &json) +{ + header = json.value("header").toString(); + description = json.value("description").toString(); + QJsonObject containerJson = json.value("container").toObject(); + container.fromJson(containerJson); +} + +void EdhrecTopCardsApiResponse::debugPrint() const +{ + qDebug() << "Header:" << header; + qDebug() << "Description:" << description; + container.debugPrint(); +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h b/cockatrice/src/client/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h new file mode 100644 index 000000000..569c31894 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h @@ -0,0 +1,20 @@ +#ifndef EDHREC_TOP_CARDS_API_RESPONSE_H +#define EDHREC_TOP_CARDS_API_RESPONSE_H + +#include "../cards/edhrec_api_response_card_container.h" + +#include +#include + +class EdhrecTopCardsApiResponse +{ +public: + QString header; + QString description; + EdhrecApiResponseCardContainer container; + + void fromJson(const QJsonObject &json); + void debugPrint() const; +}; + +#endif // EDHREC_TOP_CARDS_API_RESPONSE_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.cpp new file mode 100644 index 000000000..83b10c9e5 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.cpp @@ -0,0 +1,19 @@ +#include "edhrec_top_commanders_api_response.h" + +#include +#include + +void EdhrecTopCommandersApiResponse::fromJson(const QJsonObject &json) +{ + header = json.value("header").toString(); + description = json.value("description").toString(); + QJsonObject containerJson = json.value("container").toObject(); + container.fromJson(containerJson); +} + +void EdhrecTopCommandersApiResponse::debugPrint() const +{ + qDebug() << "Header:" << header; + qDebug() << "Description:" << description; + container.debugPrint(); +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h b/cockatrice/src/client/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h new file mode 100644 index 000000000..526611eb7 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h @@ -0,0 +1,20 @@ +#ifndef EDHREC_TOP_COMMANDERS_API_RESPONSE_H +#define EDHREC_TOP_COMMANDERS_API_RESPONSE_H + +#include "../cards/edhrec_api_response_card_container.h" + +#include +#include + +class EdhrecTopCommandersApiResponse +{ +public: + QString header; + QString description; + EdhrecApiResponseCardContainer container; + + void fromJson(const QJsonObject &json); + void debugPrint() const; +}; + +#endif // EDHREC_TOP_COMMANDERS_API_RESPONSE_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.cpp new file mode 100644 index 000000000..56eb4fa4f --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.cpp @@ -0,0 +1,19 @@ +#include "edhrec_top_tags_api_response.h" + +#include +#include + +void EdhrecTopTagsApiResponse::fromJson(const QJsonObject &json) +{ + header = json.value("header").toString(); + description = json.value("description").toString(); + QJsonObject containerJson = json.value("container").toObject(); + container.fromJson(containerJson); +} + +void EdhrecTopTagsApiResponse::debugPrint() const +{ + qDebug() << "Header:" << header; + qDebug() << "Description:" << description; + container.debugPrint(); +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h b/cockatrice/src/client/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h new file mode 100644 index 000000000..f31d03a8e --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h @@ -0,0 +1,20 @@ +#ifndef EDHREC_TOP_TAGS_API_RESPONSE_H +#define EDHREC_TOP_TAGS_API_RESPONSE_H + +#include "../cards/edhrec_api_response_card_container.h" + +#include +#include + +class EdhrecTopTagsApiResponse +{ +public: + QString header; + QString description; + EdhrecApiResponseCardContainer container; + + void fromJson(const QJsonObject &json); + void debugPrint() const; +}; + +#endif // EDHREC_TOP_TAGS_API_RESPONSE_H diff --git a/cockatrice/src/client/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.cpp new file mode 100644 index 000000000..75902daad --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.cpp @@ -0,0 +1,66 @@ +#include "edhrec_api_response_card_prices_display_widget.h" + +EdhrecApiResponseCardPricesDisplayWidget::EdhrecApiResponseCardPricesDisplayWidget(QWidget *parent, + const CardPrices &_cardPrices) + : QWidget(parent), cardPrices(_cardPrices) +{ + layout = new QGridLayout(this); + setLayout(layout); + + cardHoarderLabel = new QLabel(this); + cardHoarderPrice = new QLabel(QString::number(cardPrices.getCardhoarder().value("price").toDouble()), this); + cardKingdomLabel = new QLabel(this); + cardKingdomPrice = new QLabel(QString::number(cardPrices.getCardkingdom().value("price").toDouble()), this); + cardMarketLabel = new QLabel(this); + cardMarketPrice = new QLabel(QString::number(cardPrices.getCardmarket().value("price").toDouble()), this); + face2faceLabel = new QLabel(this); + face2facePrice = new QLabel(QString::number(cardPrices.getFace2face().value("price").toDouble()), this); + manaPoolLabel = new QLabel(this); + manaPoolPrice = new QLabel(QString::number(cardPrices.getManapool().value("price").toDouble()), this); + mtgStocksLabel = new QLabel(this); + mtgStocksPrice = new QLabel(QString::number(cardPrices.getMtgstocks().value("price").toDouble()), this); + scgLabel = new QLabel(this); + scgPrice = new QLabel(QString::number(cardPrices.getScg().value("price").toDouble()), this); + tcglLabel = new QLabel(this); + tcglPrice = new QLabel(QString::number(cardPrices.getTcgl().value("price").toDouble()), this); + tcgplayerLabel = new QLabel(this); + tcgplayerPrice = new QLabel(QString::number(cardPrices.getTcgplayer().value("price").toDouble()), this); + + layout->addWidget(cardHoarderLabel, 0, 0); + layout->addWidget(cardHoarderPrice, 0, 1); + layout->addWidget(cardKingdomLabel, 0, 2); + layout->addWidget(cardKingdomPrice, 0, 3); + + layout->addWidget(cardMarketLabel, 1, 0); + layout->addWidget(cardMarketPrice, 1, 1); + layout->addWidget(face2faceLabel, 1, 2); + layout->addWidget(face2facePrice, 1, 3); + + layout->addWidget(manaPoolLabel, 2, 0); + layout->addWidget(manaPoolPrice, 2, 1); + layout->addWidget(mtgStocksLabel, 2, 2); + layout->addWidget(mtgStocksPrice, 2, 3); + + layout->addWidget(scgLabel, 3, 0); + layout->addWidget(scgPrice, 3, 1); + layout->addWidget(tcglLabel, 3, 2); + layout->addWidget(tcglPrice, 3, 3); + + layout->addWidget(tcgplayerLabel, 4, 0); + layout->addWidget(tcgplayerPrice, 4, 1); + + retranslateUi(); +} + +void EdhrecApiResponseCardPricesDisplayWidget::retranslateUi() +{ + cardHoarderLabel->setText(tr("Card Hoarder")); + cardKingdomLabel->setText(tr("Card Kingdom")); + cardMarketLabel->setText(tr("Card Market")); + face2faceLabel->setText(tr("Face 2-Face")); + manaPoolLabel->setText(tr("Mana Pool")); + mtgStocksLabel->setText(tr("MTG Stocks")); + scgLabel->setText(tr("Scg")); + tcglLabel->setText(tr("Tcgl")); + tcgplayerLabel->setText(tr("Tcgplayer")); +} diff --git a/cockatrice/src/client/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.h new file mode 100644 index 000000000..8dae375fe --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.h @@ -0,0 +1,42 @@ +#ifndef EDHREC_API_RESPONSE_CARD_PRICES_DISPLAY_WIDGET_H +#define EDHREC_API_RESPONSE_CARD_PRICES_DISPLAY_WIDGET_H + +#include "../../api_response/card_prices/edhrec_api_response_card_prices.h" + +#include +#include +#include + +class EdhrecApiResponseCardPricesDisplayWidget : public QWidget +{ + Q_OBJECT +public: + EdhrecApiResponseCardPricesDisplayWidget(QWidget *parent, const CardPrices &cardPrices); + +public slots: + void retranslateUi(); + +private: + CardPrices cardPrices; + QGridLayout *layout; + QLabel *cardHoarderLabel; + QLabel *cardHoarderPrice; + QLabel *cardKingdomLabel; + QLabel *cardKingdomPrice; + QLabel *cardMarketLabel; + QLabel *cardMarketPrice; + QLabel *face2faceLabel; + QLabel *face2facePrice; + QLabel *manaPoolLabel; + QLabel *manaPoolPrice; + QLabel *mtgStocksLabel; + QLabel *mtgStocksPrice; + QLabel *scgLabel; + QLabel *scgPrice; + QLabel *tcglLabel; + QLabel *tcglPrice; + QLabel *tcgplayerLabel; + QLabel *tcgplayerPrice; +}; + +#endif // EDHREC_API_RESPONSE_CARD_PRICES_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp new file mode 100644 index 000000000..f6e0cf4fa --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -0,0 +1,67 @@ +#include "edhrec_api_response_card_details_display_widget.h" + +#include "../../../../../../game/cards/card_database_manager.h" +#include "../../tab_edhrec_main.h" + +EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWidget( + QWidget *parent, + const EdhrecApiResponseCardDetails &_toDisplay) + : QWidget(parent), toDisplay(_toDisplay) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + cardPictureWidget = new CardInfoPictureWidget(this); + cardPictureWidget->setCard(CardDatabaseManager::getInstance()->guessCard(toDisplay.sanitized)); + + label = new QLabel(this); + label->setText(toDisplay.name + "\n" + toDisplay.label); + label->setAlignment(Qt::AlignHCenter); + + int inclusionRate = 0; + // Set label color based on inclusion rate + if (toDisplay.potentialDecks != 0) { + inclusionRate = (toDisplay.numDecks * 100) / toDisplay.potentialDecks; + } + + QColor labelColor; + if (inclusionRate <= 30) { + labelColor = QColor(255, 0, 0); // Red + } else if (inclusionRate <= 60) { + int red = 255 - ((inclusionRate - 30) * 2); + int green = (inclusionRate - 30) * 4; // Adjust green to make the transition smoother + labelColor = QColor(red, green, 0); // purple-ish + } else if (inclusionRate <= 90) { + int green = (inclusionRate - 60) * 5; // Increase green + labelColor = QColor(100, green, 100); // Green shades + } else { + labelColor = QColor(100, 200, 100); // Dark Green + } + + label->setStyleSheet(QString("color: %1").arg(labelColor.name())); + + layout->addWidget(cardPictureWidget); + layout->addWidget(label); + + QWidget *currentParent = parentWidget(); + TabEdhRecMain *parentTab = nullptr; + + while (currentParent) { + if ((parentTab = qobject_cast(currentParent))) { + break; + } + currentParent = currentParent->parentWidget(); + } + + if (parentTab) { + connect(cardPictureWidget, &CardInfoPictureWidget::cardClicked, this, + &EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation); + connect(this, &EdhrecApiResponseCardDetailsDisplayWidget::requestUrl, parentTab, + &TabEdhRecMain::actNavigatePage); + } +} + +void EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation() +{ + emit requestUrl(toDisplay.url); +} diff --git a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h new file mode 100644 index 000000000..7492659e9 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h @@ -0,0 +1,28 @@ +#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H +#define EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H + +#include "../../../../../ui/widgets/cards/card_info_picture_widget.h" +#include "../../api_response/cards/edhrec_api_response_card_details.h" + +#include +#include +#include + +class EdhrecApiResponseCardDetailsDisplayWidget : public QWidget +{ + Q_OBJECT +public: + explicit EdhrecApiResponseCardDetailsDisplayWidget(QWidget *parent, const EdhrecApiResponseCardDetails &_toDisplay); +public slots: + void actRequestPageNavigation(); +signals: + void requestUrl(QString url); + +private: + EdhrecApiResponseCardDetails toDisplay; + QVBoxLayout *layout; + CardInfoPictureWidget *cardPictureWidget; + QLabel *label; +}; + +#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.cpp new file mode 100644 index 000000000..068003fac --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.cpp @@ -0,0 +1,33 @@ +#include "edhrec_api_response_card_list_display_widget.h" + +#include "../../../../../ui/widgets/general/display/banner_widget.h" +#include "edhrec_api_response_card_details_display_widget.h" + +#include + +EdhrecApiResponseCardListDisplayWidget::EdhrecApiResponseCardListDisplayWidget(QWidget *parent, + EdhrecApiResponseCardList toDisplay) + : QWidget(parent) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + header = new BannerWidget(this, toDisplay.header); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + header->setBuddy(flowWidget); + + foreach (EdhrecApiResponseCardDetails card_detail, toDisplay.cardViews) { + auto widget = new EdhrecApiResponseCardDetailsDisplayWidget(flowWidget, card_detail); + flowWidget->addWidget(widget); + } + + layout->addWidget(header); + layout->addWidget(flowWidget); +} + +void EdhrecApiResponseCardListDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + qDebug() << event->size(); +} diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h similarity index 53% rename from cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.h rename to cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h index 9df667929..291150cde 100644 --- a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.h +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h @@ -1,20 +1,19 @@ #ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H #define EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H -#include "../../../ui/widgets/general/display/banner_widget.h" -#include "../../../ui/widgets/general/layout_containers/flow_widget.h" -#include "api_response/edhrec_commander_api_response_card_list.h" +#include "../../../../../ui/widgets/general/display/banner_widget.h" +#include "../../../../../ui/widgets/general/layout_containers/flow_widget.h" +#include "../../api_response/cards/edhrec_api_response_card_list.h" #include #include #include -class EdhrecCommanderApiResponseCardListDisplayWidget : public QWidget +class EdhrecApiResponseCardListDisplayWidget : public QWidget { Q_OBJECT public: - explicit EdhrecCommanderApiResponseCardListDisplayWidget(QWidget *parent, - EdhrecCommanderApiResponseCardList toDisplay); + explicit EdhrecApiResponseCardListDisplayWidget(QWidget *parent, EdhrecApiResponseCardList toDisplay); void resizeEvent(QResizeEvent *event) override; [[nodiscard]] QString getBannerText() const { diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp similarity index 61% rename from cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp rename to cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index 58b77df73..e04325a35 100644 --- a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp @@ -1,13 +1,13 @@ -#include "edhrec_commander_api_response_commander_details_display_widget.h" +#include "edhrec_api_response_commander_details_display_widget.h" -#include "../../../../game/cards/card_database_manager.h" -#include "../../../ui/widgets/cards/card_info_picture_widget.h" - -#include +#include "../../../../../../game/cards/card_database_manager.h" +#include "../../../../../ui/widgets/cards/card_info_picture_widget.h" +#include "../card_prices/edhrec_api_response_card_prices_display_widget.h" EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCommanderDetailsDisplayWidget( QWidget *parent, - const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails) + const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails, + QString baseUrl) : QWidget(parent), commanderDetails(_commanderDetails) { layout = new QVBoxLayout(this); @@ -23,9 +23,16 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom salt = new QLabel(this); salt->setAlignment(Qt::AlignCenter); + 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); + retranslateUi(); } @@ -33,4 +40,4 @@ void EdhrecCommanderResponseCommanderDetailsDisplayWidget::retranslateUi() { label->setText(commanderDetails.getLabel()); salt->setText(tr("Salt: ") + QString::number(commanderDetails.getSalt())); -} \ No newline at end of file +} diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h similarity index 59% rename from cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.h rename to cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h index 85970d503..ba6a895b1 100644 --- a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.h +++ b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h @@ -1,28 +1,34 @@ #ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H #define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H -#include "../../../ui/widgets/cards/card_info_picture_widget.h" -#include "api_response/edhrec_commander_api_response_commander_details.h" +#include "../../../../../ui/widgets/cards/card_info_picture_widget.h" +#include "../../api_response/cards/edhrec_commander_api_response_commander_details.h" +#include "../card_prices/edhrec_api_response_card_prices_display_widget.h" +#include "edhrec_commander_api_response_navigation_widget.h" #include #include #include +class EdhrecCommanderApiResponseNavigationWidget; class EdhrecCommanderResponseCommanderDetailsDisplayWidget : public QWidget { Q_OBJECT public: explicit EdhrecCommanderResponseCommanderDetailsDisplayWidget( QWidget *parent, - const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails); + const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails, + QString baseUrl); void retranslateUi(); private: - QLabel *label; - QLabel *salt; + EdhrecCommanderApiResponseCommanderDetails commanderDetails; QVBoxLayout *layout; CardInfoPictureWidget *commanderPicture; - EdhrecCommanderApiResponseCommanderDetails commanderDetails; + QLabel *label; + QLabel *salt; + EdhrecApiResponseCardPricesDisplayWidget *cardPricesDisplayWidget; + EdhrecCommanderApiResponseNavigationWidget *navigationWidget; }; #endif // EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp similarity index 81% rename from cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp rename to cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp index 3cab65bf8..9ea071546 100644 --- a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp @@ -1,9 +1,9 @@ #include "edhrec_commander_api_response_display_widget.h" -#include "../../../ui/widgets/cards/card_info_picture_widget.h" -#include "api_response/edhrec_commander_api_response.h" -#include "edhrec_commander_api_response_card_list_display_widget.h" -#include "edhrec_commander_api_response_commander_details_display_widget.h" +#include "../../../../../ui/widgets/cards/card_info_picture_widget.h" +#include "../../api_response/commander/edhrec_commander_api_response.h" +#include "../cards/edhrec_api_response_card_list_display_widget.h" +#include "edhrec_api_response_commander_details_display_widget.h" #include #include @@ -12,7 +12,8 @@ #include EdhrecCommanderApiResponseDisplayWidget::EdhrecCommanderApiResponseDisplayWidget(QWidget *parent, - EdhrecCommanderApiResponse response) + EdhrecCommanderApiResponse response, + QString baseUrl) : QWidget(parent) { layout = new QHBoxLayout(this); @@ -31,15 +32,15 @@ EdhrecCommanderApiResponseDisplayWidget::EdhrecCommanderApiResponseDisplayWidget QStringList widgetNames; // Add commander details - auto commanderPicture = - new EdhrecCommanderResponseCommanderDetailsDisplayWidget(this, response.container.getCommanderDetails()); + auto commanderPicture = new EdhrecCommanderResponseCommanderDetailsDisplayWidget( + this, response.container.getCommanderDetails(), baseUrl); cardDisplayLayout->addWidget(commanderPicture); widgetNames.append("Commander Details"); // Add card list widgets auto edhrec_commander_api_response_card_lists = response.container.getCardlists(); - for (const EdhrecCommanderApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) { - auto cardListDisplayWidget = new EdhrecCommanderApiResponseCardListDisplayWidget(this, card_list); + for (const EdhrecApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) { + auto cardListDisplayWidget = new EdhrecApiResponseCardListDisplayWidget(this, card_list); cardDisplayLayout->addWidget(cardListDisplayWidget); widgetNames.append(cardListDisplayWidget->getBannerText()); } diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.h similarity index 71% rename from cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.h rename to cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.h index d1e95e3d6..172b64e32 100644 --- a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.h +++ b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.h @@ -1,7 +1,7 @@ #ifndef EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H #define EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H -#include "api_response/edhrec_commander_api_response.h" +#include "../../api_response/commander/edhrec_commander_api_response.h" #include #include @@ -12,7 +12,9 @@ class EdhrecCommanderApiResponseDisplayWidget : public QWidget Q_OBJECT public: - explicit EdhrecCommanderApiResponseDisplayWidget(QWidget *parent, EdhrecCommanderApiResponse response); + explicit EdhrecCommanderApiResponseDisplayWidget(QWidget *parent, + EdhrecCommanderApiResponse response, + QString baseUrl); void resizeEvent(QResizeEvent *event) override; public slots: diff --git a/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp new file mode 100644 index 000000000..3f3b9f1ba --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp @@ -0,0 +1,174 @@ +#include "edhrec_commander_api_response_navigation_widget.h" + +#include "../../tab_edhrec_main.h" + +EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigationWidget( + QWidget *parent, + const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails, + QString baseUrl) + : QWidget(parent), commanderDetails(_commanderDetails) +{ + layout = new QGridLayout(this); + setLayout(layout); + + gameChangerLabel = new QLabel(this); + budgetLabel = new QLabel(this); + + comboPushButton = new QPushButton(this); + averageDeckPushButton = new QPushButton(this); + + 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, ""); + + QWidget *currentParent = parentWidget(); + TabEdhRecMain *parentTab = nullptr; + + while (currentParent) { + if ((parentTab = qobject_cast(currentParent))) { + break; + } + currentParent = currentParent->parentWidget(); + } + + if (parentTab) { + connect(comboPushButton, &QPushButton::clicked, this, + &EdhrecCommanderApiResponseNavigationWidget::actRequestComboNavigation); + connect(averageDeckPushButton, &QPushButton::clicked, this, + &EdhrecCommanderApiResponseNavigationWidget::actRequestAverageDeckNavigation); + connect(this, &EdhrecCommanderApiResponseNavigationWidget::requestUrl, parentTab, + &TabEdhRecMain::actNavigatePage); + } + + 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 (!selectedBudget.isEmpty()) { + baseUrl += "/" + selectedBudget; + } + return baseUrl; +} + +void EdhrecCommanderApiResponseNavigationWidget::actRequestCommanderNavigation() +{ + emit requestUrl(addNavigationOptionsToUrl("/commanders/" + commanderDetails.getSanitized())); +} + +void EdhrecCommanderApiResponseNavigationWidget::actRequestComboNavigation() +{ + emit requestUrl("/combos/" + commanderDetails.getSanitized()); +} + +void EdhrecCommanderApiResponseNavigationWidget::actRequestAverageDeckNavigation() +{ + emit requestUrl(addNavigationOptionsToUrl("/average-decks/" + commanderDetails.getSanitized())); +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h new file mode 100644 index 000000000..16f601b9b --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h @@ -0,0 +1,54 @@ +#ifndef EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H +#define EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H + +#include "edhrec_api_response_commander_details_display_widget.h" + +#include +#include +#include +#include + +class EdhrecCommanderApiResponseNavigationWidget : public QWidget +{ + Q_OBJECT +public: + explicit EdhrecCommanderApiResponseNavigationWidget( + QWidget *parent, + const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails, + QString baseUrl); + void retranslateUi(); + void applyOptionsFromUrl(const QString &url); + +public slots: + void actRequestCommanderNavigation(); + void actRequestComboNavigation(); + void actRequestAverageDeckNavigation(); +signals: + void requestUrl(QString url); + +private: + QGridLayout *layout; + + QLabel *gameChangerLabel; + QLabel *budgetLabel; + + QStringList gameChangerOptions = {"", "core", "upgraded", "optimized"}; + QStringList budgetOptions = {"", "budget", "expensive"}; + + QString selectedGameChanger; + QString selectedBudget; + + QMap gameChangerButtons; + QMap budgetButtons; + + QPushButton *comboPushButton; + QPushButton *averageDeckPushButton; + + EdhrecCommanderApiResponseCommanderDetails commanderDetails; + + void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); + QString addNavigationOptionsToUrl(QString baseUrl); + QString buildComboUrl() const; +}; + +#endif // EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp new file mode 100644 index 000000000..ae07c9dc8 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp @@ -0,0 +1,45 @@ +#include "edhrec_top_cards_api_response_display_widget.h" + +#include "../../api_response/top_cards/edhrec_top_cards_api_response.h" +#include "../cards/edhrec_api_response_card_list_display_widget.h" + +EdhrecTopCardsApiResponseDisplayWidget::EdhrecTopCardsApiResponseDisplayWidget(QWidget *parent, + EdhrecTopCardsApiResponse response) + : QWidget(parent) +{ + layout = new QHBoxLayout(this); + setLayout(layout); + + cardDisplayLayout = new QVBoxLayout(this); + + // Add card list widgets + auto edhrec_commander_api_response_card_lists = response.container.getCardlists(); + for (const EdhrecApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) { + auto cardListDisplayWidget = new EdhrecApiResponseCardListDisplayWidget(this, card_list); + cardDisplayLayout->addWidget(cardListDisplayWidget); + } + + // Create a QScrollArea to hold the card display widgets + scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + // Set the cardDisplayLayout inside the scroll area + auto scrollWidget = new QWidget(scrollArea); + scrollWidget->setLayout(cardDisplayLayout); + scrollArea->setWidget(scrollWidget); + + layout->addWidget(scrollArea); +} + +void EdhrecTopCardsApiResponseDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layout->invalidate(); + layout->activate(); + layout->update(); + if (scrollArea && scrollArea->widget()) { + scrollArea->widget()->resize(event->size()); + } +} diff --git a/cockatrice/src/client/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.h new file mode 100644 index 000000000..4b7a34d6a --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.h @@ -0,0 +1,25 @@ +#ifndef EDHREC_TOP_CARDS_API_RESPONSE_DISPLAY_WIDGET_H +#define EDHREC_TOP_CARDS_API_RESPONSE_DISPLAY_WIDGET_H + +#include "../../api_response/top_cards/edhrec_top_cards_api_response.h" + +#include +#include +#include +#include + +class EdhrecTopCardsApiResponseDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit EdhrecTopCardsApiResponseDisplayWidget(QWidget *parent, EdhrecTopCardsApiResponse response); + void resizeEvent(QResizeEvent *event) override; + +private: + QHBoxLayout *layout; + QVBoxLayout *cardDisplayLayout; + QScrollArea *scrollArea; +}; + +#endif // EDHREC_TOP_CARDS_API_RESPONSE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp new file mode 100644 index 000000000..13f54a979 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp @@ -0,0 +1,47 @@ +#include "edhrec_top_commanders_api_response_display_widget.h" + +#include "../../api_response/top_commanders/edhrec_top_commanders_api_response.h" +#include "../cards/edhrec_api_response_card_list_display_widget.h" + +EdhrecTopCommandersApiResponseDisplayWidget::EdhrecTopCommandersApiResponseDisplayWidget( + QWidget *parent, + EdhrecTopCommandersApiResponse response) + : QWidget(parent) +{ + layout = new QHBoxLayout(this); + setLayout(layout); + + cardDisplayLayout = new QVBoxLayout(this); + + // Add card list widgets + auto edhrec_commander_api_response_card_lists = response.container.getCardlists(); + for (const EdhrecApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) { + auto cardListDisplayWidget = new EdhrecApiResponseCardListDisplayWidget(this, card_list); + cardDisplayLayout->addWidget(cardListDisplayWidget); + } + + // Create a QScrollArea to hold the card display widgets + scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + // Set the cardDisplayLayout inside the scroll area + auto scrollWidget = new QWidget(scrollArea); + scrollWidget->setLayout(cardDisplayLayout); + scrollArea->setWidget(scrollWidget); + + layout->addWidget(scrollArea); +} + +void EdhrecTopCommandersApiResponseDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + qDebug() << event->size(); + layout->invalidate(); + layout->activate(); + layout->update(); + if (scrollArea && scrollArea->widget()) { + scrollArea->widget()->resize(event->size()); + } +} diff --git a/cockatrice/src/client/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.h new file mode 100644 index 000000000..e8774d38b --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.h @@ -0,0 +1,25 @@ +#ifndef EDHREC_TOP_COMMANDERS_API_RESPONSE_DISPLAY_WIDGET_H +#define EDHREC_TOP_COMMANDERS_API_RESPONSE_DISPLAY_WIDGET_H + +#include "../../api_response/top_commanders/edhrec_top_commanders_api_response.h" + +#include +#include +#include +#include + +class EdhrecTopCommandersApiResponseDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit EdhrecTopCommandersApiResponseDisplayWidget(QWidget *parent, EdhrecTopCommandersApiResponse response); + void resizeEvent(QResizeEvent *event) override; + +private: + QHBoxLayout *layout; + QVBoxLayout *cardDisplayLayout; + QScrollArea *scrollArea; +}; + +#endif // EDHREC_TOP_COMMANDERS_API_RESPONSE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp new file mode 100644 index 000000000..91a1ce3fa --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp @@ -0,0 +1,45 @@ +#include "edhrec_top_tags_api_response_display_widget.h" + +#include "../../api_response/top_tags/edhrec_top_tags_api_response.h" +#include "../cards/edhrec_api_response_card_list_display_widget.h" + +EdhrecTopTagsApiResponseDisplayWidget::EdhrecTopTagsApiResponseDisplayWidget(QWidget *parent, + EdhrecTopTagsApiResponse response) + : QWidget(parent) +{ + layout = new QHBoxLayout(this); + setLayout(layout); + + cardDisplayLayout = new QVBoxLayout(this); + + // Add card list widgets + auto edhrec_commander_api_response_card_lists = response.container.getCardlists(); + for (const EdhrecApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) { + auto cardListDisplayWidget = new EdhrecApiResponseCardListDisplayWidget(this, card_list); + cardDisplayLayout->addWidget(cardListDisplayWidget); + } + + // Create a QScrollArea to hold the card display widgets + scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + // Set the cardDisplayLayout inside the scroll area + auto scrollWidget = new QWidget(scrollArea); + scrollWidget->setLayout(cardDisplayLayout); + scrollArea->setWidget(scrollWidget); + + layout->addWidget(scrollArea); +} + +void EdhrecTopTagsApiResponseDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layout->invalidate(); + layout->activate(); + layout->update(); + if (scrollArea && scrollArea->widget()) { + scrollArea->widget()->resize(event->size()); + } +} diff --git a/cockatrice/src/client/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h new file mode 100644 index 000000000..d67875058 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h @@ -0,0 +1,25 @@ +#ifndef EDHREC_TOP_TAGS_API_RESPONSE_DISPLAY_WIDGET_H +#define EDHREC_TOP_TAGS_API_RESPONSE_DISPLAY_WIDGET_H + +#include "../../api_response/top_tags/edhrec_top_tags_api_response.h" + +#include +#include +#include +#include + +class EdhrecTopTagsApiResponseDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit EdhrecTopTagsApiResponseDisplayWidget(QWidget *parent, EdhrecTopTagsApiResponse response); + void resizeEvent(QResizeEvent *event) override; + +private: + QHBoxLayout *layout; + QVBoxLayout *cardDisplayLayout; + QScrollArea *scrollArea; +}; + +#endif // EDHREC_TOP_TAGS_API_RESPONSE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp deleted file mode 100644 index 87853b9aa..000000000 --- a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "edhrec_commander_api_response_card_details_display_widget.h" - -#include "../../../../game/cards/card_database_manager.h" - -EdhrecCommanderApiResponseCardDetailsDisplayWidget::EdhrecCommanderApiResponseCardDetailsDisplayWidget( - QWidget *parent, - const EdhrecCommanderApiResponseCardDetails &_toDisplay) - : QWidget(parent), toDisplay(_toDisplay) -{ - layout = new QVBoxLayout(this); - setLayout(layout); - - cardPictureWidget = new CardInfoPictureWidget(this); - cardPictureWidget->setCard(CardDatabaseManager::getInstance()->getCard(toDisplay.name)); - - label = new QLabel(this); - label->setText(toDisplay.name + "\n" + toDisplay.label); - label->setAlignment(Qt::AlignHCenter); - - // Set label color based on inclusion rate - int inclusionRate = (toDisplay.numDecks * 100) / toDisplay.potentialDecks; - - QColor labelColor; - if (inclusionRate <= 30) { - labelColor = QColor(255, 0, 0); // Red - } else if (inclusionRate <= 60) { - int red = 255 - ((inclusionRate - 30) * 2); - int green = (inclusionRate - 30) * 4; // Adjust green to make the transition smoother - labelColor = QColor(red, green, 0); // purple-ish - } else if (inclusionRate <= 90) { - int green = (inclusionRate - 60) * 5; // Increase green - labelColor = QColor(100, green, 100); // Green shades - } else { - labelColor = QColor(100, 200, 100); // Dark Green - } - - label->setStyleSheet(QString("color: %1").arg(labelColor.name())); - - layout->addWidget(cardPictureWidget); - layout->addWidget(label); -} diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.h deleted file mode 100644 index 1cd76989c..000000000 --- a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H -#define EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H - -#include "../../../ui/widgets/cards/card_info_picture_widget.h" -#include "api_response/edhrec_commander_api_response_card_details.h" - -#include -#include -#include - -class EdhrecCommanderApiResponseCardDetailsDisplayWidget : public QWidget -{ - Q_OBJECT -public: - explicit EdhrecCommanderApiResponseCardDetailsDisplayWidget( - QWidget *parent, - const EdhrecCommanderApiResponseCardDetails &_toDisplay); - -private: - EdhrecCommanderApiResponseCardDetails toDisplay; - QVBoxLayout *layout; - CardInfoPictureWidget *cardPictureWidget; - QLabel *label; -}; - -#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp deleted file mode 100644 index 0d1f1e8f3..000000000 --- a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "edhrec_commander_api_response_card_list_display_widget.h" - -#include "../../../ui/widgets/general/display/banner_widget.h" -#include "edhrec_commander_api_response_card_details_display_widget.h" - -#include - -EdhrecCommanderApiResponseCardListDisplayWidget::EdhrecCommanderApiResponseCardListDisplayWidget( - QWidget *parent, - EdhrecCommanderApiResponseCardList toDisplay) - : QWidget(parent) -{ - layout = new QVBoxLayout(this); - setLayout(layout); - - header = new BannerWidget(this, toDisplay.header); - - flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); - header->setBuddy(flowWidget); - - foreach (EdhrecCommanderApiResponseCardDetails card_detail, toDisplay.cardViews) { - auto widget = new EdhrecCommanderApiResponseCardDetailsDisplayWidget(flowWidget, card_detail); - flowWidget->addWidget(widget); - } - - layout->addWidget(header); - layout->addWidget(flowWidget); -} - -void EdhrecCommanderApiResponseCardListDisplayWidget::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); - qDebug() << event->size(); -} diff --git a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp index 18662aaab..a2755b9e1 100644 --- a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp @@ -1,7 +1,7 @@ #include "tab_edhrec.h" -#include "api_response/edhrec_commander_api_response.h" -#include "edhrec_commander_api_response_display_widget.h" +#include "api_response/commander/edhrec_commander_api_response.h" +#include "display/commander/edhrec_commander_api_response_display_widget.h" #include #include @@ -21,7 +21,7 @@ TabEdhRec::TabEdhRec(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) #endif networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); - connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); + connect(networkManager, &QNetworkAccessManager::finished, this, &TabEdhRec::processApiJson); } void TabEdhRec::retranslateUi() @@ -76,7 +76,7 @@ void TabEdhRec::processApiJson(QNetworkReply *reply) EdhrecCommanderApiResponse deckData; deckData.fromJson(jsonObj); - displayWidget = new EdhrecCommanderApiResponseDisplayWidget(this, deckData); + displayWidget = new EdhrecCommanderApiResponseDisplayWidget(this, deckData, reply->url().toString()); // flowWidget->addWidget(displayWidget); setCentralWidget(displayWidget); diff --git a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h index c15f22b26..ff299fadc 100644 --- a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h +++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h @@ -1,12 +1,11 @@ #ifndef TAB_EDHREC_H #define TAB_EDHREC_H -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include "../../../ui/widgets/general/layout_containers/flow_widget.h" #include "../../tab.h" -#include "edhrec_commander_api_response_display_widget.h" +#include "display/commander/edhrec_commander_api_response_display_widget.h" -#include #include class TabEdhRec : public Tab diff --git a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp new file mode 100644 index 000000000..ac30cef4d --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp @@ -0,0 +1,380 @@ +#include "tab_edhrec_main.h" + +#include "../../../../game/cards/card_completer_proxy_model.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../game/cards/card_search_model.h" +#include "../../tab_supervisor.h" +#include "api_response/average_deck/edhrec_average_deck_api_response.h" +#include "api_response/commander/edhrec_commander_api_response.h" +#include "api_response/top_cards/edhrec_top_cards_api_response.h" +#include "api_response/top_commanders/edhrec_top_commanders_api_response.h" +#include "api_response/top_tags/edhrec_top_tags_api_response.h" +#include "display/commander/edhrec_commander_api_response_display_widget.h" +#include "display/top_cards/edhrec_top_cards_api_response_display_widget.h" +#include "display/top_commander/edhrec_top_commanders_api_response_display_widget.h" +#include "display/top_tags/edhrec_top_tags_api_response_display_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool canBeCommander(const CardInfoPtr &cardInfo) +{ + return ((cardInfo->getCardType().contains("Legendary", Qt::CaseInsensitive) && + cardInfo->getCardType().contains("Creature", Qt::CaseInsensitive))) || + cardInfo->getText().contains("can be your commander", Qt::CaseInsensitive); +} + +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 *))); + + container = new QWidget(this); + mainLayout = new QVBoxLayout(container); + container->setLayout(mainLayout); + + navigationContainer = new QWidget(container); + navigationContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + navigationLayout = new QHBoxLayout(navigationContainer); + navigationLayout->setSpacing(5); + navigationContainer->setLayout(navigationLayout); + + cardsPushButton = new QPushButton(navigationContainer); + connect(cardsPushButton, &QPushButton::clicked, this, &TabEdhRecMain::getTopCards); + topCommandersPushButton = new QPushButton(navigationContainer); + connect(topCommandersPushButton, &QPushButton::clicked, this, &TabEdhRecMain::getTopCommanders); + tagsPushButton = new QPushButton(navigationContainer); + connect(tagsPushButton, &QPushButton::clicked, this, &TabEdhRecMain::getTopTags); + + searchBar = new QLineEdit(this); + auto cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this); + auto displayModel = new CardDatabaseDisplayModel(this); + displayModel->setSourceModel(cardDatabaseModel); + CardSearchModel *searchModel = new CardSearchModel(displayModel, this); + + CardCompleterProxyModel *proxyModel = new CardCompleterProxyModel(this); + proxyModel->setSourceModel(searchModel); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setFilterRole(Qt::DisplayRole); + + QCompleter *completer = new QCompleter(proxyModel, this); + completer->setCompletionRole(Qt::DisplayRole); + completer->setCompletionMode(QCompleter::PopupCompletion); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setFilterMode(Qt::MatchContains); + completer->setMaxVisibleItems(10); + searchBar->setCompleter(completer); + + // Update suggestions dynamically + connect(searchBar, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults); + connect(searchBar, &QLineEdit::textChanged, this, [=](const QString &text) { + // Ensure substring matching + QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; + proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + + if (!text.isEmpty()) { + completer->complete(); // Force the dropdown to appear + } + }); + + searchPushButton = new QPushButton(navigationContainer); + connect(searchPushButton, &QPushButton::clicked, this, [=, this]() { doSearch(); }); + + navigationLayout->addWidget(cardsPushButton); + navigationLayout->addWidget(topCommandersPushButton); + navigationLayout->addWidget(tagsPushButton); + navigationLayout->addWidget(searchBar); + navigationLayout->addWidget(searchPushButton); + + currentPageDisplay = new QWidget(container); + currentPageLayout = new QVBoxLayout(currentPageDisplay); + currentPageDisplay->setLayout(currentPageLayout); + + 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); + + TabEdhRecMain::retranslateUi(); + + getTopCards(); +} + +void TabEdhRecMain::retranslateUi() +{ + cardsPushButton->setText(tr("&Cards")); + topCommandersPushButton->setText(tr("Top Commanders")); + tagsPushButton->setText(tr("Tags")); + searchBar->setPlaceholderText(tr("Search for a card ...")); + searchPushButton->setText(tr("Search")); +} + +void TabEdhRecMain::doSearch() +{ + CardInfoPtr searchedCard = CardDatabaseManager::getInstance()->getCard(searchBar->text()); + if (!searchedCard) { + return; + } + + setCard(searchedCard, canBeCommander(searchedCard)); +} + +void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander) +{ + cardToQuery = _cardToQuery; + + if (!cardToQuery) { + qDebug() << "Invalid card information provided."; + return; + } + + QString cardName = cardToQuery->getName(); + QString formattedName = cardName.toLower().replace(" ", "-").remove(QRegularExpression("[^a-z0-9\\-]")); + + QString url; + if (isCommander) { + url = QString("https://json.edhrec.com/pages/commanders/%1.json").arg(formattedName); + } else { + url = QString("https://json.edhrec.com/pages/cards/%1.json").arg(formattedName); + } + + QNetworkRequest request{QUrl(url)}; + + networkManager->get(request); +} + +void TabEdhRecMain::actNavigatePage(QString url) +{ + QNetworkRequest request{QUrl("https://json.edhrec.com/pages" + url + ".json")}; + + networkManager->get(request); +} + +void TabEdhRecMain::getTopCards() +{ + QNetworkRequest request{QUrl("https://json.edhrec.com/pages/top/year.json")}; + + networkManager->get(request); +} + +void TabEdhRecMain::getTopCommanders() +{ + QNetworkRequest request{QUrl("https://json.edhrec.com/pages/commanders/year.json")}; + + networkManager->get(request); +} + +void TabEdhRecMain::getTopTags() +{ + QNetworkRequest request{QUrl("https://json.edhrec.com/pages/tags.json")}; + + networkManager->get(request); +} + +void TabEdhRecMain::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://json.edhrec.com/pages/commanders/year.json")) { + processTopCommandersResponse(jsonObj); + } else if (responseUrl.startsWith("https://json.edhrec.com/pages/commanders/")) { + qInfo() << "Received top kek"; + processCommanderResponse(jsonObj, responseUrl); + } else if (responseUrl.startsWith("https://json.edhrec.com/pages/cards/")) { + processCommanderResponse(jsonObj); + } else if (responseUrl.startsWith("https://json.edhrec.com/pages/tags/")) { + processCommanderResponse(jsonObj); + } else if (responseUrl.startsWith("https://json.edhrec.com/pages/tags.json")) { + processTopTagsResponse(jsonObj); + } else if (responseUrl.startsWith("https://json.edhrec.com/pages/top/year.json")) { + processTopCardsResponse(jsonObj); + } else if (responseUrl.startsWith("https://json.edhrec.com/pages/combos/")) { + qInfo() << "Received combos"; + processCommanderResponse(jsonObj); + } else if (responseUrl.startsWith("https://json.edhrec.com/pages/average-decks/")) { + processAverageDeckResponse(jsonObj); + } else { + prettyPrintJson(jsonObj, 4); + } + + reply->deleteLater(); +} + +void TabEdhRecMain::processTopCardsResponse(QJsonObject reply) +{ + EdhrecTopCardsApiResponse 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 EdhrecTopCardsApiResponseDisplayWidget(currentPageDisplay, deckData); + 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 TabEdhRecMain::processTopTagsResponse(QJsonObject reply) +{ + EdhrecTopTagsApiResponse 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 EdhrecTopTagsApiResponseDisplayWidget(currentPageDisplay, deckData); + 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 TabEdhRecMain::processTopCommandersResponse(QJsonObject reply) +{ + EdhrecTopCommandersApiResponse 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 EdhrecTopCommandersApiResponseDisplayWidget(currentPageDisplay, deckData); + 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 TabEdhRecMain::processCommanderResponse(QJsonObject reply, QString responseUrl) +{ + EdhrecCommanderApiResponse 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 EdhrecCommanderApiResponseDisplayWidget(currentPageDisplay, deckData, responseUrl); + 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 TabEdhRecMain::processAverageDeckResponse(QJsonObject reply) +{ + EdhrecAverageDeckApiResponse deckData; + deckData.fromJson(reply); + tabSupervisor->addVisualDeckEditorTab(deckData.deck.deckLoader); +} + +void TabEdhRecMain::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/client/tabs/api/edhrec/tab_edhrec_main.h b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.h new file mode 100644 index 000000000..1c6c67e5c --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.h @@ -0,0 +1,60 @@ +#ifndef TAB_EDHREC_MAIN_H +#define TAB_EDHREC_MAIN_H + +#include "../../../../game/cards/card_database.h" +#include "../../../ui/widgets/general/layout_containers/flow_widget.h" +#include "../../tab.h" +#include "display/commander/edhrec_commander_api_response_display_widget.h" + +#include +#include +#include +#include + +class TabEdhRecMain : public Tab +{ + Q_OBJECT +public: + explicit TabEdhRecMain(TabSupervisor *_tabSupervisor); + + void retranslateUi() override; + void doSearch(); + QString getTabText() const override + { + auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName(); + return tr("EDHREC: ") + cardName; + } + + QNetworkAccessManager *networkManager; + +public slots: + void processApiJson(QNetworkReply *reply); + void processCommanderResponse(QJsonObject reply, QString responseUrl = ""); + void processTopCardsResponse(QJsonObject reply); + void processTopTagsResponse(QJsonObject reply); + void processTopCommandersResponse(QJsonObject reply); + void processAverageDeckResponse(QJsonObject reply); + void prettyPrintJson(const QJsonValue &value, int indentLevel); + void setCard(CardInfoPtr _cardToQuery, bool isCommander = false); + void actNavigatePage(QString url); + void getTopCards(); + void getTopCommanders(); + void getTopTags(); + +private: + QWidget *container; + QWidget *navigationContainer; + QWidget *currentPageDisplay; + QVBoxLayout *mainLayout; + QHBoxLayout *navigationLayout; + QVBoxLayout *currentPageLayout; + QPushButton *cardsPushButton; + QPushButton *topCommandersPushButton; + QPushButton *tagsPushButton; + QLineEdit *searchBar; + QPushButton *searchPushButton; + CardInfoPtr cardToQuery; + EdhrecCommanderApiResponseDisplayWidget *displayWidget; +}; + +#endif // TAB_EDHREC_MAIN_H diff --git a/cockatrice/src/client/tabs/tab_deck_editor.cpp b/cockatrice/src/client/tabs/tab_deck_editor.cpp index dbe38a215..1179c5a92 100644 --- a/cockatrice/src/client/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/tab_deck_editor.cpp @@ -55,7 +55,7 @@ TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor) : AbstractTabDeckEdi void TabDeckEditor::createMenus() { - deckMenu = new DeckEditorMenu(this, this); + deckMenu = new DeckEditorMenu(this); addTabMenu(deckMenu); viewMenu = new QMenu(this); @@ -67,36 +67,36 @@ void TabDeckEditor::createMenus() aCardInfoDockVisible = cardInfoDockMenu->addAction(QString()); aCardInfoDockVisible->setCheckable(true); - connect(aCardInfoDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + connect(aCardInfoDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); aCardInfoDockFloating = cardInfoDockMenu->addAction(QString()); aCardInfoDockFloating->setCheckable(true); - connect(aCardInfoDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + connect(aCardInfoDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); aDeckDockVisible = deckDockMenu->addAction(QString()); aDeckDockVisible->setCheckable(true); - connect(aDeckDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + connect(aDeckDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); aDeckDockFloating = deckDockMenu->addAction(QString()); aDeckDockFloating->setCheckable(true); - connect(aDeckDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + connect(aDeckDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); aFilterDockVisible = filterDockMenu->addAction(QString()); aFilterDockVisible->setCheckable(true); - connect(aFilterDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + connect(aFilterDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); aFilterDockFloating = filterDockMenu->addAction(QString()); aFilterDockFloating->setCheckable(true); - connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + connect(aFilterDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString()); aPrintingSelectorDockVisible->setCheckable(true); - connect(aPrintingSelectorDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + connect(aPrintingSelectorDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString()); aPrintingSelectorDockFloating->setCheckable(true); - connect(aPrintingSelectorDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + connect(aPrintingSelectorDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); viewMenu->addSeparator(); aResetLayout = viewMenu->addAction(QString()); - connect(aResetLayout, SIGNAL(triggered()), this, SLOT(restartLayout())); + connect(aResetLayout, &QAction::triggered, this, &TabDeckEditor::restartLayout); viewMenu->addAction(aResetLayout); deckMenu->setSaveStatus(false); @@ -200,7 +200,7 @@ void TabDeckEditor::loadLayout() databaseDisplayDockWidget->setMinimumSize(100, 100); databaseDisplayDockWidget->setMaximumSize(1400, 5000); - QTimer::singleShot(100, this, SLOT(freeDocksSize())); + QTimer::singleShot(100, this, &TabDeckEditor::freeDocksSize); } void TabDeckEditor::restartLayout() @@ -236,7 +236,7 @@ void TabDeckEditor::restartLayout() splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal); splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Vertical); - QTimer::singleShot(100, this, SLOT(freeDocksSize())); + QTimer::singleShot(100, this, &TabDeckEditor::freeDocksSize); } void TabDeckEditor::freeDocksSize() diff --git a/cockatrice/src/client/tabs/tab_deck_editor.h b/cockatrice/src/client/tabs/tab_deck_editor.h index d9b10de7d..710689b8e 100644 --- a/cockatrice/src/client/tabs/tab_deck_editor.h +++ b/cockatrice/src/client/tabs/tab_deck_editor.h @@ -1,7 +1,7 @@ #ifndef WINDOW_DECKEDITOR_H #define WINDOW_DECKEDITOR_H -#include "../../game/cards/card_database.h" +#include "../../game/cards/card_info.h" #include "../game_logic/key_signals.h" #include "../ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h" #include "abstract_tab_deck_editor.h" diff --git a/cockatrice/src/client/tabs/tab_deck_storage.cpp b/cockatrice/src/client/tabs/tab_deck_storage.cpp index f696f607c..426431c68 100644 --- a/cockatrice/src/client/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/client/tabs/tab_deck_storage.cpp @@ -105,19 +105,19 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, // Left side actions aOpenLocalDeck = new QAction(this); aOpenLocalDeck->setIcon(QPixmap("theme:icons/pencil")); - connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck())); + connect(aOpenLocalDeck, &QAction::triggered, this, &TabDeckStorage::actOpenLocalDeck); aRenameLocal = new QAction(this); aRenameLocal->setIcon(QPixmap("theme:icons/rename")); connect(aRenameLocal, &QAction::triggered, this, &TabDeckStorage::actRenameLocal); aUpload = new QAction(this); aUpload->setIcon(QPixmap("theme:icons/arrow_right_green")); - connect(aUpload, SIGNAL(triggered()), this, SLOT(actUpload())); + connect(aUpload, &QAction::triggered, this, &TabDeckStorage::actUpload); aNewLocalFolder = new QAction(this); aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder)); connect(aNewLocalFolder, &QAction::triggered, this, &TabDeckStorage::actNewLocalFolder); aDeleteLocalDeck = new QAction(this); aDeleteLocalDeck->setIcon(QPixmap("theme:icons/remove_row")); - connect(aDeleteLocalDeck, SIGNAL(triggered()), this, SLOT(actDeleteLocalDeck())); + connect(aDeleteLocalDeck, &QAction::triggered, this, &TabDeckStorage::actDeleteLocalDeck); aOpenDecksFolder = new QAction(this); aOpenDecksFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon)); @@ -126,16 +126,16 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, // Right side actions aOpenRemoteDeck = new QAction(this); aOpenRemoteDeck->setIcon(QPixmap("theme:icons/pencil")); - connect(aOpenRemoteDeck, SIGNAL(triggered()), this, SLOT(actOpenRemoteDeck())); + connect(aOpenRemoteDeck, &QAction::triggered, this, &TabDeckStorage::actOpenRemoteDeck); aDownload = new QAction(this); aDownload->setIcon(QPixmap("theme:icons/arrow_left_green")); - connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload())); + connect(aDownload, &QAction::triggered, this, &TabDeckStorage::actDownload); aNewFolder = new QAction(this); aNewFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder)); - connect(aNewFolder, SIGNAL(triggered()), this, SLOT(actNewFolder())); + connect(aNewFolder, &QAction::triggered, this, &TabDeckStorage::actNewFolder); aDeleteRemoteDeck = new QAction(this); aDeleteRemoteDeck->setIcon(QPixmap("theme:icons/remove_row")); - connect(aDeleteRemoteDeck, SIGNAL(triggered()), this, SLOT(actDeleteRemoteDeck())); + connect(aDeleteRemoteDeck, &QAction::triggered, this, &TabDeckStorage::actDeleteRemoteDeck); // Add actions to toolbars leftToolBar->addAction(aOpenLocalDeck); @@ -339,8 +339,7 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa cmd.set_deck_list(deckString.toStdString()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(uploadFinished(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &TabDeckStorage::uploadFinished); client->sendCommand(pend); } @@ -421,8 +420,7 @@ void TabDeckStorage::actOpenRemoteDeck() cmd.set_deck_id(node->getId()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(openRemoteDeckFinished(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &TabDeckStorage::openRemoteDeckFinished); client->sendCommand(pend); } } @@ -479,8 +477,7 @@ void TabDeckStorage::downloadNodeAtIndex(const QModelIndex &curLeft, const QMode PendingCommand *pend = client->prepareSessionCommand(cmd); pend->setExtraData(filePath); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(downloadFinished(Response, CommandContainer, QVariant))); + connect(pend, &PendingCommand::finished, this, &TabDeckStorage::downloadFinished); client->sendCommand(pend); } // node at index is invalid @@ -522,8 +519,7 @@ void TabDeckStorage::actNewFolder() cmd.set_dir_name(folder); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(newFolderFinished(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &TabDeckStorage::newFolderFinished); client->sendCommand(pend); } @@ -573,15 +569,13 @@ void TabDeckStorage::deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *curR Command_DeckDelDir cmd; cmd.set_path(targetPath.toStdString()); pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(deleteFolderFinished(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &TabDeckStorage::deleteFolderFinished); } else { const auto *deckNode = dynamic_cast(curRight); Command_DeckDel cmd; cmd.set_deck_id(deckNode->getId()); pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(deleteDeckFinished(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &TabDeckStorage::deleteDeckFinished); } client->sendCommand(pend); diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index 8c018f644..e01153d00 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -115,11 +115,12 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) createReplayMenuItems(); createViewMenuItems(); retranslateUi(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &TabGame::refreshShortcuts); refreshShortcuts(); messageLog->logReplayStarted(gameInfo.game_id()); - QTimer::singleShot(0, this, SLOT(loadLayout())); + QTimer::singleShot(0, this, &TabGame::loadLayout); } TabGame::TabGame(TabSupervisor *_tabSupervisor, @@ -156,14 +157,15 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, createMenuItems(); createViewMenuItems(); retranslateUi(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &TabGame::refreshShortcuts); refreshShortcuts(); // append game to rooms game list for others to see for (int i = gameInfo.game_types_size() - 1; i >= 0; i--) gameTypes.append(roomGameTypes.find(gameInfo.game_types(i)).value()); - QTimer::singleShot(0, this, SLOT(loadLayout())); + QTimer::singleShot(0, this, &TabGame::loadLayout); } void TabGame::addMentionTag(const QString &value) @@ -628,7 +630,7 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) } scene->addPlayer(newPlayer); - connect(newPlayer, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *))); + connect(newPlayer, &Player::newCardAdded, this, &TabGame::newCardAdded); messageLog->connectToPlayer(newPlayer); if (local && !spectator) { @@ -636,7 +638,7 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) newPlayer->setShortcutsActive(); auto *deckView = new DeckViewContainer(playerId, this); - connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *))); + connect(deckView, &DeckViewContainer::newCardAdded, this, &TabGame::newCardAdded); deckViewContainers.insert(playerId, deckView); deckViewContainerLayout->addWidget(deckView); @@ -784,8 +786,7 @@ void TabGame::sendGameCommand(PendingCommand *pend, int playerId) if (!client) return; - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(commandFinished(const Response &))); + connect(pend, &PendingCommand::finished, this, &TabGame::commandFinished); client->sendCommand(pend); } @@ -796,8 +797,7 @@ void TabGame::sendGameCommand(const google::protobuf::Message &command, int play return; PendingCommand *pend = prepareGameCommand(command); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(commandFinished(const Response &))); + connect(pend, &PendingCommand::finished, this, &TabGame::commandFinished); client->sendCommand(pend); } @@ -1198,10 +1198,11 @@ void TabGame::eventSetActivePhase(const Event_SetActivePhase &event, void TabGame::newCardAdded(AbstractCardItem *card) { - connect(card, SIGNAL(hovered(AbstractCardItem *)), cardInfoFrameWidget, SLOT(setCard(AbstractCardItem *))); + connect(card, &AbstractCardItem::hovered, cardInfoFrameWidget, + qOverload(&CardInfoFrameWidget::setCard)); connect(card, &AbstractCardItem::showCardInfoPopup, this, &TabGame::showCardInfoPopup); connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); - connect(card, SIGNAL(cardShiftClicked(QString)), this, SLOT(linkCardToChat(QString))); + connect(card, &AbstractCardItem::cardShiftClicked, this, &TabGame::linkCardToChat); } CardItem *TabGame::getCard(int playerId, const QString &zoneName, int cardId) const @@ -1289,34 +1290,34 @@ void TabGame::updateCardMenu(AbstractCardItem *card) void TabGame::createMenuItems() { aNextPhase = new QAction(this); - connect(aNextPhase, SIGNAL(triggered()), this, SLOT(actNextPhase())); + connect(aNextPhase, &QAction::triggered, this, &TabGame::actNextPhase); aNextPhaseAction = new QAction(this); - connect(aNextPhaseAction, SIGNAL(triggered()), this, SLOT(actNextPhaseAction())); + connect(aNextPhaseAction, &QAction::triggered, this, &TabGame::actNextPhaseAction); aNextTurn = new QAction(this); - connect(aNextTurn, SIGNAL(triggered()), this, SLOT(actNextTurn())); + connect(aNextTurn, &QAction::triggered, this, &TabGame::actNextTurn); aReverseTurn = new QAction(this); - connect(aReverseTurn, SIGNAL(triggered()), this, SLOT(actReverseTurn())); + connect(aReverseTurn, &QAction::triggered, this, &TabGame::actReverseTurn); aRemoveLocalArrows = new QAction(this); - connect(aRemoveLocalArrows, SIGNAL(triggered()), this, SLOT(actRemoveLocalArrows())); + connect(aRemoveLocalArrows, &QAction::triggered, this, &TabGame::actRemoveLocalArrows); aRotateViewCW = new QAction(this); - connect(aRotateViewCW, SIGNAL(triggered()), this, SLOT(actRotateViewCW())); + connect(aRotateViewCW, &QAction::triggered, this, &TabGame::actRotateViewCW); aRotateViewCCW = new QAction(this); - connect(aRotateViewCCW, SIGNAL(triggered()), this, SLOT(actRotateViewCCW())); + connect(aRotateViewCCW, &QAction::triggered, this, &TabGame::actRotateViewCCW); aGameInfo = new QAction(this); - connect(aGameInfo, SIGNAL(triggered()), this, SLOT(actGameInfo())); + connect(aGameInfo, &QAction::triggered, this, &TabGame::actGameInfo); aConcede = new QAction(this); - connect(aConcede, SIGNAL(triggered()), this, SLOT(actConcede())); + connect(aConcede, &QAction::triggered, this, &TabGame::actConcede); aLeaveGame = new QAction(this); connect(aLeaveGame, &QAction::triggered, this, [this] { closeRequest(); }); aFocusChat = new QAction(this); - connect(aFocusChat, SIGNAL(triggered()), sayEdit, SLOT(setFocus())); + connect(aFocusChat, &QAction::triggered, sayEdit, qOverload<>(&LineEditCompleter::setFocus)); aCloseReplay = nullptr; phasesMenu = new TearOffMenu(this); for (int i = 0; i < phasesToolbar->phaseCount(); ++i) { QAction *temp = new QAction(QString(), this); - connect(temp, SIGNAL(triggered()), this, SLOT(actPhaseAction())); + connect(temp, &QAction::triggered, this, &TabGame::actPhaseAction); phasesMenu->addAction(temp); phaseActions.append(temp); } @@ -1375,40 +1376,40 @@ void TabGame::createViewMenuItems() aCardInfoDockVisible = cardInfoDockMenu->addAction(QString()); aCardInfoDockVisible->setCheckable(true); - connect(aCardInfoDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + connect(aCardInfoDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered); aCardInfoDockFloating = cardInfoDockMenu->addAction(QString()); aCardInfoDockFloating->setCheckable(true); - connect(aCardInfoDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + connect(aCardInfoDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered); aMessageLayoutDockVisible = messageLayoutDockMenu->addAction(QString()); aMessageLayoutDockVisible->setCheckable(true); - connect(aMessageLayoutDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + connect(aMessageLayoutDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered); aMessageLayoutDockFloating = messageLayoutDockMenu->addAction(QString()); aMessageLayoutDockFloating->setCheckable(true); - connect(aMessageLayoutDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + connect(aMessageLayoutDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered); aPlayerListDockVisible = playerListDockMenu->addAction(QString()); aPlayerListDockVisible->setCheckable(true); - connect(aPlayerListDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + connect(aPlayerListDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered); aPlayerListDockFloating = playerListDockMenu->addAction(QString()); aPlayerListDockFloating->setCheckable(true); - connect(aPlayerListDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + connect(aPlayerListDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered); if (replayDock) { replayDockMenu = viewMenu->addMenu(QString()); aReplayDockVisible = replayDockMenu->addAction(QString()); aReplayDockVisible->setCheckable(true); - connect(aReplayDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + connect(aReplayDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered); aReplayDockFloating = replayDockMenu->addAction(QString()); aReplayDockFloating->setCheckable(true); - connect(aReplayDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + connect(aReplayDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered); } viewMenu->addSeparator(); aResetLayout = viewMenu->addAction(QString()); - connect(aResetLayout, SIGNAL(triggered()), this, SLOT(actResetLayout())); + connect(aResetLayout, &QAction::triggered, this, &TabGame::actResetLayout); viewMenu->addAction(aResetLayout); addTabMenu(viewMenu); @@ -1459,7 +1460,7 @@ void TabGame::loadLayout() aReplayDockFloating->setChecked(replayDock->isFloating()); } - QTimer::singleShot(100, this, SLOT(freeDocksSize())); + QTimer::singleShot(100, this, &TabGame::freeDocksSize); } void TabGame::freeDocksSize() @@ -1525,15 +1526,15 @@ void TabGame::actResetLayout() playerListDock->setMaximumSize(250, 50); } - QTimer::singleShot(100, this, SLOT(freeDocksSize())); + QTimer::singleShot(100, this, &TabGame::freeDocksSize); } void TabGame::createPlayAreaWidget(bool bReplay) { phasesToolbar = new PhasesToolbar; if (!bReplay) - connect(phasesToolbar, SIGNAL(sendGameCommand(const ::google::protobuf::Message &, int)), this, - SLOT(sendGameCommand(const ::google::protobuf::Message &, int))); + connect(phasesToolbar, &PhasesToolbar::sendGameCommand, this, + qOverload(&TabGame::sendGameCommand)); scene = new GameScene(phasesToolbar, this); gameView = new GameView(scene); @@ -1551,9 +1552,8 @@ void TabGame::createReplayDock() // timeline widget timelineWidget = new ReplayTimelineWidget; timelineWidget->setTimeline(replayTimeline); - connect(timelineWidget, SIGNAL(processNextEvent(Player::EventProcessingOptions)), this, - SLOT(replayNextEvent(Player::EventProcessingOptions))); - connect(timelineWidget, SIGNAL(replayFinished()), this, SLOT(replayFinished())); + connect(timelineWidget, &ReplayTimelineWidget::processNextEvent, this, &TabGame::replayNextEvent); + connect(timelineWidget, &ReplayTimelineWidget::replayFinished, this, &TabGame::replayFinished); connect(timelineWidget, &ReplayTimelineWidget::rewound, this, &TabGame::replayRewind); // timeline skip shortcuts @@ -1585,13 +1585,13 @@ void TabGame::createReplayDock() playButtonIcon.addPixmap(QPixmap("theme:replay/pause"), QIcon::Normal, QIcon::On); replayPlayButton->setIcon(playButtonIcon); replayPlayButton->setCheckable(true); - connect(replayPlayButton, SIGNAL(toggled(bool)), this, SLOT(replayPlayButtonToggled(bool))); + connect(replayPlayButton, &QToolButton::toggled, this, &TabGame::replayPlayButtonToggled); replayFastForwardButton = new QToolButton; replayFastForwardButton->setIconSize(QSize(32, 32)); replayFastForwardButton->setIcon(QPixmap("theme:replay/fastforward")); replayFastForwardButton->setCheckable(true); - connect(replayFastForwardButton, SIGNAL(toggled(bool)), this, SLOT(replayFastForwardButtonToggled(bool))); + connect(replayFastForwardButton, &QToolButton::toggled, this, &TabGame::replayFastForwardButtonToggled); // putting everything together replayControlLayout = new QHBoxLayout; @@ -1611,7 +1611,7 @@ void TabGame::createReplayDock() replayDock->setFloating(false); replayDock->installEventFilter(this); - connect(replayDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); + connect(replayDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged); } void TabGame::createDeckViewContainerWidget(bool bReplay) @@ -1652,7 +1652,7 @@ void TabGame::createCardInfoDock(bool bReplay) cardInfoDock->setFloating(false); cardInfoDock->installEventFilter(this); - connect(cardInfoDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); + connect(cardInfoDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged); } void TabGame::createPlayerListDock(bool bReplay) @@ -1674,26 +1674,28 @@ void TabGame::createPlayerListDock(bool bReplay) playerListDock->setFloating(false); playerListDock->installEventFilter(this); - connect(playerListDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); + connect(playerListDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged); } void TabGame::createMessageDock(bool bReplay) { messageLog = new MessageLogWidget(tabSupervisor, this); - connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfoFrameWidget, SLOT(setCard(QString))); + connect(messageLog, &MessageLogWidget::cardNameHovered, cardInfoFrameWidget, + qOverload(&CardInfoFrameWidget::setCard)); connect(messageLog, &MessageLogWidget::showCardInfoPopup, this, &TabGame::showCardInfoPopup); - connect(messageLog, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); + connect(messageLog, &MessageLogWidget::deleteCardInfoPopup, this, &TabGame::deleteCardInfoPopup); if (!bReplay) { - connect(messageLog, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); - connect(messageLog, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString))); - connect(&SettingsCache::instance(), SIGNAL(chatMentionCompleterChanged()), this, SLOT(actCompleterChanged())); + connect(messageLog, &MessageLogWidget::openMessageDialog, this, &TabGame::openMessageDialog); + connect(messageLog, &MessageLogWidget::addMentionTag, this, &TabGame::addMentionTag); + connect(&SettingsCache::instance(), &SettingsCache::chatMentionCompleterChanged, this, + &TabGame::actCompleterChanged); timeElapsedLabel = new QLabel; timeElapsedLabel->setAlignment(Qt::AlignCenter); gameTimer = new QTimer(this); gameTimer->setInterval(1000); - connect(gameTimer, SIGNAL(timeout()), this, SLOT(incrementGameTime())); + connect(gameTimer, &QTimer::timeout, this, &TabGame::incrementGameTime); gameTimer->start(); sayLabel = new QLabel; @@ -1721,8 +1723,8 @@ void TabGame::createMessageDock(bool bReplay) } } - connect(tabSupervisor, SIGNAL(adminLockChanged(bool)), this, SLOT(adminLockChanged(bool))); - connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(actSay())); + connect(tabSupervisor, &TabSupervisor::adminLockChanged, this, &TabGame::adminLockChanged); + connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabGame::actSay); sayHLayout = new QHBoxLayout; sayHLayout->addWidget(sayLabel); @@ -1748,7 +1750,7 @@ void TabGame::createMessageDock(bool bReplay) messageLayoutDock->setFloating(false); messageLayoutDock->installEventFilter(this); - connect(messageLayoutDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); + connect(messageLayoutDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged); } void TabGame::hideEvent(QHideEvent *event) diff --git a/cockatrice/src/client/tabs/tab_logs.cpp b/cockatrice/src/client/tabs/tab_logs.cpp index e43be2215..0f75b4c74 100644 --- a/cockatrice/src/client/tabs/tab_logs.cpp +++ b/cockatrice/src/client/tabs/tab_logs.cpp @@ -116,8 +116,7 @@ void TabLog::getClicked() cmd.set_date_range(dateRange); cmd.set_maximum_results(maximumResults->value()); PendingCommand *pend = client->prepareModeratorCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(viewLogHistory_processResponse(Response))); + connect(pend, &PendingCommand::finished, this, &TabLog::viewLogHistory_processResponse); client->sendCommand(pend); } @@ -186,11 +185,11 @@ void TabLog::createDock() getButton = new QPushButton(tr("Get User Logs")); getButton->setAutoDefault(true); - connect(getButton, SIGNAL(clicked()), this, SLOT(getClicked())); + connect(getButton, &QPushButton::clicked, this, &TabLog::getClicked); clearButton = new QPushButton(tr("Clear Filters")); clearButton->setAutoDefault(true); - connect(clearButton, SIGNAL(clicked()), this, SLOT(clearClicked())); + connect(clearButton, &QPushButton::clicked, this, &TabLog::clearClicked); criteriaGrid = new QGridLayout; criteriaGrid->addWidget(labelFindUserName, 0, 0); diff --git a/cockatrice/src/client/tabs/tab_message.cpp b/cockatrice/src/client/tabs/tab_message.cpp index 78950e5be..9dc53156d 100644 --- a/cockatrice/src/client/tabs/tab_message.cpp +++ b/cockatrice/src/client/tabs/tab_message.cpp @@ -28,11 +28,11 @@ TabMessage::TabMessage(TabSupervisor *_tabSupervisor, { chatView = new ChatView(tabSupervisor, 0, true); connect(chatView, &ChatView::showCardInfoPopup, this, &TabMessage::showCardInfoPopup); - connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); - connect(chatView, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString))); + connect(chatView, &ChatView::deleteCardInfoPopup, this, &TabMessage::deleteCardInfoPopup); + connect(chatView, &ChatView::addMentionTag, this, &TabMessage::addMentionTag); sayEdit = new LineEditUnfocusable; sayEdit->setMaxLength(MAX_TEXT_LENGTH); - connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage())); + connect(sayEdit, &LineEditUnfocusable::returnPressed, this, &TabMessage::sendMessage); QVBoxLayout *vbox = new QVBoxLayout; vbox->addWidget(chatView); @@ -102,7 +102,7 @@ void TabMessage::sendMessage() cmd.set_message(sayEdit->text().toStdString()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(messageSent(const Response &))); + connect(pend, &PendingCommand::finished, this, &TabMessage::messageSent); client->sendCommand(pend); sayEdit->clear(); @@ -140,10 +140,10 @@ bool TabMessage::shouldShowSystemPopup(const Event_UserMessage &event) void TabMessage::showSystemPopup(const Event_UserMessage &event) { if (trayIcon) { - disconnect(trayIcon, SIGNAL(messageClicked()), 0, 0); + disconnect(trayIcon, &QSystemTrayIcon::messageClicked, 0, 0); trayIcon->showMessage(tr("Private message from") + " " + otherUserInfo->name().c_str(), event.message().c_str()); - connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(messageClicked())); + connect(trayIcon, &QSystemTrayIcon::messageClicked, this, &TabMessage::messageClicked); } else { qCWarning(TabMessageLog) << "Error: trayIcon is NULL. TabMessage::showSystemPopup failed"; } diff --git a/cockatrice/src/client/tabs/tab_replays.cpp b/cockatrice/src/client/tabs/tab_replays.cpp index 8081883cd..123c3d3fc 100644 --- a/cockatrice/src/client/tabs/tab_replays.cpp +++ b/cockatrice/src/client/tabs/tab_replays.cpp @@ -96,8 +96,8 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, c // Left side actions aOpenLocalReplay = new QAction(this); aOpenLocalReplay->setIcon(QPixmap("theme:icons/view")); - connect(aOpenLocalReplay, SIGNAL(triggered()), this, SLOT(actOpenLocalReplay())); - connect(localDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenLocalReplay())); + connect(aOpenLocalReplay, &QAction::triggered, this, &TabReplays::actOpenLocalReplay); + connect(localDirView, &QTreeView::doubleClicked, this, &TabReplays::actOpenLocalReplay); aRenameLocal = new QAction(this); aRenameLocal->setIcon(QPixmap("theme:icons/rename")); connect(aRenameLocal, &QAction::triggered, this, &TabReplays::actRenameLocal); @@ -106,7 +106,7 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, c connect(aNewLocalFolder, &QAction::triggered, this, &TabReplays::actNewLocalFolder); aDeleteLocalReplay = new QAction(this); aDeleteLocalReplay->setIcon(QPixmap("theme:icons/remove_row")); - connect(aDeleteLocalReplay, SIGNAL(triggered()), this, SLOT(actDeleteLocalReplay())); + connect(aDeleteLocalReplay, &QAction::triggered, this, &TabReplays::actDeleteLocalReplay); aOpenReplaysFolder = new QAction(this); aOpenReplaysFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon)); @@ -115,17 +115,17 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, c // Right side actions aOpenRemoteReplay = new QAction(this); aOpenRemoteReplay->setIcon(QPixmap("theme:icons/view")); - connect(aOpenRemoteReplay, SIGNAL(triggered()), this, SLOT(actOpenRemoteReplay())); - connect(serverDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenRemoteReplay())); + connect(aOpenRemoteReplay, &QAction::triggered, this, &TabReplays::actOpenRemoteReplay); + connect(serverDirView, &QTreeView::doubleClicked, this, &TabReplays::actOpenRemoteReplay); aDownload = new QAction(this); aDownload->setIcon(QPixmap("theme:icons/arrow_left_green")); - connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload())); + connect(aDownload, &QAction::triggered, this, &TabReplays::actDownload); aKeep = new QAction(this); aKeep->setIcon(QPixmap("theme:icons/lock")); - connect(aKeep, SIGNAL(triggered()), this, SLOT(actKeepRemoteReplay())); + connect(aKeep, &QAction::triggered, this, &TabReplays::actKeepRemoteReplay); aDeleteRemoteReplay = new QAction(this); aDeleteRemoteReplay->setIcon(QPixmap("theme:icons/remove_row")); - connect(aDeleteRemoteReplay, SIGNAL(triggered()), this, SLOT(actDeleteRemoteReplay())); + connect(aDeleteRemoteReplay, &QAction::triggered, this, &TabReplays::actDeleteRemoteReplay); // Add actions to toolbars leftToolBar->addAction(aOpenLocalReplay); @@ -146,8 +146,7 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, c mainWidget->setLayout(hbox); setCentralWidget(mainWidget); - connect(client, SIGNAL(replayAddedEventReceived(const Event_ReplayAdded &)), this, - SLOT(replayAddedEventReceived(const Event_ReplayAdded &))); + connect(client, &AbstractClient::replayAddedEventReceived, this, &TabReplays::replayAddedEventReceived); connect(client, &AbstractClient::userInfoChanged, this, &TabReplays::handleConnected); connect(client, &AbstractClient::statusChanged, this, &TabReplays::handleConnectionChanged); @@ -323,8 +322,7 @@ void TabReplays::actOpenRemoteReplay() cmd.set_replay_id(curRight->replay_id()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(openRemoteReplayFinished(const Response &))); + connect(pend, &PendingCommand::finished, this, &TabReplays::openRemoteReplayFinished); client->sendCommand(pend); } } @@ -378,8 +376,7 @@ void TabReplays::downloadNodeAtIndex(const QModelIndex &curLeft, const QModelInd PendingCommand *pend = client->prepareSessionCommand(cmd); pend->setExtraData(filePath); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(downloadFinished(Response, CommandContainer, QVariant))); + connect(pend, &PendingCommand::finished, this, &TabReplays::downloadFinished); client->sendCommand(pend); } // node at index was invalid @@ -416,8 +413,7 @@ void TabReplays::actKeepRemoteReplay() cmd.set_do_not_hide(!curRight->do_not_hide()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(keepRemoteReplayFinished(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &TabReplays::keepRemoteReplayFinished); client->sendCommand(pend); } } @@ -455,8 +451,7 @@ void TabReplays::actDeleteRemoteReplay() cmd.set_game_id(curRight->game_id()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(deleteRemoteReplayFinished(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &TabReplays::deleteRemoteReplayFinished); client->sendCommand(pend); } } diff --git a/cockatrice/src/client/tabs/tab_room.cpp b/cockatrice/src/client/tabs/tab_room.cpp index 02243cd19..1eb648f48 100644 --- a/cockatrice/src/client/tabs/tab_room.cpp +++ b/cockatrice/src/client/tabs/tab_room.cpp @@ -53,28 +53,29 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, SIGNAL(openMessageDialog(const QString &, bool))); chatView = new ChatView(tabSupervisor, nullptr, true, this); - connect(chatView, SIGNAL(showMentionPopup(const QString &)), this, SLOT(actShowMentionPopup(const QString &))); - connect(chatView, SIGNAL(messageClickedSignal()), this, SLOT(focusTab())); - connect(chatView, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); + connect(chatView, &ChatView::showMentionPopup, this, &TabRoom::actShowMentionPopup); + connect(chatView, &ChatView::messageClickedSignal, this, &TabRoom::focusTab); + connect(chatView, &ChatView::openMessageDialog, this, &TabRoom::openMessageDialog); connect(chatView, &ChatView::showCardInfoPopup, this, &TabRoom::showCardInfoPopup); - connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); - connect(chatView, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString))); - connect(&SettingsCache::instance(), SIGNAL(chatMentionCompleterChanged()), this, SLOT(actCompleterChanged())); + connect(chatView, &ChatView::deleteCardInfoPopup, this, &TabRoom::deleteCardInfoPopup); + connect(chatView, &ChatView::addMentionTag, this, &TabRoom::addMentionTag); + connect(&SettingsCache::instance(), &SettingsCache::chatMentionCompleterChanged, this, + &TabRoom::actCompleterChanged); sayLabel = new QLabel; sayEdit = new LineEditCompleter; sayEdit->setMaxLength(MAX_TEXT_LENGTH); sayLabel->setBuddy(sayEdit); - connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage())); + connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabRoom::sendMessage); QMenu *chatSettingsMenu = new QMenu(this); aClearChat = chatSettingsMenu->addAction(QString()); - connect(aClearChat, SIGNAL(triggered()), this, SLOT(actClearChat())); + connect(aClearChat, &QAction::triggered, this, &TabRoom::actClearChat); chatSettingsMenu->addSeparator(); aOpenChatSettings = chatSettingsMenu->addAction(QString()); - connect(aOpenChatSettings, SIGNAL(triggered()), this, SLOT(actOpenChatSettings())); + connect(aOpenChatSettings, &QAction::triggered, this, &TabRoom::actOpenChatSettings); QToolButton *chatSettingsButton = new QToolButton; chatSettingsButton->setIcon(QPixmap("theme:icons/settings")); @@ -126,7 +127,8 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, sayEdit->setCompleter(completer); actCompleterChanged(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &TabRoom::refreshShortcuts); refreshShortcuts(); retranslateUi(); @@ -165,9 +167,9 @@ void TabRoom::actShowPopup(const QString &message) { if (trayIcon && (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this) || QApplication::activeWindow() == nullptr || QApplication::focusWidget() == nullptr)) { - disconnect(trayIcon, SIGNAL(messageClicked()), nullptr, nullptr); + disconnect(trayIcon, &QSystemTrayIcon::messageClicked, nullptr, nullptr); trayIcon->showMessage(message, tr("Click to view")); - connect(trayIcon, SIGNAL(messageClicked()), chatView, SLOT(actMessageClicked())); + connect(trayIcon, &QSystemTrayIcon::messageClicked, chatView, &ChatView::messageClickedSignal); } } @@ -201,8 +203,7 @@ void TabRoom::sendMessage() cmd.set_message(sayEdit->text().toStdString()); PendingCommand *pend = prepareRoomCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(sayFinished(const Response &))); + connect(pend, &PendingCommand::finished, this, &TabRoom::sayFinished); sendRoomCommand(pend); sayEdit->clear(); } diff --git a/cockatrice/src/client/tabs/tab_server.cpp b/cockatrice/src/client/tabs/tab_server.cpp index 40fa401d2..24c3e7839 100644 --- a/cockatrice/src/client/tabs/tab_server.cpp +++ b/cockatrice/src/client/tabs/tab_server.cpp @@ -33,7 +33,7 @@ RoomSelector::RoomSelector(AbstractClient *_client, QWidget *parent) : QGroupBox roomList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); joinButton = new QPushButton; - connect(joinButton, SIGNAL(clicked()), this, SLOT(joinClicked())); + connect(joinButton, &QPushButton::clicked, this, &RoomSelector::joinClicked); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); buttonLayout->addWidget(joinButton); @@ -44,9 +44,8 @@ RoomSelector::RoomSelector(AbstractClient *_client, QWidget *parent) : QGroupBox retranslateUi(); setLayout(vbox); - connect(client, SIGNAL(listRoomsEventReceived(const Event_ListRooms &)), this, - SLOT(processListRoomsEvent(const Event_ListRooms &))); - connect(roomList, SIGNAL(activated(const QModelIndex &)), this, SLOT(joinClicked())); + connect(client, &AbstractClient::listRoomsEventReceived, this, &RoomSelector::processListRoomsEvent); + connect(roomList, &QTreeWidget::activated, this, &RoomSelector::joinClicked); client->sendCommand(client->prepareSessionCommand(Command_ListRooms())); } @@ -144,10 +143,9 @@ TabServer::TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T serverInfoBox = new QTextBrowser; serverInfoBox->setOpenExternalLinks(true); - connect(roomSelector, SIGNAL(joinRoomRequest(int, bool)), this, SLOT(joinRoom(int, bool))); + connect(roomSelector, &RoomSelector::joinRoomRequest, this, &TabServer::joinRoom); - connect(client, SIGNAL(serverMessageEventReceived(const Event_ServerMessage &)), this, - SLOT(processServerMessageEvent(const Event_ServerMessage &))); + connect(client, &AbstractClient::serverMessageEventReceived, this, &TabServer::processServerMessageEvent); QVBoxLayout *vbox = new QVBoxLayout; vbox->addWidget(roomSelector); @@ -185,8 +183,7 @@ void TabServer::joinRoom(int id, bool setCurrent) PendingCommand *pend = client->prepareSessionCommand(cmd); pend->setExtraData(setCurrent); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(joinRoomFinished(Response, CommandContainer, QVariant))); + connect(pend, &PendingCommand::finished, this, &TabServer::joinRoomFinished); client->sendCommand(pend); diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp index e7be2a1ff..5d9600d11 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.cpp +++ b/cockatrice/src/client/tabs/tab_supervisor.cpp @@ -6,6 +6,7 @@ #include "../../server/user/user_list_widget.h" #include "../../settings/cache_settings.h" #include "../ui/pixel_map_generator.h" +#include "api/edhrec/tab_edhrec_main.h" #include "pb/event_game_joined.pb.h" #include "pb/event_notify_user.pb.h" #include "pb/event_user_message.pb.h" @@ -25,6 +26,9 @@ #include "tab_replays.h" #include "tab_room.h" #include "tab_server.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 @@ -130,10 +134,19 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget * aTabDeckEditor = new QAction(this); connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(nullptr); }); + aTabVisualDeckEditor = new QAction(this); + connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(nullptr); }); + + aTabEdhRec = new QAction(this); + connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); }); + aTabVisualDeckStorage = new QAction(this); aTabVisualDeckStorage->setCheckable(true); connect(aTabVisualDeckStorage, &QAction::triggered, this, &TabSupervisor::actTabVisualDeckStorage); + aTabVisualDatabaseDisplay = new QAction(this); + connect(aTabVisualDatabaseDisplay, &QAction::triggered, this, [this] { addVisualDatabaseDisplayTab(); }); + aTabServer = new QAction(this); aTabServer->setCheckable(true); connect(aTabServer, &QAction::triggered, this, &TabSupervisor::actTabServer); @@ -176,7 +189,10 @@ void TabSupervisor::retranslateUi() { // tab menu actions aTabDeckEditor->setText(tr("Deck Editor")); + aTabVisualDeckEditor->setText(tr("Visual Deck Editor")); + aTabEdhRec->setText(tr("EDHRec")); aTabVisualDeckStorage->setText(tr("&Visual Deck Storage")); + aTabVisualDatabaseDisplay->setText(tr("Visual Database Display")); aTabServer->setText(tr("Server")); aTabAccount->setText(tr("Account")); aTabDeckStorage->setText(tr("Deck Storage")); @@ -223,6 +239,7 @@ void TabSupervisor::refreshShortcuts() { ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); aTabDeckEditor->setShortcuts(shortcuts.getShortcut("Tabs/aTabDeckEditor")); + aTabVisualDeckEditor->setShortcuts(shortcuts.getShortcut("Tabs/aTabVisualDeckEditor")); aTabVisualDeckStorage->setShortcuts(shortcuts.getShortcut("Tabs/aTabVisualDeckStorage")); aTabServer->setShortcuts(shortcuts.getShortcut("Tabs/aTabServer")); aTabAccount->setShortcuts(shortcuts.getShortcut("Tabs/aTabAccount")); @@ -370,8 +387,11 @@ void TabSupervisor::resetTabsMenu() { tabsMenu->clear(); tabsMenu->addAction(aTabDeckEditor); + tabsMenu->addAction(aTabVisualDeckEditor); + tabsMenu->addAction(aTabEdhRec); tabsMenu->addSeparator(); tabsMenu->addAction(aTabVisualDeckStorage); + tabsMenu->addAction(aTabVisualDatabaseDisplay); tabsMenu->addAction(aTabDeckStorage); tabsMenu->addAction(aTabReplays); } @@ -804,6 +824,36 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen) return tab; } +TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(const DeckLoader *deckToOpen) +{ + auto *tab = new TabDeckEditorVisual(this); + if (deckToOpen) + tab->openDeck(new DeckLoader(*deckToOpen)); + connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed); + connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addVisualDeckEditorTab); + myAddTab(tab); + deckEditorTabs.append(tab); + setCurrentWidget(tab); + return tab; +} + +TabEdhRecMain *TabSupervisor::addEdhrecMainTab() +{ + auto *tab = new TabEdhRecMain(this); + + myAddTab(tab); + setCurrentWidget(tab); + return tab; +} + +TabVisualDatabaseDisplay *TabSupervisor::addVisualDatabaseDisplayTab() +{ + auto *tab = new TabVisualDatabaseDisplay(this); + myAddTab(tab); + setCurrentWidget(tab); + return tab; +} + TabEdhRec *TabSupervisor::addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander) { auto *tab = new TabEdhRec(this); diff --git a/cockatrice/src/client/tabs/tab_supervisor.h b/cockatrice/src/client/tabs/tab_supervisor.h index c8ae109d8..0ca13ac41 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.h +++ b/cockatrice/src/client/tabs/tab_supervisor.h @@ -5,6 +5,10 @@ #include "../../server/user/user_list_proxy.h" #include "abstract_tab_deck_editor.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 @@ -91,8 +95,8 @@ private: QList deckEditorTabs; bool isLocalGame; - QAction *aTabDeckEditor, *aTabVisualDeckStorage, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, - *aTabAdmin, *aTabLog; + QAction *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabVisualDeckStorage, *aTabVisualDatabaseDisplay, + *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin, *aTabLog; int myAddTab(Tab *tab, QAction *manager = nullptr); void addCloseButtonToTab(Tab *tab, int tabIndex, QAction *manager); @@ -149,6 +153,9 @@ signals: public slots: TabDeckEditor *addDeckEditorTab(const DeckLoader *deckToOpen); + TabDeckEditorVisual *addVisualDeckEditorTab(const DeckLoader *deckToOpen); + TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab(); + TabEdhRecMain *addEdhrecMainTab(); TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false); void openReplay(GameReplay *replay); void maximizeMainWindow(); diff --git a/cockatrice/src/client/tabs/tab_visual_database_display.cpp b/cockatrice/src/client/tabs/tab_visual_database_display.cpp new file mode 100644 index 000000000..d45cdded0 --- /dev/null +++ b/cockatrice/src/client/tabs/tab_visual_database_display.cpp @@ -0,0 +1,20 @@ +#include "tab_visual_database_display.h" + +#include "tab_deck_editor.h" + +TabVisualDatabaseDisplay::TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) +{ + deckEditor = new TabDeckEditor(_tabSupervisor); + deckEditor->setHidden(true); + visualDatabaseDisplayWidget = + new VisualDatabaseDisplayWidget(this, deckEditor, deckEditor->databaseDisplayDockWidget->databaseModel, + deckEditor->databaseDisplayDockWidget->databaseDisplayModel); + + setCentralWidget(visualDatabaseDisplayWidget); + + TabVisualDatabaseDisplay::retranslateUi(); +} + +void TabVisualDatabaseDisplay::retranslateUi() +{ +} diff --git a/cockatrice/src/client/tabs/tab_visual_database_display.h b/cockatrice/src/client/tabs/tab_visual_database_display.h new file mode 100644 index 000000000..55f895e5b --- /dev/null +++ b/cockatrice/src/client/tabs/tab_visual_database_display.h @@ -0,0 +1,26 @@ +#ifndef TAB_VISUAL_DATABASE_DISPLAY_H +#define TAB_VISUAL_DATABASE_DISPLAY_H + +#include "../ui/widgets/visual_database_display/visual_database_display_widget.h" +#include "tab.h" + +#include + +class TabVisualDatabaseDisplay : public Tab +{ + Q_OBJECT + +private: + TabDeckEditor *deckEditor; + VisualDatabaseDisplayWidget *visualDatabaseDisplayWidget; + +public: + TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor); + void retranslateUi() override; + QString getTabText() const override + { + return tr("Visual Database Display"); + } +}; + +#endif // TAB_VISUAL_DATABASE_DISPLAY_H diff --git a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp new file mode 100644 index 000000000..224c4db15 --- /dev/null +++ b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -0,0 +1,470 @@ +#include "tab_deck_editor_visual.h" + +#include "../../../deck/deck_list_model.h" +#include "../../../deck/deck_stats_interface.h" +#include "../../../game/cards/card_database_model.h" +#include "../../../game/filters/filter_builder.h" +#include "../../../server/pending_command.h" +#include "../../../settings/cache_settings.h" +#include "../../ui/pixel_map_generator.h" +#include "../../ui/widgets/cards/card_info_frame_widget.h" +#include "../../ui/widgets/deck_analytics/deck_analytics_widget.h" +#include "../../ui/widgets/visual_deck_editor/visual_deck_editor_widget.h" +#include "../tab_deck_editor.h" +#include "../tab_supervisor.h" +#include "pb/command_deck_upload.pb.h" +#include "tab_deck_editor_visual_tab_widget.h" +#include "trice_limits.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : AbstractTabDeckEditor(_tabSupervisor) +{ + setObjectName("TabDeckEditorVisual"); + + createCentralFrame(); + + TabDeckEditorVisual::createMenus(); + + installEventFilter(this); + + TabDeckEditorVisual::retranslateUi(); + connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + TabDeckEditorVisual::refreshShortcuts(); + + TabDeckEditorVisual::loadLayout(); + databaseDisplayDockWidget->setHidden(true); +} + +void TabDeckEditorVisual::createCentralFrame() +{ + centralWidget = new QWidget(this); + centralWidget->setObjectName("centralWidget"); + + centralFrame = new QVBoxLayout; + centralWidget->setLayout(centralFrame); + + tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckDockWidget->deckModel, + databaseDisplayDockWidget->databaseModel, + databaseDisplayDockWidget->databaseDisplayModel); + connect(tabContainer, &TabDeckEditorVisualTabWidget::cardChanged, this, + &TabDeckEditorVisual::changeModelIndexAndCardInfo); + connect(tabContainer, &TabDeckEditorVisualTabWidget::cardChangedDatabaseDisplay, this, + &AbstractTabDeckEditor::updateCard); + connect(tabContainer, &TabDeckEditorVisualTabWidget::cardClicked, this, + &TabDeckEditorVisual::processMainboardCardClick); + + connect(tabContainer, &TabDeckEditorVisualTabWidget::cardClickedDatabaseDisplay, this, + &TabDeckEditorVisual::processCardClickDatabaseDisplay); + centralFrame->addWidget(tabContainer); + + setCentralWidget(centralWidget); + setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); +} + +void TabDeckEditorVisual::onDeckChanged() +{ + AbstractTabDeckEditor::onDeckChanged(); + tabContainer->visualDeckView->decklistDataChanged(QModelIndex(), QModelIndex()); + tabContainer->deckAnalytics->refreshDisplays(deckDockWidget->deckModel); + tabContainer->sampleHandWidget->setDeckModel(deckDockWidget->deckModel); +} + +void TabDeckEditorVisual::createMenus() +{ + deckMenu = new DeckEditorMenu(this); + addTabMenu(deckMenu); + + viewMenu = new QMenu(this); + + cardInfoDockMenu = viewMenu->addMenu(QString()); + deckDockMenu = viewMenu->addMenu(QString()); + deckAnalyticsMenu = viewMenu->addMenu(QString()); + filterDockMenu = viewMenu->addMenu(QString()); + printingSelectorDockMenu = viewMenu->addMenu(QString()); + + aCardInfoDockVisible = cardInfoDockMenu->addAction(QString()); + aCardInfoDockVisible->setCheckable(true); + connect(aCardInfoDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + aCardInfoDockFloating = cardInfoDockMenu->addAction(QString()); + aCardInfoDockFloating->setCheckable(true); + connect(aCardInfoDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + + aDeckDockVisible = deckDockMenu->addAction(QString()); + aDeckDockVisible->setCheckable(true); + connect(aDeckDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + aDeckDockFloating = deckDockMenu->addAction(QString()); + aDeckDockFloating->setCheckable(true); + connect(aDeckDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + + aDeckAnalyticsDockVisible = deckAnalyticsMenu->addAction(QString()); + aDeckAnalyticsDockVisible->setCheckable(true); + connect(aDeckAnalyticsDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + aDeckAnalyticsDockFloating = deckAnalyticsMenu->addAction(QString()); + aDeckAnalyticsDockFloating->setCheckable(true); + connect(aDeckAnalyticsDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + + aFilterDockVisible = filterDockMenu->addAction(QString()); + aFilterDockVisible->setCheckable(true); + connect(aFilterDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + aFilterDockFloating = filterDockMenu->addAction(QString()); + aFilterDockFloating->setCheckable(true); + connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + + aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString()); + aPrintingSelectorDockVisible->setCheckable(true); + connect(aPrintingSelectorDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString()); + aPrintingSelectorDockFloating->setCheckable(true); + connect(aPrintingSelectorDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + + viewMenu->addSeparator(); + + aResetLayout = viewMenu->addAction(QString()); + connect(aResetLayout, SIGNAL(triggered()), this, SLOT(restartLayout())); + viewMenu->addAction(aResetLayout); + + deckMenu->setSaveStatus(false); + + addTabMenu(viewMenu); +} + +QString TabDeckEditorVisual::getTabText() const +{ + QString result = tr("Visual Deck: %1").arg(deckDockWidget->getSimpleDeckName()); + if (modified) + result.prepend("* "); + return result; +} + +void TabDeckEditorVisual::changeModelIndexAndCardInfo(const CardInfoPtr &activeCard) +{ + updateCard(activeCard); + changeModelIndexToCard(activeCard); +} + +void TabDeckEditorVisual::changeModelIndexToCard(const CardInfoPtr &activeCard) +{ + QString cardName = activeCard->getName(); + QModelIndex index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_MAIN); + if (!index.isValid()) { + index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_SIDE); + } + deckDockWidget->deckView->setCurrentIndex(index); +} + +void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, + CardInfoPictureWithTextOverlayWidget *instance, + QString zoneName) +{ + if (event->button() == Qt::LeftButton) { + actSwapCard(instance->getInfo(), zoneName); + } else if (event->button() == Qt::RightButton) { + actDecrementCard(instance->getInfo()); + } else if (event->button() == Qt::MiddleButton) { + deckDockWidget->actRemoveCard(); + } +} + +void TabDeckEditorVisual::processCardClickDatabaseDisplay(QMouseEvent *event, + CardInfoPictureWithTextOverlayWidget *instance) +{ + if (event->button() == Qt::LeftButton) { + actAddCard(instance->getInfo()); + } else if (event->button() == Qt::RightButton) { + actDecrementCard(instance->getInfo()); + } else if (event->button() == Qt::MiddleButton) { + deckDockWidget->actRemoveCard(); + } +} + +bool TabDeckEditorVisual::actSaveDeckAs() +{ + // We have to disable the quick-add search bar or else it'll steal focus after dialog creation. + tabContainer->visualDeckView->searchBar->setEnabled(false); + auto result = AbstractTabDeckEditor::actSaveDeckAs(); + tabContainer->visualDeckView->searchBar->setEnabled(true); + return result; +} + +void TabDeckEditorVisual::showPrintingSelector() +{ + printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getInfo(), DECK_ZONE_MAIN); + printingSelectorDockWidget->printingSelector->updateDisplay(); + aPrintingSelectorDockVisible->setChecked(true); + printingSelectorDockWidget->setVisible(true); +} + +void TabDeckEditorVisual::restartLayout() +{ + deckDockWidget->setVisible(true); + cardInfoDockWidget->setVisible(true); + filterDockWidget->setVisible(true); + + deckDockWidget->setFloating(false); + cardInfoDockWidget->setFloating(false); + filterDockWidget->setFloating(false); + + aCardInfoDockVisible->setChecked(true); + aDeckDockVisible->setChecked(true); + aFilterDockVisible->setChecked(true); + + aCardInfoDockFloating->setChecked(false); + aDeckDockFloating->setChecked(false); + aFilterDockFloating->setChecked(false); + + addDockWidget(static_cast(2), deckDockWidget); + addDockWidget(static_cast(2), cardInfoDockWidget); + addDockWidget(static_cast(1), deckAnalyticsDock); + addDockWidget(static_cast(2), filterDockWidget); + + splitDockWidget(cardInfoDockWidget, deckDockWidget, Qt::Horizontal); + splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Vertical); + splitDockWidget(searchAndDatabaseDock, deckAnalyticsDock, Qt::Vertical); + + deckDockWidget->setMinimumWidth(360); + deckDockWidget->setMaximumWidth(360); + + cardInfoDockWidget->setMinimumSize(250, 360); + cardInfoDockWidget->setMaximumSize(250, 360); + QTimer::singleShot(100, this, SLOT(freeDocksSize())); +} + +void TabDeckEditorVisual::freeDocksSize() +{ + deckDockWidget->setMinimumSize(100, 100); + deckDockWidget->setMaximumSize(5000, 5000); + + cardInfoDockWidget->setMinimumSize(100, 100); + cardInfoDockWidget->setMaximumSize(5000, 5000); + + filterDockWidget->setMinimumSize(100, 100); + filterDockWidget->setMaximumSize(5000, 5000); + + databaseDisplayDockWidget->setMinimumSize(100, 100); + databaseDisplayDockWidget->setMaximumSize(1400, 5000); +} + +void TabDeckEditorVisual::refreshShortcuts() +{ + ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); + aResetLayout->setShortcuts(shortcuts.getShortcut("TabDeckEditorVisual/aResetLayout")); +} + +void TabDeckEditorVisual::loadLayout() +{ + LayoutsSettings &layouts = SettingsCache::instance().layouts(); + auto &layoutState = layouts.getDeckEditorLayoutState(); + if (layoutState.isNull()) { + restartLayout(); + } else { + restoreState(layoutState); + restoreGeometry(layouts.getDeckEditorGeometry()); + } + + aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden()); + aFilterDockVisible->setChecked(!filterDockWidget->isHidden()); + aDeckDockVisible->setChecked(!deckDockWidget->isHidden()); + aPrintingSelectorDockVisible->setChecked(!printingSelectorDockWidget->isHidden()); + + aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); + aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); + aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); + aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked()); + + aCardInfoDockFloating->setChecked(cardInfoDockWidget->isFloating()); + aFilterDockFloating->setChecked(filterDockWidget->isFloating()); + aDeckDockFloating->setChecked(deckDockWidget->isFloating()); + aPrintingSelectorDockFloating->setChecked(printingSelectorDockWidget->isFloating()); + + cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); + cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); + + filterDockWidget->setMinimumSize(layouts.getDeckEditorFilterSize()); + filterDockWidget->setMaximumSize(layouts.getDeckEditorFilterSize()); + + deckDockWidget->setMinimumSize(layouts.getDeckEditorDeckSize()); + deckDockWidget->setMaximumSize(layouts.getDeckEditorDeckSize()); + + printingSelectorDockWidget->setMinimumSize(layouts.getDeckEditorPrintingSelectorSize()); + printingSelectorDockWidget->setMaximumSize(layouts.getDeckEditorPrintingSelectorSize()); + + databaseDisplayDockWidget->setMinimumSize(100, 100); + databaseDisplayDockWidget->setMaximumSize(1400, 5000); + + QTimer::singleShot(100, this, &TabDeckEditorVisual::freeDocksSize); +} + +void TabDeckEditorVisual::retranslateUi() +{ + deckMenu->setTitle(tr("&Visual Deck Editor")); + + cardInfoDockWidget->setWindowTitle(tr("Card Info")); + deckDockWidget->setWindowTitle(tr("Deck")); + filterDockWidget->setWindowTitle(tr("Filters")); + + viewMenu->setTitle(tr("&View")); + cardInfoDockMenu->setTitle(tr("Card Info")); + deckDockMenu->setTitle(tr("Deck")); + deckAnalyticsMenu->setTitle(tr("Deck Analytics")); + filterDockMenu->setTitle(tr("Filters")); + printingSelectorDockMenu->setTitle(tr("Printing")); + + aCardInfoDockVisible->setText(tr("Visible")); + aCardInfoDockFloating->setText(tr("Floating")); + + aDeckDockVisible->setText(tr("Visible")); + aDeckDockFloating->setText(tr("Floating")); + + aDeckAnalyticsDockVisible->setText(tr("Visible")); + aDeckAnalyticsDockFloating->setText(tr("Floating")); + + aFilterDockVisible->setText(tr("Visible")); + aFilterDockFloating->setText(tr("Floating")); + + aPrintingSelectorDockVisible->setText(tr("Visible")); + aPrintingSelectorDockFloating->setText(tr("Floating")); + + aResetLayout->setText(tr("Reset layout")); +} + +// Method uses to sync docks state with menu items state +bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e) +{ + if (e->type() == QEvent::Close) { + if (o == cardInfoDockWidget) { + aCardInfoDockVisible->setChecked(false); + aCardInfoDockFloating->setEnabled(false); + } else if (o == deckDockWidget) { + aDeckDockVisible->setChecked(false); + aDeckDockFloating->setEnabled(false); + } else if (o == deckAnalyticsDock) { + aDeckAnalyticsDockVisible->setChecked(false); + aDeckAnalyticsDockFloating->setEnabled(false); + } else if (o == filterDockWidget) { + aFilterDockVisible->setChecked(false); + aFilterDockFloating->setEnabled(false); + } else if (o == printingSelectorDockWidget) { + aPrintingSelectorDockVisible->setChecked(false); + aPrintingSelectorDockFloating->setEnabled(false); + } + } + if (o == this && e->type() == QEvent::Hide) { + LayoutsSettings &layouts = SettingsCache::instance().layouts(); + layouts.setDeckEditorLayoutState(saveState()); + layouts.setDeckEditorGeometry(saveGeometry()); + layouts.setDeckEditorCardSize(cardInfoDockWidget->size()); + layouts.setDeckEditorFilterSize(filterDockWidget->size()); + layouts.setDeckEditorDeckSize(deckDockWidget->size()); + layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size()); + } + return false; +} + +void TabDeckEditorVisual::dockVisibleTriggered() +{ + QObject *o = sender(); + if (o == aCardInfoDockVisible) { + cardInfoDockWidget->setHidden(!aCardInfoDockVisible->isChecked()); + aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); + return; + } + + if (o == aDeckDockVisible) { + deckDockWidget->setHidden(!aDeckDockVisible->isChecked()); + aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); + return; + } + + if (o == aDeckAnalyticsDockVisible) { + deckAnalyticsDock->setHidden(!aDeckAnalyticsDockVisible->isChecked()); + aDeckAnalyticsDockFloating->setEnabled(aDeckAnalyticsDockVisible->isChecked()); + return; + } + + if (o == aFilterDockVisible) { + filterDockWidget->setHidden(!aFilterDockVisible->isChecked()); + aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); + return; + } + + if (o == aPrintingSelectorDockVisible) { + printingSelectorDockWidget->setHidden(!aPrintingSelectorDockVisible->isChecked()); + aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked()); + return; + } +} + +void TabDeckEditorVisual::dockFloatingTriggered() +{ + QObject *o = sender(); + if (o == aCardInfoDockFloating) { + cardInfoDockWidget->setFloating(aCardInfoDockFloating->isChecked()); + return; + } + + if (o == aDeckDockFloating) { + deckDockWidget->setFloating(aDeckDockFloating->isChecked()); + return; + } + + if (o == aDeckAnalyticsDockFloating) { + deckAnalyticsDock->setFloating(aDeckAnalyticsDockFloating->isChecked()); + return; + } + + if (o == aFilterDockFloating) { + filterDockWidget->setFloating(aFilterDockFloating->isChecked()); + return; + } + + if (o == aPrintingSelectorDockFloating) { + printingSelectorDockWidget->setFloating(aPrintingSelectorDockFloating->isChecked()); + return; + } +} + +void TabDeckEditorVisual::dockTopLevelChanged(bool topLevel) +{ + QObject *o = sender(); + if (o == cardInfoDockWidget) { + aCardInfoDockFloating->setChecked(topLevel); + return; + } + + if (o == deckDockWidget) { + aDeckDockFloating->setChecked(topLevel); + return; + } + + if (o == filterDockWidget) { + aFilterDockFloating->setChecked(topLevel); + return; + } + + if (o == deckAnalyticsDock) { + aDeckAnalyticsDockFloating->setChecked(topLevel); + return; + } + + if (o == printingSelectorDockWidget) { + aPrintingSelectorDockFloating->setChecked(topLevel); + return; + } +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.h b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.h new file mode 100644 index 000000000..f4add316e --- /dev/null +++ b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.h @@ -0,0 +1,53 @@ +#ifndef WINDOW_DECKEDITORVISUAL_H +#define WINDOW_DECKEDITORVISUAL_H + +#include "../tab.h" +#include "tab_deck_editor_visual_tab_widget.h" + +class TabDeckEditorVisual : public AbstractTabDeckEditor +{ + Q_OBJECT +protected slots: + void loadLayout() override; + void restartLayout() override; + void freeDocksSize() override; + void refreshShortcuts() override; + + bool eventFilter(QObject *o, QEvent *e) override; + void dockVisibleTriggered() override; + void dockFloatingTriggered() override; + void dockTopLevelChanged(bool topLevel) override; + +protected: + TabDeckEditorVisualTabWidget *tabContainer; + + QVBoxLayout *centralFrame; + QVBoxLayout *searchAndDatabaseFrame; + QHBoxLayout *searchLayout; + QDockWidget *searchAndDatabaseDock; + QDockWidget *deckAnalyticsDock; + QWidget *centralWidget; + QMenu *deckAnalyticsMenu; + QAction *aDeckAnalyticsDockVisible, *aDeckAnalyticsDockFloating; + +public: + explicit TabDeckEditorVisual(TabSupervisor *_tabSupervisor); + void retranslateUi() override; + QString getTabText() const override; + void changeModelIndexAndCardInfo(const CardInfoPtr &activeCard); + void changeModelIndexToCard(const CardInfoPtr &activeCard); + void createDeckAnalyticsDock(); + void createMenus() override; + void createSearchAndDatabaseFrame(); + void createCentralFrame(); + +public slots: + void onDeckChanged() override; + void showPrintingSelector() override; + void + processMainboardCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); + void processCardClickDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + bool actSaveDeckAs() override; +}; + +#endif diff --git a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp new file mode 100644 index 000000000..a34d7ad94 --- /dev/null +++ b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp @@ -0,0 +1,113 @@ +#include "tab_deck_editor_visual_tab_widget.h" + +#include "../../ui/widgets/visual_database_display/visual_database_display_widget.h" +#include "../abstract_tab_deck_editor.h" + +TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent, + AbstractTabDeckEditor *_deckEditor, + DeckListModel *_deckModel, + CardDatabaseModel *_cardDatabaseModel, + CardDatabaseDisplayModel *_cardDatabaseDisplayModel) + : QTabWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), cardDatabaseModel(_cardDatabaseModel), + cardDatabaseDisplayModel(_cardDatabaseDisplayModel) +{ + this->setTabsClosable(true); // Enable tab closing + connect(this, &QTabWidget::tabCloseRequested, this, &TabDeckEditorVisualTabWidget::handleTabClose); + + // Set up the layout and add tab widget + layout = new QVBoxLayout(this); + setLayout(layout); + + visualDeckView = new VisualDeckEditorWidget(this, deckModel); + visualDeckView->setObjectName("visualDeckView"); + connect(visualDeckView, &VisualDeckEditorWidget::activeCardChanged, this, + &TabDeckEditorVisualTabWidget::onCardChanged); + connect(visualDeckView, &VisualDeckEditorWidget::cardClicked, this, + &TabDeckEditorVisualTabWidget::onCardClickedDeckEditor); + connect(visualDeckView, &VisualDeckEditorWidget::cardAdditionRequested, deckEditor, + &AbstractTabDeckEditor::actAddCard); + + visualDatabaseDisplay = + new VisualDatabaseDisplayWidget(this, deckEditor, _cardDatabaseModel, _cardDatabaseDisplayModel); + visualDatabaseDisplay->setObjectName("visualDatabaseView"); + connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardHoveredDatabaseDisplay, this, + &TabDeckEditorVisualTabWidget::onCardChangedDatabaseDisplay); + connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardClickedDatabaseDisplay, this, + &TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay); + + deckAnalytics = new DeckAnalyticsWidget(this, deckModel); + deckAnalytics->setObjectName("deckAnalytics"); + + sampleHandWidget = new VisualDeckEditorSampleHandWidget(this, deckModel); + + this->addNewTab(visualDeckView, tr("Visual Deck View")); + this->addNewTab(visualDatabaseDisplay, tr("Visual Database Display")); + this->addNewTab(deckAnalytics, tr("Deck Analytics")); + this->addNewTab(sampleHandWidget, tr("Sample Hand")); +} + +void TabDeckEditorVisualTabWidget::onCardChanged(CardInfoPtr activeCard) +{ + emit cardChanged(activeCard); +} + +void TabDeckEditorVisualTabWidget::onCardChangedDatabaseDisplay(CardInfoPtr activeCard) +{ + emit cardChangedDatabaseDisplay(activeCard); +} + +void TabDeckEditorVisualTabWidget::onCardClickedDeckEditor(QMouseEvent *event, + CardInfoPictureWithTextOverlayWidget *instance, + QString zoneName) +{ + emit cardClicked(event, instance, zoneName); +} + +void TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay(QMouseEvent *event, + CardInfoPictureWithTextOverlayWidget *instance) +{ + emit cardClickedDatabaseDisplay(event, instance); +} + +void TabDeckEditorVisualTabWidget::addNewTab(QWidget *widget, const QString &title) +{ + // Add new tab to the tab widget + this->addTab(widget, title); +} + +void TabDeckEditorVisualTabWidget::removeCurrentTab() +{ + // Remove the currently selected tab + int currentIndex = this->currentIndex(); + if (currentIndex != -1) { + this->removeTab(currentIndex); + } +} + +void TabDeckEditorVisualTabWidget::setTabTitle(int index, const QString &title) +{ + // Set the title of the tab at the given index + if (index >= 0 && index < this->count()) { + this->setTabText(index, title); + } +} + +QWidget *TabDeckEditorVisualTabWidget::getCurrentTab() const +{ + // Return the currently selected tab widget + return this->currentWidget(); +} + +int TabDeckEditorVisualTabWidget::getTabCount() const +{ + // Return the number of tabs + return this->count(); +} + +void TabDeckEditorVisualTabWidget::handleTabClose(int index) +{ + // Handle closing of the tab at the given index + QWidget *tab = this->widget(index); + this->removeTab(index); + delete tab; // Delete the tab's widget to free memory +} diff --git a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h new file mode 100644 index 000000000..c2454538a --- /dev/null +++ b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h @@ -0,0 +1,62 @@ +#ifndef TAB_DECK_EDITOR_VISUAL_TAB_WIDGET_H +#define TAB_DECK_EDITOR_VISUAL_TAB_WIDGET_H + +#include "../../ui/widgets/deck_analytics/deck_analytics_widget.h" +#include "../../ui/widgets/printing_selector/printing_selector.h" +#include "../../ui/widgets/visual_database_display/visual_database_display_widget.h" +#include "../../ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h" +#include "../../ui/widgets/visual_deck_editor/visual_deck_editor_widget.h" +#include "../abstract_tab_deck_editor.h" + +#include +#include +#include + +class TabDeckEditorVisualTabWidget : public QTabWidget +{ + Q_OBJECT + +public: + explicit TabDeckEditorVisualTabWidget(QWidget *parent, + AbstractTabDeckEditor *_deckEditor, + DeckListModel *_deckModel, + CardDatabaseModel *_cardDatabaseModel, + CardDatabaseDisplayModel *_cardDatabaseDisplayModel); + + // Utility functions + void addNewTab(QWidget *widget, const QString &title); + void removeCurrentTab(); + void setTabTitle(int index, const QString &title); + QWidget *getCurrentTab() const; + int getTabCount() const; + + VisualDeckEditorWidget *visualDeckView; + DeckAnalyticsWidget *deckAnalytics; + VisualDatabaseDisplayWidget *visualDatabaseDisplay; + PrintingSelector *printingSelector; + VisualDeckEditorSampleHandWidget *sampleHandWidget; + +public slots: + void onCardChanged(CardInfoPtr activeCard); + void onCardChangedDatabaseDisplay(CardInfoPtr activeCard); + void onCardClickedDeckEditor(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); + void onCardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + +signals: + void cardChanged(CardInfoPtr activeCard); + void cardChangedDatabaseDisplay(CardInfoPtr activeCard); + void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); + void cardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + +private: + QVBoxLayout *layout; // Layout for the tab widget and other controls + AbstractTabDeckEditor *deckEditor; + DeckListModel *deckModel; + CardDatabaseModel *cardDatabaseModel; + CardDatabaseDisplayModel *cardDatabaseDisplayModel; + +private slots: + void handleTabClose(int index); // Slot for closing a tab +}; + +#endif // TAB_DECK_EDITOR_VISUAL_TAB_WIDGET_H diff --git a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp index 332f5b779..38af03c59 100644 --- a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp +++ b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -12,7 +12,7 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor), visualDeckStorageWidget(new VisualDeckStorageWidget(this)) { - connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::addDeckEditorTab); + connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::addVisualDeckEditorTab); connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckLoadRequested, this, &TabDeckStorageVisual::actOpenLocalDeck); connect(visualDeckStorageWidget, &VisualDeckStorageWidget::openDeckEditor, this, diff --git a/cockatrice/src/client/ui/layouts/overlap_layout.cpp b/cockatrice/src/client/ui/layouts/overlap_layout.cpp index da44399a7..234bc41f4 100644 --- a/cockatrice/src/client/ui/layouts/overlap_layout.cpp +++ b/cockatrice/src/client/ui/layouts/overlap_layout.cpp @@ -1,5 +1,6 @@ #include "overlap_layout.h" +#include #include /** @@ -32,9 +33,10 @@ OverlapLayout::OverlapLayout(QWidget *parent, const int overlapPercentage, const int maxColumns, const int maxRows, - const Qt::Orientation direction) + const Qt::Orientation _overlapDirection, + const Qt::Orientation _flowDirection) : QLayout(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows), - direction(direction) + overlapDirection(_overlapDirection), flowDirection(_flowDirection) { } @@ -148,17 +150,21 @@ void OverlapLayout::setGeometry(const QRect &rect) } // Calculate the overlap offsets based on the layout direction and overlap percentage. - const int overlapOffsetWidth = (direction == Qt::Horizontal) ? (maxItemWidth * overlapPercentage / 100) : 0; - const int overlapOffsetHeight = (direction == Qt::Vertical) ? (maxItemHeight * overlapPercentage / 100) : 0; + const int overlapOffsetWidth = (overlapDirection == Qt::Horizontal) ? (maxItemWidth * overlapPercentage / 100) : 0; + const int overlapOffsetHeight = (overlapDirection == Qt::Vertical) ? (maxItemHeight * overlapPercentage / 100) : 0; // Determine the number of columns based on layout constraints and available space. int columns; - if (direction == Qt::Horizontal) { + if (flowDirection == Qt::Horizontal) { if (maxColumns > 0) { // Calculate the maximum possible columns given the available width and overlap. const int availableColumns = (availableWidth + overlapOffsetWidth) / (maxItemWidth - overlapOffsetWidth); // Use the smaller of maxColumns and availableColumns. + qCDebug(OverlapLayoutLog) << " Max Columns " << maxColumns << " available columns " << availableColumns; columns = qMin(maxColumns, availableColumns); + if (columns < 1) { + columns = 1; + } } else { // If no maxColumns constraint, allow as many columns as possible. columns = INT_MAX; @@ -170,12 +176,16 @@ void OverlapLayout::setGeometry(const QRect &rect) // Determine the number of rows based on layout constraints and available space. int rows; - if (direction == Qt::Vertical) { + if (flowDirection == Qt::Vertical) { if (maxRows > 0) { // Calculate the maximum possible rows given the available height and overlap. const int availableRows = (availableHeight + overlapOffsetHeight) / (maxItemHeight - overlapOffsetHeight); // Use the smaller of maxRows and availableRows. + qCDebug(OverlapLayoutLog) << " Max Rows " << maxRows << " available rows " << availableRows; rows = qMin(maxRows, availableRows); + if (rows < 1) { + rows = 1; + } } else { // If no maxRows constraint, allow as many rows as possible. rows = INT_MAX; @@ -200,16 +210,17 @@ void OverlapLayout::setGeometry(const QRect &rect) const int yPos = rect.top() + currentRow * (maxItemHeight - overlapOffsetHeight); item->setGeometry(QRect(xPos, yPos, maxItemWidth, maxItemHeight)); + // TODO: Figure this out properly or maybe adjust size hint to account for this? // Update row and column indices based on the layout direction. - if (direction == Qt::Horizontal) { + if (overlapDirection == Qt::Horizontal) { currentColumn++; - if (currentColumn >= columns) { + if (currentColumn > qCeil(itemList.size() / rows)) { currentColumn = 0; currentRow++; } } else { currentRow++; - if (currentRow >= rows) { + if (currentRow > qCeil(itemList.size() / columns)) { currentRow = 0; currentColumn++; } @@ -223,88 +234,86 @@ void OverlapLayout::setGeometry(const QRect &rect) */ QSize OverlapLayout::calculatePreferredSize() const { + // Get the parent widget for size and margin calculations. + const QWidget *parentWidget = this->parentWidget(); + if (!parentWidget) { + return QSize(0, 0); + } - // Determine the maximum item dimensions. + if (itemList.isEmpty()) { + return QSize(0, 0); + } + + int availableWidth = parentWidget->width(); + int availableHeight = parentWidget->height(); + const QMargins margins = parentWidget->contentsMargins(); + availableWidth -= margins.left() + margins.right(); + availableHeight -= margins.top() + margins.bottom(); + + // Determine the maximum item width and height among all layout items. int maxItemWidth = 0; int maxItemHeight = 0; for (QLayoutItem *item : itemList) { - if (item == nullptr) { - continue; + if (item != nullptr && item->widget()) { + QSize itemSize = item->widget()->sizeHint(); + maxItemWidth = qMax(maxItemWidth, itemSize.width()); + maxItemHeight = qMax(maxItemHeight, itemSize.height()); } - if (!item->widget()) { - continue; - } - - QSize itemSize = item->widget()->sizeHint(); - maxItemWidth = qMax(maxItemWidth, itemSize.width()); - maxItemHeight = qMax(maxItemHeight, itemSize.height()); } - // Calculate the overlap offsets. - const int extra_for_overlap = (100 - overlapPercentage); - const int overlapOffsetWidth = - (direction == Qt::Horizontal) ? qRound((maxItemWidth / 100.0) * extra_for_overlap) : 0; - const int overlapOffsetHeight = - (direction == Qt::Vertical) ? qRound((maxItemHeight / 100.0) * extra_for_overlap) : 0; + // Calculate the overlap offsets based on the layout direction and overlap percentage. + const int overlapOffsetWidth = (overlapDirection == Qt::Horizontal) ? (maxItemWidth * overlapPercentage / 100) : 0; + const int overlapOffsetHeight = (overlapDirection == Qt::Vertical) ? (maxItemHeight * overlapPercentage / 100) : 0; - // Variables to hold the total dimensions based on layout orientation. - int totalWidth = 0; - int totalHeight = 0; - - // Calculate the total size based on the layout direction and constraints. - if (direction == Qt::Horizontal) { - // Determine the number of columns: - // - Use maxColumns if it is greater than 0 (constraint set by the user). - // - Otherwise, set the number of columns to the total number of items in the layout. - const int numColumns = (maxColumns > 0) ? static_cast(maxColumns) : static_cast(itemList.size()); - - // Calculate the extra space required for overlaps between columns: - // - Each overlap reduces the effective width of subsequent items. - const int extra_space_for_overlaps = overlapOffsetWidth * (numColumns - 1); - - // Total width: - // - The first item's width is fully included (maxItemWidth). - // - Add the space for all overlaps between adjacent items. - totalWidth = maxItemWidth + extra_space_for_overlaps; - - // Determine the number of rows: - // - Use maxRows if maxColumns is set (to constrain the number of rows). - // - Otherwise, assume a single row. - const int numRows = - (maxColumns > 0) ? qCeil(static_cast(itemList.size()) / static_cast(numColumns)) : 1; - - // Total height: - // - Multiply the number of rows by the item height (maxItemHeight). - // - Subtract the height overlap between rows to avoid double-counting. - totalHeight = maxItemHeight * numRows - (numRows - 1) * overlapOffsetHeight; - } else if (direction == Qt::Vertical) { - // Determine the number of columns: - // - Use maxRows to calculate how many columns are needed if a row constraint exists. - // - Otherwise, assume a single column. - const int numColumns = - (maxRows != 0) ? qCeil(static_cast(itemList.size()) / static_cast(maxRows)) : 1; - - // Total width: - // - Multiply the number of columns by the item width (maxItemWidth). - // - Subtract the width overlap between columns to avoid double-counting. - totalWidth = maxItemWidth * numColumns - (numColumns - 1) * overlapOffsetWidth; - - // Determine the number of rows: - // - Use maxRows if it is greater than 0 (constraint set by the user). - // - Otherwise, set the number of rows to the total number of items in the layout. - const int numRows = (maxRows > 0) ? static_cast(maxRows) : static_cast(itemList.size()); - - // Calculate the extra space required for overlaps between rows: - // - Each overlap reduces the effective height of subsequent items. - const int extraSpaceForOverlaps = overlapOffsetHeight * (numRows - 1); - - // Total height: - // - The first item's height is fully included (maxItemHeight). - // - Add the space for all overlaps between adjacent items. - totalHeight = maxItemHeight + extraSpaceForOverlaps; + // Determine the number of columns based on layout constraints and available space. + int columns; + if (flowDirection == Qt::Horizontal) { + if (maxColumns > 0) { + // Calculate the maximum possible columns given the available width and overlap. + const int availableColumns = (availableWidth + overlapOffsetWidth) / (maxItemWidth - overlapOffsetWidth); + // Use the smaller of maxColumns and availableColumns. + qCDebug(OverlapLayoutLog) << " Max Columns " << maxColumns << " available columns " << availableColumns; + columns = qMin(maxColumns, availableColumns); + if (columns < 1) { + columns = 1; + } + } else { + // If no maxColumns constraint, allow as many columns as possible. + columns = INT_MAX; + } + } else { + // If not a horizontal layout, column count is irrelevant. + columns = INT_MAX; } - return {totalWidth, totalHeight}; + // Determine the number of rows based on layout constraints and available space. + int rows; + if (flowDirection == Qt::Vertical) { + if (maxRows > 0) { + // Calculate the maximum possible rows given the available height and overlap. + const int availableRows = (availableHeight + overlapOffsetHeight) / (maxItemHeight - overlapOffsetHeight); + // Use the smaller of maxRows and availableRows. + qCDebug(OverlapLayoutLog) << " Max Rows " << maxRows << " available rows " << availableRows; + rows = qMin(maxRows, availableRows); + if (rows < 1) { + rows = 1; + } + } else { + // If no maxRows constraint, allow as many rows as possible. + rows = INT_MAX; + } + } else { + // If not a vertical layout, row count is irrelevant. + rows = INT_MAX; + } + + if (overlapDirection == Qt::Horizontal) { + return QSize(maxItemWidth + ((qCeil(itemList.size() / rows)) * (maxItemWidth - overlapOffsetWidth)), + rows * maxItemHeight); + } else { + return QSize(columns * maxItemWidth, + maxItemHeight + ((qCeil(itemList.size() / columns)) * (maxItemHeight - overlapOffsetHeight))); + } } /** @@ -341,7 +350,7 @@ QSize OverlapLayout::minimumSize() const */ void OverlapLayout::setDirection(const Qt::Orientation _direction) { - direction = _direction; + overlapDirection = _direction; } /** @@ -378,7 +387,7 @@ void OverlapLayout::setMaxRows(const int _maxRows) */ int OverlapLayout::calculateMaxColumns() const { - if (direction != Qt::Vertical || itemList.isEmpty()) { + if (overlapDirection != Qt::Vertical || itemList.isEmpty()) { return 1; // Only relevant if the layout direction is vertical } @@ -410,7 +419,7 @@ int OverlapLayout::calculateMaxColumns() const */ int OverlapLayout::calculateRowsForColumns(const int columns) const { - if (direction != Qt::Vertical || itemList.isEmpty() || columns <= 0) { + if (overlapDirection != Qt::Vertical || itemList.isEmpty() || columns <= 0) { return 1; // Only relevant if the layout direction is vertical and there are items } @@ -429,7 +438,7 @@ int OverlapLayout::calculateRowsForColumns(const int columns) const */ int OverlapLayout::calculateMaxRows() const { - if (direction != Qt::Horizontal || itemList.isEmpty()) { + if (overlapDirection != Qt::Horizontal || itemList.isEmpty()) { return 1; // Only relevant if the layout direction is horizontal } @@ -464,7 +473,7 @@ int OverlapLayout::calculateMaxRows() const */ int OverlapLayout::calculateColumnsForRows(const int rows) const { - if (direction != Qt::Horizontal || itemList.isEmpty() || rows <= 0) { + if (overlapDirection != Qt::Horizontal || itemList.isEmpty() || rows <= 0) { return 1; // Only relevant if the layout direction is horizontal and there are items } diff --git a/cockatrice/src/client/ui/layouts/overlap_layout.h b/cockatrice/src/client/ui/layouts/overlap_layout.h index 1975cce89..d2cfca32a 100644 --- a/cockatrice/src/client/ui/layouts/overlap_layout.h +++ b/cockatrice/src/client/ui/layouts/overlap_layout.h @@ -3,8 +3,11 @@ #include #include +#include #include +inline Q_LOGGING_CATEGORY(OverlapLayoutLog, "overlap_layout"); + class OverlapLayout : public QLayout { public: @@ -12,7 +15,8 @@ public: int overlapPercentage = 10, int maxColumns = 2, int maxRows = 2, - Qt::Orientation direction = Qt::Horizontal); + Qt::Orientation overlapDirection = Qt::Vertical, + Qt::Orientation flowDirection = Qt::Horizontal); ~OverlapLayout(); void addItem(QLayoutItem *item) override; @@ -35,7 +39,8 @@ private: int overlapPercentage; int maxColumns; int maxRows; - Qt::Orientation direction; + Qt::Orientation overlapDirection; + Qt::Orientation flowDirection; // Calculate the preferred size of the layout QSize calculatePreferredSize() const; diff --git a/cockatrice/src/client/ui/line_edit_completer.cpp b/cockatrice/src/client/ui/line_edit_completer.cpp index 31e82248d..7820ad91b 100644 --- a/cockatrice/src/client/ui/line_edit_completer.cpp +++ b/cockatrice/src/client/ui/line_edit_completer.cpp @@ -117,7 +117,7 @@ void LineEditCompleter::setCompleter(QCompleter *completer) { c = completer; c->setWidget(this); - connect(c, SIGNAL(activated(QString)), this, SLOT(insertCompletion(QString))); + connect(c, qOverload(&QCompleter::activated), this, &LineEditCompleter::insertCompletion); } void LineEditCompleter::setCompletionList(QStringList completionList) diff --git a/cockatrice/src/client/ui/phases_toolbar.cpp b/cockatrice/src/client/ui/phases_toolbar.cpp index 036a7c19e..7c3bede16 100644 --- a/cockatrice/src/client/ui/phases_toolbar.cpp +++ b/cockatrice/src/client/ui/phases_toolbar.cpp @@ -18,7 +18,7 @@ PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_ { if (highlightable) { activeAnimationTimer = new QTimer(this); - connect(activeAnimationTimer, SIGNAL(timeout()), this, SLOT(updateAnimation())); + connect(activeAnimationTimer, &QTimer::timeout, this, &PhaseButton::updateAnimation); activeAnimationTimer->setSingleShot(false); } else activeAnimationCounter = 9; @@ -106,9 +106,9 @@ PhasesToolbar::PhasesToolbar(QGraphicsItem *parent) : QGraphicsItem(parent), width(100), height(100), ySpacing(1), symbolSize(8) { auto *aUntapAll = new QAction(this); - connect(aUntapAll, SIGNAL(triggered()), this, SLOT(actUntapAll())); + connect(aUntapAll, &QAction::triggered, this, &PhasesToolbar::actUntapAll); auto *aDrawCard = new QAction(this); - connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard())); + connect(aDrawCard, &QAction::triggered, this, &PhasesToolbar::actDrawCard); PhaseButton *untapButton = new PhaseButton("untap", this, aUntapAll); PhaseButton *upkeepButton = new PhaseButton("upkeep", this); @@ -126,10 +126,10 @@ PhasesToolbar::PhasesToolbar(QGraphicsItem *parent) << combatBlockersButton << combatDamageButton << combatEndButton << main2Button << cleanupButton; for (auto &i : buttonList) - connect(i, SIGNAL(clicked()), this, SLOT(phaseButtonClicked())); + connect(i, &PhaseButton::clicked, this, &PhasesToolbar::phaseButtonClicked); nextTurnButton = new PhaseButton("nextturn", this, nullptr, false); - connect(nextTurnButton, SIGNAL(clicked()), this, SLOT(actNextTurn())); + connect(nextTurnButton, &PhaseButton::clicked, this, &PhasesToolbar::actNextTurn); rearrangeButtons(); diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader.cpp b/cockatrice/src/client/ui/picture_loader/picture_loader.cpp index eafeb9467..61ea8d0ad 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_loader.cpp +++ b/cockatrice/src/client/ui/picture_loader/picture_loader.cpp @@ -23,11 +23,10 @@ PictureLoader::PictureLoader() : QObject(nullptr) { worker = new PictureLoaderWorker; - connect(&SettingsCache::instance(), SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); - connect(&SettingsCache::instance(), SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged())); + connect(&SettingsCache::instance(), &SettingsCache::picsPathChanged, this, &PictureLoader::picsPathChanged); + connect(&SettingsCache::instance(), &SettingsCache::picDownloadChanged, this, &PictureLoader::picDownloadChanged); - connect(worker, SIGNAL(imageLoaded(CardInfoPtr, const QImage &)), this, - SLOT(imageLoaded(CardInfoPtr, const QImage &))); + connect(worker, &PictureLoaderWorker::imageLoaded, this, &PictureLoader::imageLoaded); } PictureLoader::~PictureLoader() diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader.h b/cockatrice/src/client/ui/picture_loader/picture_loader.h index 68a9424e4..e2769f567 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_loader.h +++ b/cockatrice/src/client/ui/picture_loader/picture_loader.h @@ -1,7 +1,7 @@ #ifndef PICTURELOADER_H #define PICTURELOADER_H -#include "../../../game/cards/card_database.h" +#include "../../../game/cards/card_info.h" #include "picture_loader_worker.h" #include diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp index a1cfa53be..fce81fe6a 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp @@ -19,9 +19,11 @@ PictureLoaderWorker::PictureLoaderWorker() picDownload(SettingsCache::instance().getPicDownload()), downloadRunning(false), loadQueueRunning(false), overrideAllCardArtWithPersonalPreference(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection); - connect(&SettingsCache::instance(), SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); - connect(&SettingsCache::instance(), SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged())); + connect(this, &PictureLoaderWorker::startLoadQueue, this, &PictureLoaderWorker::processLoadQueue, + Qt::QueuedConnection); + connect(&SettingsCache::instance(), &SettingsCache::picsPathChanged, this, &PictureLoaderWorker::picsPathChanged); + connect(&SettingsCache::instance(), &SettingsCache::picDownloadChanged, this, + &PictureLoaderWorker::picDownloadChanged); connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, &PictureLoaderWorker::setOverrideAllCardArtWithPersonalPreference); @@ -43,7 +45,7 @@ PictureLoaderWorker::PictureLoaderWorker() // Use a ManualRedirectPolicy since we keep track of redirects in picDownloadFinished // We can't use NoLessSafeRedirectPolicy because it is not applied with AlwaysCache networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); - connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *))); + connect(networkManager, &QNetworkAccessManager::finished, this, &PictureLoaderWorker::picDownloadFinished); cacheFilePath = SettingsCache::instance().getRedirectCachePath() + REDIRECT_CACHE_FILENAME; loadRedirectCache(); @@ -244,6 +246,23 @@ QNetworkReply *PictureLoaderWorker::makeRequest(const QUrl &url) QNetworkRequest req(url); + // QNetworkDiskCache leaks file descriptors when downloading compressed + // files, see https://bugreports.qt.io/browse/QTBUG-135641 + // + // We can set the Accept-Encoding header manually to disable Qt's automatic + // response decompression, but then we would have to deal with decompression + // ourselves. + // + // Since we are dowloading images that are usually stored in a + // compressed format (e.g. jpeg or webp), it's not clear compression at the + // HTTP level helps; in fact, images are typically returned without + // compression. Redirects, on the other hand, are compressed and cause file + // descriptor leaks -- but since redirects have no payload, we don't really + // care either. + // + // In the end, just do the simple thing and disable HTTP compression. + req.setRawHeader("accept-encoding", "identity"); + if (!picDownload) { req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); } diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h index e85ede8f1..47d2cca5a 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h @@ -1,7 +1,7 @@ #ifndef PICTURE_LOADER_WORKER_H #define PICTURE_LOADER_WORKER_H -#include "../../../game/cards/card_database.h" +#include "../../../game/cards/card_info.h" #include "picture_to_load.h" #include diff --git a/cockatrice/src/client/ui/picture_loader/picture_to_load.h b/cockatrice/src/client/ui/picture_loader/picture_to_load.h index 96a114cd4..9d1a784f0 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_to_load.h +++ b/cockatrice/src/client/ui/picture_loader/picture_to_load.h @@ -1,7 +1,7 @@ #ifndef PICTURE_TO_LOAD_H #define PICTURE_TO_LOAD_H -#include "../../../game/cards/card_database.h" +#include "../../../game/cards/card_info.h" #include diff --git a/cockatrice/src/client/ui/theme_manager.cpp b/cockatrice/src/client/ui/theme_manager.cpp index 0cff534a2..78d1344a1 100644 --- a/cockatrice/src/client/ui/theme_manager.cpp +++ b/cockatrice/src/client/ui/theme_manager.cpp @@ -24,7 +24,7 @@ static const QStringList DEFAULT_RESOURCE_PATHS = {":/resources"}; ThemeManager::ThemeManager(QObject *parent) : QObject(parent) { ensureThemeDirectoryExists(); - connect(&SettingsCache::instance(), SIGNAL(themeChanged()), this, SLOT(themeChangedSlot())); + connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &ThemeManager::themeChangedSlot); themeChangedSlot(); } diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h index 8518aa1a5..9b8fef71c 100644 --- a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h @@ -1,7 +1,7 @@ #ifndef COLOR_IDENTITY_WIDGET_H #define COLOR_IDENTITY_WIDGET_H -#include "../../../../../game/cards/card_database.h" +#include "../../../../../game/cards/card_info.h" #include #include diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.h b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.h index 0872183f3..c9ed221be 100644 --- a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.h @@ -1,7 +1,7 @@ #ifndef MANA_COST_WIDGET_H #define MANA_COST_WIDGET_H -#include "../../../../../game/cards/card_database.h" +#include "../../../../../game/cards/card_info.h" #include #include diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp new file mode 100644 index 000000000..71569e92a --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -0,0 +1,81 @@ +#include "card_group_display_widget.h" + +#include "../../../../../deck/deck_list_model.h" +#include "../../../../../game/cards/card_database_manager.h" +#include "../../../../../utility/card_info_comparator.h" +#include "../card_info_picture_with_text_overlay_widget.h" + +#include + +CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, + DeckListModel *_deckListModel, + QString _zoneName, + QString _cardGroupCategory, + QString _activeGroupCriteria, + QStringList _activeSortCriteria, + int bannerOpacity, + CardSizeWidget *_cardSizeWidget) + : QWidget(parent), deckListModel(_deckListModel), zoneName(_zoneName), cardGroupCategory(_cardGroupCategory), + activeGroupCriteria(_activeGroupCriteria), activeSortCriteria(_activeSortCriteria), + cardSizeWidget(_cardSizeWidget) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + setMinimumSize(QSize(0, 0)); + + banner = new BannerWidget(this, cardGroupCategory, Qt::Orientation::Vertical, bannerOpacity); + + layout->addWidget(banner); + updateCardDisplays(); +} + +void CardGroupDisplayWidget::updateCardDisplays() +{ +} + +QList CardGroupDisplayWidget::getCardsMatchingGroup(QList cardsToSort) +{ + cardsToSort = sortCardList(cardsToSort, activeSortCriteria, Qt::SortOrder::AscendingOrder); + + QList activeList; + for (const CardInfoPtr &info : cardsToSort) { + if (info && info->getProperty(activeGroupCriteria) == cardGroupCategory) { + activeList.append(info); + } + } + + return activeList; +} + +QList CardGroupDisplayWidget::sortCardList(QList cardsToSort, + const QStringList properties, + Qt::SortOrder order = Qt::AscendingOrder) +{ + CardInfoComparator comparator(properties, order); + std::sort(cardsToSort.begin(), cardsToSort.end(), comparator); + + return cardsToSort; +} + +void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria) +{ + if (activeSortCriteria != _activeSortCriteria) { + activeSortCriteria = _activeSortCriteria; + updateCardDisplays(); // Refresh display with new sorting + } +} + +void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) +{ + emit cardClicked(event, card); +} + +void CardGroupDisplayWidget::onHover(CardInfoPtr card) +{ + emit cardHovered(card); +} + +void CardGroupDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.h b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.h new file mode 100644 index 000000000..079c5e3ea --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.h @@ -0,0 +1,53 @@ +#ifndef CARD_GROUP_DISPLAY_WIDGET_H +#define CARD_GROUP_DISPLAY_WIDGET_H + +#include "../../../../../deck/deck_list_model.h" +#include "../../../../../game/cards/card_info.h" +#include "../../general/display/banner_widget.h" +#include "../card_info_picture_with_text_overlay_widget.h" +#include "../card_size_widget.h" + +#include +#include +#include + +class CardGroupDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + CardGroupDisplayWidget(QWidget *parent, + DeckListModel *deckListModel, + QString zoneName, + QString cardGroupCategory, + QString activeGroupCriteria, + QStringList activeSortCriteria, + int bannerOpacity, + CardSizeWidget *cardSizeWidget); + + QList getCardsMatchingGroup(QList cardsToSort); + void resizeEvent(QResizeEvent *event) override; + + DeckListModel *deckListModel; + QString zoneName; + QString cardGroupCategory; + QString activeGroupCriteria; + QStringList activeSortCriteria; + CardSizeWidget *cardSizeWidget; + +public slots: + QList sortCardList(QList cardsToSort, QStringList properties, Qt::SortOrder order); + void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); + void onHover(CardInfoPtr card); + virtual void updateCardDisplays(); + void onActiveSortCriteriaChanged(QStringList activeSortCriteria); + +signals: + void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); + void cardHovered(CardInfoPtr card); + +protected: + QVBoxLayout *layout; + BannerWidget *banner; +}; +#endif // CARD_GROUP_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp new file mode 100644 index 000000000..62eeb0134 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp @@ -0,0 +1,107 @@ +#include "flat_card_group_display_widget.h" + +#include "../../../../../deck/deck_list_model.h" +#include "../../../../../game/cards/card_database_manager.h" +#include "../../../../../utility/card_info_comparator.h" +#include "../card_info_picture_with_text_overlay_widget.h" + +#include + +FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent, + DeckListModel *_deckListModel, + QString _zoneName, + QString _cardGroupCategory, + QString _activeGroupCriteria, + QStringList _activeSortCriteria, + int bannerOpacity, + CardSizeWidget *_cardSizeWidget) + : CardGroupDisplayWidget(parent, + _deckListModel, + _zoneName, + _cardGroupCategory, + _activeGroupCriteria, + _activeSortCriteria, + bannerOpacity, + _cardSizeWidget) +{ + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + banner->setBuddy(flowWidget); + + layout->addWidget(flowWidget); + FlatCardGroupDisplayWidget::updateCardDisplays(); + connect(deckListModel, &DeckListModel::dataChanged, this, &FlatCardGroupDisplayWidget::updateCardDisplays); +} + +void FlatCardGroupDisplayWidget::updateCardDisplays() +{ + // Retrieve and sort cards + QList cardsInZone = getCardsMatchingGroup(deckListModel->getCardsAsCardInfoPtrsForZone(zoneName)); + + // Show or hide widget + bool shouldBeVisible = !cardsInZone.isEmpty(); + if (shouldBeVisible != isVisible()) { + setVisible(shouldBeVisible); + } + + // Retrieve existing widgets + QList existingWidgets = + flowWidget->findChildren(); + + QHash> widgetMap; + for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) { + widgetMap[widget->getInfo()->getName()].append(widget); + } + + QList sortedWidgets; + QSet usedWidgets; + + // Ensure widgets are ordered to match the sorted cards + for (const CardInfoPtr &card : cardsInZone) { + QString name = card->getName(); + CardInfoPictureWithTextOverlayWidget *widget = nullptr; + + if (!widgetMap[name].isEmpty()) { + // Reuse an existing widget + widget = widgetMap[name].takeFirst(); + } else { + // Create a new widget if needed + widget = new CardInfoPictureWithTextOverlayWidget(flowWidget, true); + widget->setScaleFactor(cardSizeWidget->getSlider()->value()); + widget->setCard(card); + + connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, + &FlatCardGroupDisplayWidget::onClick); + connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, + &FlatCardGroupDisplayWidget::onHover); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, + &CardInfoPictureWidget::setScaleFactor); + + flowWidget->addWidget(widget); + } + + // Store in sorted order + sortedWidgets.append(widget); + usedWidgets.insert(widget); + } + + // Remove extra widgets + for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) { + if (!usedWidgets.contains(widget)) { + flowWidget->layout()->removeWidget(widget); + widget->deleteLater(); + } + } + + // **Reorder widgets in place** + for (int i = 0; i < sortedWidgets.size(); ++i) { + sortedWidgets[i]->setParent(nullptr); // Temporarily detach + } + for (int i = 0; i < sortedWidgets.size(); ++i) { + flowWidget->addWidget(sortedWidgets[i]); // Reattach in correct order + } +} + +void FlatCardGroupDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h new file mode 100644 index 000000000..45a75907e --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h @@ -0,0 +1,30 @@ +#ifndef FLAT_CARD_GROUP_DISPLAY_WIDGET_H +#define FLAT_CARD_GROUP_DISPLAY_WIDGET_H + +#include "../../general/layout_containers/flow_widget.h" +#include "card_group_display_widget.h" + +class FlatCardGroupDisplayWidget : public CardGroupDisplayWidget +{ + Q_OBJECT + +public: + FlatCardGroupDisplayWidget(QWidget *parent, + DeckListModel *deckListModel, + QString zoneName, + QString cardGroupCategory, + QString activeGroupCriteria, + QStringList activeSortCriteria, + int bannerOpacity, + CardSizeWidget *cardSizeWidget); + + void resizeEvent(QResizeEvent *event) override; + +public slots: + void updateCardDisplays() override; + +private: + FlowWidget *flowWidget; +}; + +#endif // FLAT_CARD_GROUP_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp new file mode 100644 index 000000000..0e4a88cf3 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp @@ -0,0 +1,122 @@ +#include "overlapped_card_group_display_widget.h" + +#include "../../../../../deck/deck_list_model.h" +#include "../../../../../game/cards/card_database_manager.h" +#include "../../../../../utility/card_info_comparator.h" +#include "../card_info_picture_with_text_overlay_widget.h" + +#include + +OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *parent, + DeckListModel *_deckListModel, + QString _zoneName, + QString _cardGroupCategory, + QString _activeGroupCriteria, + QStringList _activeSortCriteria, + int bannerOpacity, + CardSizeWidget *_cardSizeWidget) + : CardGroupDisplayWidget(parent, + _deckListModel, + _zoneName, + _cardGroupCategory, + _activeGroupCriteria, + _activeSortCriteria, + bannerOpacity, + _cardSizeWidget) +{ + overlapWidget = new OverlapWidget(this, 80, 1, 1, Qt::Vertical, true); + banner->setBuddy(overlapWidget); + + layout->addWidget(overlapWidget); + OverlappedCardGroupDisplayWidget::updateCardDisplays(); + connect(deckListModel, &DeckListModel::dataChanged, this, &OverlappedCardGroupDisplayWidget::updateCardDisplays); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, this, + [this]() { overlapWidget->adjustMaxColumnsAndRows(); }); +} + +void OverlappedCardGroupDisplayWidget::updateCardDisplays() +{ + overlapWidget->setUpdatesEnabled(false); + // Retrieve and sort cards + QList cardsInZone = getCardsMatchingGroup(deckListModel->getCardsAsCardInfoPtrsForZone(zoneName)); + + // Show or hide widget + bool shouldBeVisible = !cardsInZone.isEmpty(); + if (shouldBeVisible != isVisible()) { + setVisible(shouldBeVisible); + } + + // Retrieve existing widgets + QList existingWidgets = + overlapWidget->findChildren(); + + QHash> widgetMap; + for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) { + widgetMap[widget->getInfo()->getName()].append(widget); + } + + QList sortedWidgets; + QSet usedWidgets; + + // Ensure widgets are ordered to match the sorted cards + for (const CardInfoPtr &card : cardsInZone) { + QString name = card->getName(); + CardInfoPictureWithTextOverlayWidget *widget = nullptr; + + if (!widgetMap[name].isEmpty()) { + // Reuse an existing widget + widget = widgetMap[name].takeFirst(); + } else { + // Create a new widget if needed + widget = new CardInfoPictureWithTextOverlayWidget(overlapWidget, true); + widget->setScaleFactor(cardSizeWidget->getSlider()->value()); + widget->setCard(card); + + connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, + &OverlappedCardGroupDisplayWidget::onClick); + connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, + &OverlappedCardGroupDisplayWidget::onHover); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, + &CardInfoPictureWidget::setScaleFactor); + + overlapWidget->addWidget(widget); + } + + // Store in sorted order + sortedWidgets.append(widget); + usedWidgets.insert(widget); + } + + // Remove extra widgets + for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) { + if (!usedWidgets.contains(widget)) { + overlapWidget->layout()->removeWidget(widget); + widget->deleteLater(); + } + } + + // **Reorder widgets in place** + for (int i = 0; i < sortedWidgets.size(); ++i) { + sortedWidgets[i]->setParent(nullptr); // Temporarily detach + } + for (int i = 0; i < sortedWidgets.size(); ++i) { + overlapWidget->addWidget(sortedWidgets[i]); // Reattach in correct order + } + + // Ensure proper layering + for (CardInfoPictureWithTextOverlayWidget *widget : sortedWidgets) { + widget->raise(); + } + + overlapWidget->adjustMaxColumnsAndRows(); + overlapWidget->setUpdatesEnabled(true); + overlapWidget->update(); +} + +void OverlappedCardGroupDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + overlapWidget->resize(event->size()); + overlapWidget->adjustMaxColumnsAndRows(); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h new file mode 100644 index 000000000..e492316cd --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h @@ -0,0 +1,30 @@ +#ifndef OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H +#define OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H + +#include "../../general/layout_containers/overlap_widget.h" +#include "card_group_display_widget.h" + +class OverlappedCardGroupDisplayWidget : public CardGroupDisplayWidget +{ + Q_OBJECT + +public: + OverlappedCardGroupDisplayWidget(QWidget *parent, + DeckListModel *deckListModel, + QString zoneName, + QString cardGroupCategory, + QString activeGroupCriteria, + QStringList activeSortCriteria, + int bannerOpacity, + CardSizeWidget *cardSizeWidget); + + void resizeEvent(QResizeEvent *event) override; + +public slots: + void updateCardDisplays() override; + +private: + OverlapWidget *overlapWidget; +}; + +#endif // OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp index 780ef53fe..d967c8477 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp @@ -22,7 +22,7 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const QString &cardName, pic->setObjectName("pic"); text = new CardInfoTextWidget(); text->setObjectName("text"); - connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &))); + connect(text, &CardInfoTextWidget::linkActivated, this, [this](const QString &card) { setCard(card); }); auto *layout = new QVBoxLayout(); layout->setObjectName("layout"); @@ -52,7 +52,7 @@ void CardInfoDisplayWidget::setCard(CardInfoPtr card) disconnect(info.data(), nullptr, this, nullptr); info = std::move(card); if (info) - connect(info.data(), SIGNAL(destroyed()), this, SLOT(clear())); + connect(info.data(), &QObject::destroyed, this, &CardInfoDisplayWidget::clear); text->setCard(info); pic->setCard(info); diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.h index 46d42b2bc..29ad703e7 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.h @@ -1,7 +1,7 @@ #ifndef CARDINFOWIDGET_H #define CARDINFOWIDGET_H -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include #include diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp index 714ad20ca..4d85f38a0 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp @@ -21,7 +21,7 @@ CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *paren text = new CardInfoTextWidget(); text->setObjectName("text"); - connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &))); + connect(text, &CardInfoTextWidget::linkActivated, this, qOverload(&CardInfoFrameWidget::setCard)); tab1 = new QWidget(this); tab2 = new QWidget(this); @@ -34,7 +34,7 @@ CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *paren insertTab(ImageOnlyView, tab1, QString()); insertTab(TextOnlyView, tab2, QString()); insertTab(ImageAndTextView, tab3, QString()); - connect(this, SIGNAL(currentChanged(int)), this, SLOT(setViewMode(int))); + connect(this, &CardInfoFrameWidget::currentChanged, this, &CardInfoFrameWidget::setViewMode); tab1Layout = new QVBoxLayout(); tab1Layout->setObjectName("tab1Layout"); @@ -61,6 +61,7 @@ CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *paren setViewMode(SettingsCache::instance().getCardInfoViewMode()); + // TODO: Change this to be by UUID setCard(CardDatabaseManager::getInstance()->getCard(cardName)); } @@ -154,7 +155,7 @@ void CardInfoFrameWidget::setCard(CardInfoPtr card) info = std::move(card); if (info) { - connect(info.data(), SIGNAL(destroyed()), this, SLOT(clearCard())); + connect(info.data(), &QObject::destroyed, this, &CardInfoFrameWidget::clearCard); } setViewTransformationButtonVisibility(hasTransformation(info)); diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h index 2b6012f1f..40a3b454a 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h @@ -1,7 +1,7 @@ #ifndef CARDFRAME_H #define CARDFRAME_H -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include #include diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.h index 73b0a9cc5..d65b0da69 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.h @@ -1,7 +1,7 @@ #ifndef CARD_PICTURE_ENLARGED_WIDGET_H #define CARD_PICTURE_ENLARGED_WIDGET_H -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include #include diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp index f4f699171..4d76989b1 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -29,21 +30,33 @@ * * Initializes the widget with a minimum height and sets the pixmap to a dirty state for initial loading. */ -CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool hoverToZoomEnabled) - : QWidget(parent), info(nullptr), pixmapDirty(true), hoverToZoomEnabled(hoverToZoomEnabled) +CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverToZoomEnabled, const bool _raiseOnEnter) + : QWidget(parent), info(nullptr), pixmapDirty(true), hoverToZoomEnabled(_hoverToZoomEnabled), + raiseOnEnter(_raiseOnEnter) { setMinimumHeight(baseHeight); if (hoverToZoomEnabled) { setMouseTracking(true); } - enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(this); + enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(this->window()); enlargedPixmapWidget->hide(); hoverTimer = new QTimer(this); hoverTimer->setSingleShot(true); connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap); + // Store the widget's original position + originalPos = this->pos(); + + // Create the animation + animation = new QPropertyAnimation(this, "pos"); + animation->setDuration(200); // 200ms animation duration + animation->setEasingCurve(QEasingCurve::OutQuad); + + animation->setStartValue(originalPos); + animation->setEndValue(originalPos - QPoint(0, animationOffset)); + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { Q_UNUSED(_roundCardCorners); @@ -67,7 +80,7 @@ void CardInfoPictureWidget::setCard(CardInfoPtr card) info = std::move(card); if (info) { - connect(info.data(), SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap())); + connect(info.data(), &CardInfo::pixmapUpdated, this, &CardInfoPictureWidget::updatePixmap); } updatePixmap(); @@ -83,6 +96,11 @@ void CardInfoPictureWidget::setHoverToZoomEnabled(const bool enabled) setMouseTracking(enabled); } +void CardInfoPictureWidget::setRaiseOnEnterEnabled(const bool enabled) +{ + raiseOnEnter = enabled; +} + /** * @brief Handles widget resizing by updating the pixmap size. * @param event The resize event (unused). @@ -92,6 +110,7 @@ void CardInfoPictureWidget::setHoverToZoomEnabled(const bool enabled) void CardInfoPictureWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); + originalPos = pos(); // Update the baseline position updatePixmap(); } @@ -210,7 +229,8 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event) */ QSize CardInfoPictureWidget::sizeHint() const { - return {static_cast(baseWidth * scaleFactor), static_cast(baseHeight * scaleFactor)}; + return {static_cast(baseWidth * scaleFactor / 100.0), + static_cast(baseWidth * scaleFactor / 100.0 * aspectRatio)}; } /** @@ -232,6 +252,18 @@ void CardInfoPictureWidget::enterEvent(QEvent *event) // Emit signal indicating a card is being hovered on emit hoveredOnCard(info); + + if (raiseOnEnter) { + if (animation->state() == QAbstractAnimation::Running) { + animation->pause(); // Pause current animation + } else { + originalPos = this->pos(); // Update the baseline position + animation->setStartValue(originalPos); + animation->setEndValue(originalPos - QPoint(0, animationOffset)); + } + animation->setDirection(QAbstractAnimation::Forward); + animation->start(); + } } /** @@ -241,10 +273,32 @@ void CardInfoPictureWidget::enterEvent(QEvent *event) void CardInfoPictureWidget::leaveEvent(QEvent *event) { QWidget::leaveEvent(event); + if (hoverToZoomEnabled) { hoverTimer->stop(); enlargedPixmapWidget->hide(); } + + if (raiseOnEnter) { + if (animation->state() == QAbstractAnimation::Running) { + animation->pause(); // Pause current animation + } + animation->setDirection(QAbstractAnimation::Backward); + animation->start(); + } +} + +void CardInfoPictureWidget::moveEvent(QMoveEvent *event) +{ + QWidget::moveEvent(event); + + hoverTimer->stop(); + enlargedPixmapWidget->hide(); + + if (animation->state() == QAbstractAnimation::Running) { + return; + } + originalPos = this->pos(); // Update the baseline position } /** @@ -254,10 +308,24 @@ void CardInfoPictureWidget::leaveEvent(QEvent *event) void CardInfoPictureWidget::mouseMoveEvent(QMouseEvent *event) { QWidget::mouseMoveEvent(event); + if (hoverToZoomEnabled && enlargedPixmapWidget->isVisible()) { - const QPointF cursorPos = QCursor::pos(); - enlargedPixmapWidget->move(QPoint(static_cast(cursorPos.x()) + enlargedPixmapOffset, - static_cast(cursorPos.y()) + enlargedPixmapOffset)); + const QPoint cursorPos = QCursor::pos(); + const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry(); + const QSize widgetSize = enlargedPixmapWidget->size(); + + 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; + } + if (newY + widgetSize.height() > screenGeometry.bottom()) { + newY = cursorPos.y() - widgetSize.height() - enlargedPixmapOffset; + } + + enlargedPixmapWidget->move(newX, newY); } } @@ -266,7 +334,11 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event) QWidget::mousePressEvent(event); if (event->button() == Qt::RightButton) { createRightClickMenu()->popup(QCursor::pos()); + } else { + emit cardClicked(); } + + emit cardClicked(); } QMenu *CardInfoPictureWidget::createRightClickMenu() @@ -370,13 +442,25 @@ void CardInfoPictureWidget::showEnlargedPixmap() const return; } - const QSize enlargedSize(static_cast(size().width() * scaleFactor), - static_cast(size().width() * aspectRatio * scaleFactor)); + const QSize enlargedSize(static_cast(size().width() * 2), static_cast(size().width() * aspectRatio * 2)); enlargedPixmapWidget->setCardPixmap(info, enlargedSize); - const QPointF cursorPos = QCursor::pos(); - enlargedPixmapWidget->move(static_cast(cursorPos.x()) + enlargedPixmapOffset, - static_cast(cursorPos.y()) + enlargedPixmapOffset); + const QPoint cursorPos = QCursor::pos(); + const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry(); + const QSize widgetSize = enlargedPixmapWidget->size(); + + 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; + } + if (newY + widgetSize.height() > screenGeometry.bottom()) { + newY = cursorPos.y() - widgetSize.height() - enlargedPixmapOffset; + } + + enlargedPixmapWidget->move(newX, newY); enlargedPixmapWidget->show(); } diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h index a74028a7f..10cc422aa 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h @@ -1,9 +1,10 @@ #ifndef CARD_INFO_PICTURE_H #define CARD_INFO_PICTURE_H -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include "card_info_picture_enlarged_widget.h" +#include #include #include @@ -17,23 +18,27 @@ class CardInfoPictureWidget : public QWidget Q_OBJECT public: - explicit CardInfoPictureWidget(QWidget *parent = nullptr, bool hoverToZoomEnabled = false); + explicit CardInfoPictureWidget(QWidget *parent = nullptr, + bool hoverToZoomEnabled = false, + bool raiseOnEnter = false); CardInfoPtr getInfo() { return info; } [[nodiscard]] QSize sizeHint() const override; - void setHoverToZoomEnabled(bool enabled); public slots: void setCard(CardInfoPtr card); void setScaleFactor(int scale); // New slot for scaling + void setHoverToZoomEnabled(bool enabled); + void setRaiseOnEnterEnabled(bool enabled); void updatePixmap(); signals: void hoveredOnCard(CardInfoPtr hoveredCard); void cardScaleFactorChanged(int _scale); void cardChanged(CardInfoPtr card); + void cardClicked(); protected: void resizeEvent(QResizeEvent *event) override; @@ -44,6 +49,7 @@ protected: void enterEvent(QEvent *event) override; // Qt5 signature #endif void leaveEvent(QEvent *event) override; + void moveEvent(QMoveEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void loadPixmap(); @@ -60,14 +66,18 @@ private: qreal aspectRatio = magicTheGatheringCardAspectRatio; int baseWidth = 200; int baseHeight = 200; - double scaleFactor = 1.5; + double scaleFactor = 100; QPixmap resizedPixmap; bool pixmapDirty; bool hoverToZoomEnabled; + bool raiseOnEnter; int hoverActivateThresholdInMs = 500; CardInfoPictureEnlargedWidget *enlargedPixmapWidget; int enlargedPixmapOffset = 10; QTimer *hoverTimer; + QPropertyAnimation *animation; + QPoint originalPos; // Store the original position + const int animationOffset = 10; // Adjust this for how much the widget moves up QMenu *createRightClickMenu(); QMenu *createViewRelatedCardsMenu(); diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index 255ab4738..1545dc4f6 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -9,6 +9,7 @@ * @brief Constructs a CardPictureWithTextOverlay widget. * @param parent The parent widget. * @param hoverToZoomEnabled If this widget will spawn a larger widget when hovered over. + * @param raiseOnEnter If this widget will raise slightly when entered. * @param textColor The color of the overlay text. * @param outlineColor The color of the outline around the text. * @param fontSize The font size of the overlay text. @@ -18,11 +19,12 @@ */ CardInfoPictureWithTextOverlayWidget::CardInfoPictureWithTextOverlayWidget(QWidget *parent, const bool hoverToZoomEnabled, + const bool raiseOnEnter, const QColor &textColor, const QColor &outlineColor, const int fontSize, const Qt::Alignment alignment) - : CardInfoPictureWidget(parent, hoverToZoomEnabled), textColor(textColor), outlineColor(outlineColor), + : CardInfoPictureWidget(parent, hoverToZoomEnabled, raiseOnEnter), textColor(textColor), outlineColor(outlineColor), fontSize(fontSize), textAlignment(alignment) { this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h index 44107362a..97cee1a8c 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h @@ -14,6 +14,7 @@ class CardInfoPictureWithTextOverlayWidget : public CardInfoPictureWidget public: explicit CardInfoPictureWithTextOverlayWidget(QWidget *parent = nullptr, bool hoverToZoomEnabled = false, + bool raiseOnEnter = false, const QColor &textColor = Qt::white, const QColor &outlineColor = Qt::black, int fontSize = 12, diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_text_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_text_widget.h index 17985021d..e066fdb34 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_text_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_text_widget.h @@ -1,7 +1,7 @@ #ifndef CARDINFOTEXT_H #define CARDINFOTEXT_H -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include class QLabel; diff --git a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp new file mode 100644 index 000000000..b074241fe --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp @@ -0,0 +1,168 @@ +#include "deck_card_zone_display_widget.h" + +#include "../../../../deck/deck_list_model.h" +#include "../../../../utility/card_info_comparator.h" +#include "card_group_display_widgets/flat_card_group_display_widget.h" +#include "card_group_display_widgets/overlapped_card_group_display_widget.h" + +#include + +DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, + DeckListModel *_deckListModel, + QString _zoneName, + QString _activeGroupCriteria, + QStringList _activeSortCriteria, + int bannerOpacity, + int subBannerOpacity, + CardSizeWidget *_cardSizeWidget) + : QWidget(parent), deckListModel(_deckListModel), zoneName(_zoneName), activeGroupCriteria(_activeGroupCriteria), + activeSortCriteria(_activeSortCriteria), bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity), + cardSizeWidget(_cardSizeWidget) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + banner = new BannerWidget(this, zoneName, Qt::Orientation::Vertical, bannerOpacity); + layout->addWidget(banner); + + cardGroupContainer = new QWidget(this); + cardGroupLayout = new QVBoxLayout(cardGroupContainer); + cardGroupContainer->setLayout(cardGroupLayout); + layout->addWidget(cardGroupContainer); + + banner->setBuddy(cardGroupContainer); + + displayCards(); + connect(deckListModel, &DeckListModel::dataChanged, this, &DeckCardZoneDisplayWidget::displayCards); +} +void DeckCardZoneDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + for (QObject *child : layout->children()) { + QWidget *widget = qobject_cast(child); + if (widget) { + widget->setMaximumWidth(width()); + } + } +} +void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) +{ + emit cardClicked(event, card, zoneName); +} +void DeckCardZoneDisplayWidget::onHover(CardInfoPtr card) +{ + emit cardHovered(card); +} + +void DeckCardZoneDisplayWidget::displayCards() +{ + addCardGroupIfItDoesNotExist(); + deleteCardGroupIfItDoesNotExist(); +} + +void DeckCardZoneDisplayWidget::refreshDisplayType(const QString &_displayType) +{ + displayType = _displayType; + QLayoutItem *item; + while ((item = cardGroupLayout->takeAt(0)) != nullptr) { + if (item->widget()) { + item->widget()->deleteLater(); + } else if (item->layout()) { + item->layout()->deleteLater(); + } + delete item; + } + + // We gotta wait for all the deleteLater's to finish so we fire after the next event cycle + + auto timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, &QTimer::timeout, this, [this]() { displayCards(); }); + timer->start(); +} + +void DeckCardZoneDisplayWidget::addCardGroupIfItDoesNotExist() +{ + QList cardGroupsDisplayWidgets = + cardGroupContainer->findChildren(); + + QList cardGroups = getGroupCriteriaValueList(); + + for (QString cardGroup : cardGroups) { + bool found = false; + for (CardGroupDisplayWidget *cardGroupDisplayWidget : cardGroupsDisplayWidgets) { + if (cardGroupDisplayWidget->cardGroupCategory == cardGroup) { + found = true; + } + } + + if (found) { + continue; + } + + if (displayType == "overlap") { + auto *display_widget = new OverlappedCardGroupDisplayWidget( + cardGroupContainer, deckListModel, zoneName, cardGroup, activeGroupCriteria, activeSortCriteria, + subBannerOpacity, cardSizeWidget); + connect(display_widget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this, + SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *))); + connect(display_widget, SIGNAL(cardHovered(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr))); + connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, display_widget, + &CardGroupDisplayWidget::onActiveSortCriteriaChanged); + cardGroupLayout->addWidget(display_widget); + } else if (displayType == "flat") { + auto *display_widget = new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, zoneName, + cardGroup, activeGroupCriteria, activeSortCriteria, + subBannerOpacity, cardSizeWidget); + connect(display_widget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this, + SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *))); + connect(display_widget, SIGNAL(cardHovered(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr))); + connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, display_widget, + &CardGroupDisplayWidget::onActiveSortCriteriaChanged); + cardGroupLayout->addWidget(display_widget); + } + } +} + +void DeckCardZoneDisplayWidget::deleteCardGroupIfItDoesNotExist() +{ + QList cardGroupsDisplayWidgets = + cardGroupContainer->findChildren(); + + QList validGroups = getGroupCriteriaValueList(); + + for (CardGroupDisplayWidget *cardGroupDisplayWidget : cardGroupsDisplayWidgets) { + if (!validGroups.contains(cardGroupDisplayWidget->cardGroupCategory)) { + cardGroupLayout->removeWidget(cardGroupDisplayWidget); + cardGroupDisplayWidget->deleteLater(); // Properly delete the widget after the event loop cycles + } + } +} + +void DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged(QString _activeGroupCriteria) +{ + activeGroupCriteria = _activeGroupCriteria; + displayCards(); +} + +void DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria) +{ + activeSortCriteria = _activeSortCriteria; + emit activeSortCriteriaChanged(activeSortCriteria); +} + +QList DeckCardZoneDisplayWidget::getGroupCriteriaValueList() +{ + QList groupCriteriaValues; + + QList cardsInZone = deckListModel->getCardsAsCardInfoPtrsForZone(zoneName); + + for (CardInfoPtr cardInZone : cardsInZone) { + groupCriteriaValues.append(cardInZone->getProperty(activeGroupCriteria)); + } + + groupCriteriaValues.removeDuplicates(); + groupCriteriaValues.sort(); + + return groupCriteriaValues; +} diff --git a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h new file mode 100644 index 000000000..b80ca4332 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h @@ -0,0 +1,62 @@ +#ifndef DECK_CARD_ZONE_DISPLAY_WIDGET_H +#define DECK_CARD_ZONE_DISPLAY_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../../../../game/cards/card_info.h" +#include "../general/display/banner_widget.h" +#include "../general/layout_containers/overlap_widget.h" +#include "card_info_picture_with_text_overlay_widget.h" +#include "card_size_widget.h" + +#include +#include + +class DeckCardZoneDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + DeckCardZoneDisplayWidget(QWidget *parent, + DeckListModel *deckListModel, + QString zoneName, + QString activeGroupCriteria, + QStringList activeSortCriteria, + int bannerOpacity, + int subBannerOpacity, + CardSizeWidget *_cardSizeWidget); + DeckListModel *deckListModel; + QString zoneName; + void addCardsToOverlapWidget(); + void resizeEvent(QResizeEvent *event) override; + +public slots: + void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); + void onHover(CardInfoPtr card); + void displayCards(); + void refreshDisplayType(const QString &displayType); + void addCardGroupIfItDoesNotExist(); + void deleteCardGroupIfItDoesNotExist(); + void onActiveGroupCriteriaChanged(QString activeGroupCriteria); + void onActiveSortCriteriaChanged(QStringList activeSortCriteria); + QList getGroupCriteriaValueList(); + +signals: + void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card, QString zoneName); + void cardHovered(CardInfoPtr card); + void activeSortCriteriaChanged(QStringList activeSortCriteria); + +private: + QString activeGroupCriteria; + QStringList activeSortCriteria; + int bannerOpacity = 20; + int subBannerOpacity = 10; + CardSizeWidget *cardSizeWidget; + QVBoxLayout *layout; + BannerWidget *banner; + QWidget *cardGroupContainer; + QVBoxLayout *cardGroupLayout; + QString displayType = "flat"; + OverlapWidget *overlapWidget; +}; + +#endif // DECK_CARD_ZONE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp index bf51a232d..1d508114f 100644 --- a/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp @@ -1,5 +1,7 @@ #include "deck_preview_card_picture_widget.h" +#include "../../../../settings/cache_settings.h" + #include #include #include @@ -22,15 +24,24 @@ */ DeckPreviewCardPictureWidget::DeckPreviewCardPictureWidget(QWidget *parent, const bool hoverToZoomEnabled, + const bool raiseOnEnter, const QColor &textColor, const QColor &outlineColor, const int fontSize, const Qt::Alignment alignment) - : CardInfoPictureWithTextOverlayWidget(parent, hoverToZoomEnabled, textColor, outlineColor, fontSize, alignment) + : CardInfoPictureWithTextOverlayWidget(parent, + hoverToZoomEnabled, + raiseOnEnter, + textColor, + outlineColor, + fontSize, + alignment) { singleClickTimer = new QTimer(this); singleClickTimer->setSingleShot(true); connect(singleClickTimer, &QTimer::timeout, this, [this]() { emit imageClicked(lastMouseEvent, this); }); + connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageSelectionAnimationChanged, this, + &CardInfoPictureWidget::setRaiseOnEnterEnabled); } void DeckPreviewCardPictureWidget::mousePressEvent(QMouseEvent *event) diff --git a/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h index 3fafc73e0..568ab0f51 100644 --- a/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h @@ -14,6 +14,7 @@ class DeckPreviewCardPictureWidget final : public CardInfoPictureWithTextOverlay public: explicit DeckPreviewCardPictureWidget(QWidget *parent, bool hoverToZoomEnabled = false, + bool raiseOnEnter = false, const QColor &textColor = Qt::white, const QColor &outlineColor = Qt::black, int fontSize = 12, diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp new file mode 100644 index 000000000..02a4bfb5a --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp @@ -0,0 +1,25 @@ +#include "deck_analytics_widget.h" + +DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + mainLayout = new QVBoxLayout(); + setLayout(mainLayout); + + manaCurveWidget = new ManaCurveWidget(this, deckListModel); + mainLayout->addWidget(manaCurveWidget); + + manaDevotionWidget = new ManaDevotionWidget(this, deckListModel); + mainLayout->addWidget(manaDevotionWidget); + + manaBaseWidget = new ManaBaseWidget(this, deckListModel); + mainLayout->addWidget(manaBaseWidget); +} + +void DeckAnalyticsWidget::refreshDisplays(DeckListModel *_deckModel) +{ + deckListModel = _deckModel; + manaCurveWidget->setDeckModel(_deckModel); + manaDevotionWidget->setDeckModel(_deckModel); + manaBaseWidget->setDeckModel(_deckModel); +} diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h new file mode 100644 index 000000000..6e7a273cf --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h @@ -0,0 +1,34 @@ +#ifndef DECK_ANALYTICS_WIDGET_H +#define DECK_ANALYTICS_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../../../ui/widgets/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 + +class DeckAnalyticsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel); + void setDeckList(const DeckList &_deckListModel); + std::map analyzeManaCurve(); + void refreshDisplays(DeckListModel *_deckListModel); + +private: + DeckListModel *deckListModel; + QVBoxLayout *mainLayout; + + ManaCurveWidget *manaCurveWidget; + ManaDevotionWidget *manaDevotionWidget; + ManaBaseWidget *manaBaseWidget; +}; + +#endif // DECK_ANALYTICS_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.cpp new file mode 100644 index 000000000..895e2d64d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.cpp @@ -0,0 +1,135 @@ +#include "mana_base_widget.h" + +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../general/display/banner_widget.h" +#include "../general/display/bar_widget.h" + +#include +#include +#include + +ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + bannerWidget = new BannerWidget(this, tr("Mana Base"), Qt::Vertical, 100); + bannerWidget->setMaximumHeight(100); + layout->addWidget(bannerWidget); + + barContainer = new QWidget(this); + barLayout = new QHBoxLayout(barContainer); + layout->addWidget(barContainer); + + connect(deckListModel, &DeckListModel::dataChanged, this, &ManaBaseWidget::analyzeManaBase); + + retranslateUi(); +} + +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 + QLayoutItem *item; + while ((item = barLayout->takeAt(0)) != nullptr) { + item->widget()->deleteLater(); + delete item; + } + + int highestEntry = 0; + for (auto entry : manaBaseMap) { + if (entry > highestEntry) { + highestEntry = entry; + } + } + + // Define color mapping for mana types + QHash manaColors; + manaColors.insert("W", QColor(248, 231, 185)); + manaColors.insert("U", QColor(14, 104, 171)); + manaColors.insert("B", QColor(21, 11, 0)); + manaColors.insert("R", QColor(211, 32, 42)); + manaColors.insert("G", QColor(0, 115, 62)); + manaColors.insert("C", QColor(150, 150, 150)); + + for (auto manaColor : manaBaseMap.keys()) { + QColor barColor = manaColors.value(manaColor, Qt::gray); + BarWidget *barWidget = new BarWidget(QString(manaColor), manaBaseMap[manaColor], highestEntry, barColor, this); + barLayout->addWidget(barWidget); + } + + update(); +} + +QHash ManaBaseWidget::analyzeManaBase() +{ + manaBaseMap.clear(); + InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + 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) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(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/client/ui/widgets/deck_analytics/mana_base_widget.h b/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.h new file mode 100644 index 000000000..66442f687 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_base_widget.h @@ -0,0 +1,37 @@ +#ifndef MANA_BASE_WIDGET_H +#define MANA_BASE_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../general/display/banner_widget.h" + +#include +#include +#include +#include + +class ManaBaseWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ManaBaseWidget(QWidget *parent, DeckListModel *deckListModel); + QHash analyzeManaBase(); + 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; + BannerWidget *bannerWidget; + QHash manaBaseMap; + QVBoxLayout *layout; + QWidget *barContainer; + QHBoxLayout *barLayout; +}; + +#endif // MANA_BASE_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp new file mode 100644 index 000000000..acf240e0e --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp @@ -0,0 +1,99 @@ +#include "mana_curve_widget.h" + +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../main.h" +#include "../general/display/banner_widget.h" +#include "../general/display/bar_widget.h" + +#include +#include + +ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + bannerWidget = new BannerWidget(this, tr("Mana Curve"), Qt::Vertical, 100); + bannerWidget->setMaximumHeight(100); + layout->addWidget(bannerWidget); + + barContainer = new QWidget(this); + barLayout = new QHBoxLayout(barContainer); + layout->addWidget(barContainer); + + connect(deckListModel, &DeckListModel::dataChanged, this, &ManaCurveWidget::analyzeManaCurve); + + retranslateUi(); +} + +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(); + InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + 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) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); + if (info) { + int cmc = info->getCmc().toInt(); + manaCurveMap[cmc]++; + } + } + } + } + + updateDisplay(); + + return manaCurveMap; +} + +void ManaCurveWidget::updateDisplay() +{ + // Clear the layout first + if (barLayout != nullptr) { + QLayoutItem *item; + while ((item = barLayout->takeAt(0)) != nullptr) { + item->widget()->deleteLater(); + delete item; + } + } + + int highestEntry = 0; + for (const auto &entry : manaCurveMap) { + if (entry.second > highestEntry) { + highestEntry = entry.second; + } + } + + // Convert unordered_map to ordered map to ensure sorting by CMC + std::map sortedManaCurve(manaCurveMap.begin(), manaCurveMap.end()); + + // Add new widgets to the layout in sorted order + for (const auto &entry : sortedManaCurve) { + BarWidget *barWidget = + new BarWidget(QString::number(entry.first), entry.second, highestEntry, QColor(11, 11, 11), this); + barLayout->addWidget(barWidget); + } + + update(); // Update the widget display +} diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.h b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.h new file mode 100644 index 000000000..b45e5f862 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.h @@ -0,0 +1,33 @@ +#ifndef MANA_CURVE_WIDGET_H +#define MANA_CURVE_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../general/display/banner_widget.h" + +#include +#include +#include + +class ManaCurveWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ManaCurveWidget(QWidget *parent, DeckListModel *deckListModel); + void updateDisplay(); + +public slots: + void setDeckModel(DeckListModel *deckModel); + std::unordered_map analyzeManaCurve(); + void retranslateUi(); + +private: + DeckListModel *deckListModel; + std::unordered_map manaCurveMap; + QVBoxLayout *layout; + BannerWidget *bannerWidget; + QWidget *barContainer; + QHBoxLayout *barLayout; +}; + +#endif // MANA_CURVE_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp new file mode 100644 index 000000000..50b241a2a --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp @@ -0,0 +1,146 @@ +#include "mana_devotion_widget.h" + +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../main.h" +#include "../general/display/banner_widget.h" +#include "../general/display/bar_widget.h" + +#include +#include +#include +#include +#include + +ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + bannerWidget = new BannerWidget(this, tr("Mana Devotion"), Qt::Vertical, 100); + bannerWidget->setMaximumHeight(100); + layout->addWidget(bannerWidget); + + barLayout = new QHBoxLayout(); + layout->addLayout(barLayout); + + connect(deckListModel, &DeckListModel::dataChanged, this, &ManaDevotionWidget::analyzeManaDevotion); + + retranslateUi(); +} + +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(); + InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + 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) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); + if (info) { + auto devotion = countManaSymbols(info->getManaCost()); + mergeManaCounts(manaDevotionMap, devotion); + } + } + } + } + + updateDisplay(); + return manaDevotionMap; +} + +void ManaDevotionWidget::updateDisplay() +{ + // Clear the layout first + QLayoutItem *item; + while ((item = barLayout->takeAt(0)) != nullptr) { + item->widget()->deleteLater(); + delete item; + } + + int highestEntry = 0; + for (auto entry : manaDevotionMap) { + if (highestEntry < entry.second) { + highestEntry = entry.second; + } + } + + // Define color mapping for devotion bars + std::unordered_map manaColors = {{'W', QColor(248, 231, 185)}, {'U', QColor(14, 104, 171)}, + {'B', QColor(21, 11, 0)}, {'R', QColor(211, 32, 42)}, + {'G', QColor(0, 115, 62)}, {'C', QColor(150, 150, 150)}}; + + for (auto entry : manaDevotionMap) { + QColor barColor = manaColors.count(entry.first) ? manaColors[entry.first] : Qt::gray; + BarWidget *barWidget = new BarWidget(QString(entry.first), entry.second, highestEntry, barColor, this); + barLayout->addWidget(barWidget); + } + + 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/client/ui/widgets/deck_analytics/mana_devotion_widget.h b/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.h new file mode 100644 index 000000000..551ecd82d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_devotion_widget.h @@ -0,0 +1,36 @@ +#ifndef MANA_DEVOTION_WIDGET_H +#define MANA_DEVOTION_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../general/display/banner_widget.h" + +#include +#include +#include +#include + +class ManaDevotionWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ManaDevotionWidget(QWidget *parent, DeckListModel *deckListModel); + 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; + BannerWidget *bannerWidget; + std::unordered_map manaDevotionMap; + QVBoxLayout *layout; + QHBoxLayout *barLayout; +}; + +#endif // MANA_DEVOTION_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp index 94c6609be..008a3f5eb 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp @@ -1,6 +1,7 @@ #include "deck_editor_database_display_widget.h" #include "../../../../game/cards/card_database_manager.h" +#include "../../../../game/filters/syntax_help.h" #include "../../../../settings/cache_settings.h" #include "../../../tabs/abstract_tab_deck_editor.h" #include "../../../tabs/tab_supervisor.h" @@ -42,7 +43,7 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeck setFocusPolicy(Qt::ClickFocus); searchKeySignals.setObjectName("searchKeySignals"); - connect(searchEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateSearch(const QString &))); + connect(searchEdit, &SearchLineEdit::textChanged, this, &DeckEditorDatabaseDisplayWidget::updateSearch); connect(&searchKeySignals, &KeySignals::onEnter, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); connect(&searchKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); @@ -56,7 +57,7 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeck &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); connect(&searchKeySignals, &KeySignals::onCtrlEnter, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); connect(&searchKeySignals, &KeySignals::onCtrlC, this, &DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents); - connect(help, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::showSearchSyntaxHelp); + connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); }); databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this); databaseModel->setObjectName("databaseModel"); @@ -75,7 +76,8 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeck databaseView->sortByColumn(0, Qt::AscendingOrder); databaseView->setModel(databaseDisplayModel); databaseView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(databaseView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(databaseCustomMenu(QPoint))); + connect(databaseView, &QTreeView::customContextMenuRequested, this, + &DeckEditorDatabaseDisplayWidget::databaseCustomMenu); connect(databaseView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &DeckEditorDatabaseDisplayWidget::updateCard); connect(databaseView, &QTreeView::doubleClicked, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); @@ -87,7 +89,8 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeck } else { databaseView->header()->restoreState(dbHeaderState); } - connect(databaseView->header(), SIGNAL(geometriesChanged()), this, SLOT(saveDbHeaderState())); + connect(databaseView->header(), &QHeaderView::geometriesChanged, this, + &DeckEditorDatabaseDisplayWidget::saveDbHeaderState); searchEdit->setTreeView(databaseView); @@ -234,42 +237,6 @@ void DeckEditorDatabaseDisplayWidget::saveDbHeaderState() SettingsCache::instance().layouts().setDeckEditorDbHeaderState(databaseView->header()->saveState()); } -void DeckEditorDatabaseDisplayWidget::showSearchSyntaxHelp() -{ - - QFile file("theme:help/search.md"); - - if (!file.open(QFile::ReadOnly | QFile::Text)) { - return; - } - - QTextStream in(&file); - QString text = in.readAll(); - file.close(); - - // Poor Markdown Converter - auto opts = QRegularExpression::MultilineOption; - text = text.replace(QRegularExpression("^(###)(.*)", opts), "

\\2

") - .replace(QRegularExpression("^(##)(.*)", opts), "

\\2

") - .replace(QRegularExpression("^(#)(.*)", opts), "

\\2

") - .replace(QRegularExpression("^------*", opts), "
") - .replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(\1)"); - - auto browser = new QTextBrowser; - browser->setParent(this, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | - Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint); - browser->setWindowTitle("Search Help"); - browser->setReadOnly(true); - browser->setMinimumSize({500, 600}); - - QString sheet = QString("a { text-decoration: underline; color: rgb(71,158,252) };"); - browser->document()->setDefaultStyleSheet(sheet); - - browser->setHtml(text); - connect(browser, &QTextBrowser::anchorClicked, [this](const QUrl &link) { searchEdit->setText(link.fragment()); }); - browser->show(); -} - void DeckEditorDatabaseDisplayWidget::setFilterTree(FilterTree *filterTree) { databaseDisplayModel->setFilterTree(filterTree); diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.h index 9022a2cb7..4010a2954 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.h +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.h @@ -42,7 +42,6 @@ private: QWidget *centralWidget; private slots: - void showSearchSyntaxHelp(); void retranslateUi(); void updateSearch(const QString &search); void updateCard(const QModelIndex ¤t, const QModelIndex &); diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index a89f73aed..0dd1b513f 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -20,7 +20,7 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); installEventFilter(deckEditor); - connect(this, SIGNAL(topLevelChanged(bool)), deckEditor, SLOT(dockTopLevelChanged(bool))); + connect(this, &DeckEditorDeckDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged); createDeckDock(); } @@ -28,7 +28,7 @@ void DeckEditorDeckDockWidget::createDeckDock() { deckModel = new DeckListModel(this); deckModel->setObjectName("deckModel"); - connect(deckModel, SIGNAL(deckHashChanged()), this, SLOT(updateHash())); + connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); deckView = new QTreeView(); deckView->setObjectName("deckView"); deckView->setModel(deckModel); @@ -41,15 +41,15 @@ void DeckEditorDeckDockWidget::createDeckDock() deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &DeckEditorDeckDockWidget::updateCard); - connect(deckView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actSwapCard())); - connect(deckView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(decklistCustomMenu(QPoint))); - connect(&deckViewKeySignals, SIGNAL(onShiftS()), this, SLOT(actSwapCard())); - connect(&deckViewKeySignals, SIGNAL(onEnter()), this, SLOT(actIncrement())); - connect(&deckViewKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actIncrement())); - connect(&deckViewKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrementSelection())); - connect(&deckViewKeySignals, SIGNAL(onShiftRight()), this, SLOT(actIncrement())); - connect(&deckViewKeySignals, SIGNAL(onShiftLeft()), this, SLOT(actDecrementSelection())); - connect(&deckViewKeySignals, SIGNAL(onDelete()), this, SLOT(actRemoveCard())); + connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapCard); + 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::onCtrlAltMinus, this, &DeckEditorDeckDockWidget::actDecrementSelection); + connect(&deckViewKeySignals, &KeySignals::onShiftRight, this, &DeckEditorDeckDockWidget::actIncrement); + connect(&deckViewKeySignals, &KeySignals::onShiftLeft, this, &DeckEditorDeckDockWidget::actDecrementSelection); + connect(&deckViewKeySignals, &KeySignals::onDelete, this, &DeckEditorDeckDockWidget::actRemoveCard); nameLabel = new QLabel(); nameLabel->setObjectName("nameLabel"); @@ -57,7 +57,7 @@ void DeckEditorDeckDockWidget::createDeckDock() nameEdit->setMaxLength(MAX_NAME_LENGTH); nameEdit->setObjectName("nameEdit"); nameLabel->setBuddy(nameEdit); - connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateName(const QString &))); + connect(nameEdit, &LineEditUnfocusable::textChanged, this, &DeckEditorDeckDockWidget::updateName); commentsLabel = new QLabel(); commentsLabel->setObjectName("commentsLabel"); commentsEdit = new QTextEdit; @@ -65,7 +65,7 @@ void DeckEditorDeckDockWidget::createDeckDock() commentsEdit->setMinimumHeight(nameEdit->minimumSizeHint().height()); commentsEdit->setObjectName("commentsEdit"); commentsLabel->setBuddy(commentsEdit); - connect(commentsEdit, SIGNAL(textChanged()), this, SLOT(updateComments())); + connect(commentsEdit, &QTextEdit::textChanged, this, &DeckEditorDeckDockWidget::updateComments); bannerCardLabel = new QLabel(); bannerCardLabel->setObjectName("bannerCardLabel"); bannerCardLabel->setText(tr("Banner Card")); @@ -81,25 +81,25 @@ void DeckEditorDeckDockWidget::createDeckDock() aIncrement = new QAction(QString(), this); aIncrement->setIcon(QPixmap("theme:icons/increment")); - connect(aIncrement, SIGNAL(triggered()), this, SLOT(actIncrement())); + connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrement); auto *tbIncrement = new QToolButton(this); tbIncrement->setDefaultAction(aIncrement); aDecrement = new QAction(QString(), this); aDecrement->setIcon(QPixmap("theme:icons/decrement")); - connect(aDecrement, SIGNAL(triggered()), this, SLOT(actDecrementSelection())); + connect(aDecrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actDecrementSelection); auto *tbDecrement = new QToolButton(this); tbDecrement->setDefaultAction(aDecrement); aRemoveCard = new QAction(QString(), this); aRemoveCard->setIcon(QPixmap("theme:icons/remove_row")); - connect(aRemoveCard, SIGNAL(triggered()), this, SLOT(actRemoveCard())); + connect(aRemoveCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actRemoveCard); auto *tbRemoveCard = new QToolButton(this); tbRemoveCard->setDefaultAction(aRemoveCard); aSwapCard = new QAction(QString(), this); aSwapCard->setIcon(QPixmap("theme:icons/swap")); - connect(aSwapCard, SIGNAL(triggered()), this, SLOT(actSwapCard())); + connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapCard); auto *tbSwapCard = new QToolButton(this); tbSwapCard->setDefaultAction(aSwapCard); @@ -309,6 +309,8 @@ void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck) deckView->expandAll(); deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); + + emit deckChanged(); } DeckLoader *DeckEditorDeckDockWidget::getDeckList() @@ -325,6 +327,7 @@ void DeckEditorDeckDockWidget::cleanDeck() nameEdit->setText(QString()); commentsEdit->setText(QString()); hashLabel->setText(QString()); + emit deckChanged(); updateBannerCardComboBox(); deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); } diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h index 0500b5fca..89899b4bf 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -2,7 +2,7 @@ #define DECK_EDITOR_DECK_DOCK_WIDGET_H #include "../../../../deck/custom_line_edit.h" -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include "../../../game_logic/key_signals.h" #include "../../../tabs/abstract_tab_deck_editor.h" #include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h" diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp index c60b1733b..99d0e4829 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp @@ -32,22 +32,22 @@ void DeckEditorFilterDockWidget::createFiltersDock() filterView->setHeaderHidden(true); filterView->setContextMenuPolicy(Qt::CustomContextMenu); filterView->installEventFilter(&filterViewKeySignals); - connect(filterModel, SIGNAL(layoutChanged()), filterView, SLOT(expandAll())); - connect(filterView, SIGNAL(customContextMenuRequested(const QPoint &)), this, - SLOT(filterViewCustomContextMenu(const QPoint &))); - connect(&filterViewKeySignals, SIGNAL(onDelete()), this, SLOT(actClearFilterOne())); + connect(filterModel, &FilterTreeModel::layoutChanged, filterView, &QTreeView::expandAll); + connect(filterView, &QTreeView::customContextMenuRequested, this, + &DeckEditorFilterDockWidget::filterViewCustomContextMenu); + connect(&filterViewKeySignals, &KeySignals::onDelete, this, &DeckEditorFilterDockWidget::actClearFilterOne); auto *filterBuilder = new FilterBuilder; filterBuilder->setObjectName("filterBuilder"); - connect(filterBuilder, SIGNAL(add(const CardFilter *)), filterModel, SLOT(addFilter(const CardFilter *))); + connect(filterBuilder, &FilterBuilder::add, filterModel, &FilterTreeModel::addFilter); aClearFilterOne = new QAction(QString(), this); aClearFilterOne->setIcon(QPixmap("theme:icons/decrement")); - connect(aClearFilterOne, SIGNAL(triggered()), this, SLOT(actClearFilterOne())); + connect(aClearFilterOne, &QAction::triggered, this, &DeckEditorFilterDockWidget::actClearFilterOne); aClearFilterAll = new QAction(QString(), this); aClearFilterAll->setIcon(QPixmap("theme:icons/clearsearch")); - connect(aClearFilterAll, SIGNAL(triggered()), this, SLOT(actClearFilterAll())); + connect(aClearFilterAll, &QAction::triggered, this, &DeckEditorFilterDockWidget::actClearFilterAll); auto *filterDelOne = new QToolButton(); filterDelOne->setObjectName("filterDelOne"); diff --git a/cockatrice/src/client/ui/widgets/general/display/bar_widget.cpp b/cockatrice/src/client/ui/widgets/general/display/bar_widget.cpp new file mode 100644 index 000000000..2194b6b03 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/bar_widget.cpp @@ -0,0 +1,56 @@ +#include "bar_widget.h" + +#include +#include + +BarWidget::BarWidget(QString label, int value, int total, QColor barColor, QWidget *parent) + : QWidget(parent), label(std::move(label)), value(value), total(total), barColor(barColor) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +QSize BarWidget::sizeHint() const +{ + QFontMetrics metrics(font()); + int labelHeight = metrics.height(); + int valueHeight = metrics.height(); + + // Calculate the height dynamically based on the total + int barHeight = (total > 0) ? (value * 200 / total) : 20; // Scale height proportionally + int totalHeight = barHeight + labelHeight + valueHeight + 30; // Extra space for text + return QSize(60, totalHeight); // Allow width to expand +} + +void BarWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + int widgetWidth = width(); + int widgetHeight = height(); + + // Calculate bar dimensions + int barWidth = widgetWidth * 0.8; // Use 80% of the available width + int fullBarHeight = widgetHeight - 40; // Leave space for labels + int valueBarHeight = (total > 0) ? (value * fullBarHeight / total) : 0; + + // Draw full bar background (gray) + painter.setBrush(QColor(200, 200, 200)); + painter.drawRect((widgetWidth - barWidth) / 2, 10, barWidth, fullBarHeight); + + // Draw the value-specific bar using the assigned color + painter.setBrush(barColor); + painter.drawRect((widgetWidth - barWidth) / 2, 10 + fullBarHeight - valueBarHeight, barWidth, valueBarHeight); + + // Draw the CMC label + painter.setPen(Qt::white); + QRect textRect(0, widgetHeight - 30, widgetWidth, 20); + painter.drawText(textRect, Qt::AlignCenter, label); + + // Draw the value count + painter.setPen(Qt::black); + QRect valueRect(0, 10, widgetWidth, 20); + painter.drawText(valueRect, Qt::AlignCenter, QString::number(value)); + + (void)event; // Suppress unused parameter warning +} diff --git a/cockatrice/src/client/ui/widgets/general/display/bar_widget.h b/cockatrice/src/client/ui/widgets/general/display/bar_widget.h new file mode 100644 index 000000000..e32d773e8 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/bar_widget.h @@ -0,0 +1,27 @@ +#ifndef BAR_WIDGET_H +#define BAR_WIDGET_H + +#include +#include +#include + +class BarWidget : public QWidget +{ + Q_OBJECT + +public: + explicit BarWidget(QString label, int value, int total, QColor barColor = Qt::blue, QWidget *parent = nullptr); + + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QString label; + int value; + int total; + QColor barColor; // Store the bar color +}; + +#endif // BAR_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp index 13e1f15ea..73b86a573 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp @@ -26,14 +26,14 @@ FlowWidget::FlowWidget(QWidget *parent, { // Main Widget and Layout if (_flowDirection == Qt::Horizontal) { - this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); - this->setMinimumWidth(0); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + setMinimumWidth(0); } else { - this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); - this->setMinimumHeight(0); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + setMinimumHeight(0); } mainLayout = new QHBoxLayout(this); - this->setLayout(mainLayout); + setLayout(mainLayout); if (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff) { // Scroll Area, which should expand as much as possible, since it should be the only direct child widget. diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.cpp b/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.cpp index f89399d42..33f369ef6 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.cpp +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.cpp @@ -41,9 +41,8 @@ OverlapWidget::OverlapWidget(QWidget *parent, : QWidget(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows), direction(direction), adjustOnResize(adjustOnResize) { - this->setMinimumSize(0, 0); - overlapLayout = new OverlapLayout(this, overlapPercentage, maxColumns, maxRows, direction); - this->setLayout(overlapLayout); + overlapLayout = new OverlapLayout(this, overlapPercentage, maxColumns, maxRows, direction, Qt::Horizontal); + setLayout(overlapLayout); } /** @@ -57,7 +56,12 @@ OverlapWidget::OverlapWidget(QWidget *parent, */ void OverlapWidget::addWidget(QWidget *widgetToAdd) const { - this->overlapLayout->addWidget(widgetToAdd); + overlapLayout->addWidget(widgetToAdd); +} + +void OverlapWidget::removeWidget(QWidget *widgetToRemove) const +{ + overlapLayout->removeWidget(widgetToRemove); } /** @@ -72,8 +76,8 @@ void OverlapWidget::clearLayout() if (overlapLayout != nullptr) { QLayoutItem *item; while ((item = overlapLayout->takeAt(0)) != nullptr) { - delete item->widget(); // Delete the widget - delete item; // Delete the layout item + item->widget()->deleteLater(); + delete item; } } diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.h b/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.h index 0222a386d..910362ebd 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.h +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.h @@ -17,6 +17,7 @@ public: Qt::Orientation direction, bool adjustOnResize = false); void addWidget(QWidget *widgetToAdd) const; + void removeWidget(QWidget *widgetToRemove) const; void clearLayout(); void adjustMaxColumnsAndRows(); diff --git a/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.h index a051d2ea2..74d8793f0 100644 --- a/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.h @@ -3,7 +3,7 @@ #include "../../../../deck/deck_list_model.h" #include "../../../../deck/deck_loader.h" -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include "../../../tabs/abstract_tab_deck_editor.h" #include "../general/display/dynamic_font_size_push_button.h" diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h index ea2e69a5f..515807831 100644 --- a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h @@ -2,7 +2,7 @@ #define PRINTING_SELECTOR_H #include "../../../../deck/deck_list_model.h" -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include "../cards/card_size_widget.h" #include "../general/layout_containers/flow_widget.h" #include "../quick_settings/settings_button_widget.h" diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.h index 32e1e925d..b6e4ee157 100644 --- a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.h +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.h @@ -2,7 +2,7 @@ #define PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H #include "../../../../deck/deck_list_model.h" -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include "../../../tabs/abstract_tab_deck_editor.h" #include "printing_selector_card_overlay_widget.h" #include "set_name_and_collectors_number_display_widget.h" diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.h index 5ab27a274..1741c91dc 100644 --- a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.h +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.h @@ -3,7 +3,7 @@ #include "../../../../client/ui/widgets/cards/card_info_picture_widget.h" #include "../../../../deck/deck_list_model.h" -#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_info.h" #include "../../../tabs/abstract_tab_deck_editor.h" #include "all_zones_card_amount_widget.h" #include "card_amount_widget.h" diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp index 5d3307e52..0818050d8 100644 --- a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp @@ -6,7 +6,7 @@ #include SettingsButtonWidget::SettingsButtonWidget(QWidget *parent) - : QWidget(parent), button(new QToolButton(this)), popup(new SettingsPopupWidget(this)) + : QWidget(parent), button(new QToolButton(this)), popup(new SettingsPopupWidget(nullptr)) { button->setIcon(QPixmap("theme:icons/cogwheel")); @@ -26,39 +26,40 @@ void SettingsButtonWidget::addSettingsWidget(QWidget *toAdd) const popup->addSettingsWidget(toAdd); } +void SettingsButtonWidget::setButtonIcon(QPixmap iconMap) +{ + button->setIcon(iconMap); +} + void SettingsButtonWidget::togglePopup() { if (popup->isVisible()) { popup->close(); } else { - // Ensure popup size is known before positioning - popup->adjustSize(); - QSize popupSize = popup->size(); + popup->adjustSizeToFitScreen(); // Ensure proper size - // Get button position + QSize popupSize = popup->size(); QPoint buttonGlobalPos = button->mapToGlobal(QPoint(0, button->height())); - // Get screen geometry QScreen *screen = QApplication::screenAt(buttonGlobalPos); QRect screenGeom = screen ? screen->availableGeometry() : QApplication::primaryScreen()->availableGeometry(); int x = buttonGlobalPos.x(); int y = buttonGlobalPos.y(); - // Adjust X position if popup overflows the right side of the screen + // Adjust position to stay within screen bounds if (x + popupSize.width() > screenGeom.right()) { - x = buttonGlobalPos.x() - popupSize.width() + button->width(); // Move left + x = buttonGlobalPos.x() - popupSize.width() + button->width(); } - // Adjust Y position if popup overflows the bottom of the screen if (y + popupSize.height() > screenGeom.bottom()) { - y = buttonGlobalPos.y() - popupSize.height() - button->height(); // Move up + y = buttonGlobalPos.y() - popupSize.height() - button->height(); } popup->move(x, y); popup->show(); popup->setFocus(); - button->setChecked(true); // Ensure button is checked when popup is visible + button->setChecked(true); } } diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h index 7e07f573c..4234ed438 100644 --- a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h @@ -13,6 +13,7 @@ class SettingsButtonWidget : public QWidget public: explicit SettingsButtonWidget(QWidget *parent = nullptr); void addSettingsWidget(QWidget *toAdd) const; + void setButtonIcon(QPixmap iconMap); protected: void mousePressEvent(QMouseEvent *event) override; diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp index 10b2c327c..258f1163e 100644 --- a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp @@ -3,15 +3,89 @@ #include #include #include +#include +#include SettingsPopupWidget::SettingsPopupWidget(QWidget *parent) : QWidget(parent, Qt::Popup | Qt::FramelessWindowHint) { + // Main layout for the popup itself layout = new QVBoxLayout(this); + + // Container for the content (with or without scroll area) + containerWidget = new QWidget(); + containerLayout = new QVBoxLayout(containerWidget); // Store a separate layout + containerWidget->setLayout(containerLayout); // Make sure containerWidget has its layout + + // Add the container widget directly to the layout initially + layout->addWidget(containerWidget); // Initially, we add the containerWidget without a scroll area + + setLayout(layout); + + scrollArea = nullptr; // Initialize scrollArea pointer to null } void SettingsPopupWidget::addSettingsWidget(QWidget *toAdd) const { - layout->addWidget(toAdd); + containerLayout->addWidget(toAdd); // Add to containerWidget's layout +} + +void SettingsPopupWidget::adjustSizeToFitScreen() +{ + QScreen *screen = QApplication::screenAt(this->pos()); + QRect screenGeom = screen ? screen->availableGeometry() : QApplication::primaryScreen()->availableGeometry(); + int maxHeight = screenGeom.height() / 2; // Limit height to 50% of screen + + // Adjust the container widget's size hint to get the actual size of content + containerWidget->adjustSize(); + int contentHeight = containerWidget->sizeHint().height(); + + // If content height exceeds maxHeight, we need to scroll + if (contentHeight > maxHeight) { + // Create a scroll area and add the container widget to it if not already created + if (!scrollArea) { + scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // No horizontal scrollbar + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); // Enable vertical scrollbar when needed + } + + // Set maximum height for the scroll area and show vertical scrollbar + scrollArea->setMaximumHeight(maxHeight); + + // Resize the popup widget without squishing the content + resize(sizeHint().width(), maxHeight); // Ensure content width is kept intact + + // Add scrollArea to layout if not already added + if (layout->count() == 1) { // We only have one widget (containerWidget) at the start + layout->addWidget(scrollArea); + } + + // Set the scroll area widget + scrollArea->setWidget(containerWidget); + } else { + // If the scroll area exists, remove it + if (scrollArea) { + layout->removeWidget(scrollArea); + delete scrollArea; + scrollArea = nullptr; // Reset the pointer + } + + // Set the containerWidget directly without scrollArea and adjust its height + resize(sizeHint().width(), contentHeight); // Resize the widget based on content height + layout->addWidget(containerWidget); // Re-add the containerWidget without scroll area + } + + // Ensure layout updates after changes + layout->update(); // Update layout for proper size fitting + updateGeometry(); // Update widget geometry to reflect changes +} + +void SettingsPopupWidget::resizeEvent(QResizeEvent *event) +{ + // Make sure the scroll area and popup adjust to new size constraints + adjustSizeToFitScreen(); + QWidget::resizeEvent(event); } void SettingsPopupWidget::focusOutEvent(QFocusEvent *event) @@ -34,4 +108,4 @@ void SettingsPopupWidget::paintEvent(QPaintEvent *event) painter.setPen(Qt::gray); painter.drawRect(rect().adjusted(0, 0, -1, -1)); QWidget::paintEvent(event); -} \ No newline at end of file +} diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h index 291063662..d57c123c4 100644 --- a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h @@ -2,6 +2,7 @@ #define SETTINGS_POPUP_WIDGET_H #include +#include #include #include @@ -12,6 +13,7 @@ class SettingsPopupWidget : public QWidget public: explicit SettingsPopupWidget(QWidget *parent = nullptr); void addSettingsWidget(QWidget *toAdd) const; + void adjustSizeToFitScreen(); signals: void aboutToClose(); @@ -20,8 +22,12 @@ protected: void focusOutEvent(QFocusEvent *event) override; void closeEvent(QCloseEvent *event) override; void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; QVBoxLayout *layout; + QVBoxLayout *containerLayout; + QScrollArea *scrollArea = nullptr; + QWidget *containerWidget; }; #endif // SETTINGSPOPUP_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp new file mode 100644 index 000000000..e7dfd6f43 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp @@ -0,0 +1,228 @@ +#include "visual_database_display_color_filter_widget.h" + +#include "../../../../game/filters/filter_tree.h" +#include "../cards/additional_info/mana_symbol_widget.h" + +#include +#include + +/** + * This widget provides a graphical control element for the CardFilter::Attr::AttrColor filters applied to the filter + * model. + * @param parent The Qt Widget that this widget will parent to + * @param _filterModel The filter model that this widget will manipulate + */ +VisualDatabaseDisplayColorFilterWidget::VisualDatabaseDisplayColorFilterWidget(QWidget *parent, + FilterTreeModel *_filterModel) + : QWidget(parent), filterModel(_filterModel), layout(new QHBoxLayout(this)) +{ + setLayout(layout); + layout->setSpacing(5); + layout->setContentsMargins(0, 0, 0, 0); + + QString fullColorIdentity = "WUBRG"; + for (const QChar &color : fullColorIdentity) { + auto *manaSymbol = new ManaSymbolWidget(this, color, false, true); + manaSymbol->setFixedWidth(25); + + layout->addWidget(manaSymbol); + + // Initialize the activeColors map + activeColors[color] = false; + + // Connect the color toggled signal + connect(manaSymbol, &ManaSymbolWidget::colorToggled, this, + &VisualDatabaseDisplayColorFilterWidget::handleColorToggled); + } + + toggleButton = new QPushButton(this); + toggleButton->setCheckable(true); + layout->addWidget(toggleButton); + + // Connect the button's toggled signal + connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplayColorFilterWidget::updateFilterMode); + connect(this, &VisualDatabaseDisplayColorFilterWidget::activeColorsChanged, this, + &VisualDatabaseDisplayColorFilterWidget::updateColorFilter); + connect(this, &VisualDatabaseDisplayColorFilterWidget::filterModeChanged, this, + &VisualDatabaseDisplayColorFilterWidget::updateColorFilter); + connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { + if (blockSync) { + return; // Skip sync if we're blocking it + } + QTimer::singleShot(100, this, &VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel); + }); + + // Call retranslateUi to set the initial text + retranslateUi(); +} + +void VisualDatabaseDisplayColorFilterWidget::retranslateUi() +{ + switch (currentMode) { + case FilterMode::ExactMatch: + toggleButton->setText(tr("Mode: Exact Match")); + break; + case FilterMode::Includes: + toggleButton->setText(tr("Mode: Includes")); + break; + case FilterMode::IncludeExclude: + toggleButton->setText(tr("Mode: Include/Exclude")); + break; + } +} + +void VisualDatabaseDisplayColorFilterWidget::handleColorToggled(QChar color, bool active) +{ + activeColors[color] = active; + emit activeColorsChanged(); // Notify listeners that the active colors have changed +} + +void VisualDatabaseDisplayColorFilterWidget::updateColorFilter() +{ + blockSync = true; + + // Clear previous filters + filterModel->blockSignals(true); + filterModel->filterTree()->blockSignals(true); + filterModel->clearFiltersOfType(CardFilter::Attr::AttrColor); + + QSet selectedColors; + QSet excludedColors; + + // Collect active colors in the selected and excluded sets + for (const auto &color : activeColors.keys()) { + if (activeColors[color]) { + selectedColors.insert(color); // Include this color + } else { + excludedColors.insert(color); // Exclude this color + } + } + + switch (currentMode) { + case FilterMode::ExactMatch: + // Exact Match Mode: Only selected colors are allowed + if (!selectedColors.isEmpty()) { + // Require all selected colors (TypeAnd) + for (const auto &color : selectedColors) { + QString colorString = color; + filterModel->addFilter( + new CardFilter(colorString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrColor)); + } + + // Exclude all other colors + QStringList allPossibleColors = {"W", "U", "B", "R", "G"}; + for (const auto &color : allPossibleColors) { + if (!selectedColors.contains(color)) { + QString colorString = color; + filterModel->addFilter( + new CardFilter(colorString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor)); + } + } + } + break; + + case FilterMode::Includes: + // Includes Mode: Just include selected colors without restrictions + for (const auto &color : selectedColors) { + QString colorString = color; + filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, + CardFilter::Attr::AttrColor)); // OR for selected colors + } + break; + + case FilterMode::IncludeExclude: + // Include/Exclude Mode: Include selected colors and exclude unselected colors + for (const auto &color : selectedColors) { + QString colorString = color; + filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, + CardFilter::Attr::AttrColor)); // OR for selected colors + } + for (const auto &color : excludedColors) { + QString colorString = color; + filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeAndNot, + CardFilter::Attr::AttrColor)); // AND NOT for excluded colors + } + break; + } + + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); + + blockSync = false; +} + +void VisualDatabaseDisplayColorFilterWidget::updateFilterMode() +{ + blockSync = true; + + switch (currentMode) { + case FilterMode::ExactMatch: + currentMode = FilterMode::Includes; // Switch to Includes + break; + case FilterMode::Includes: + currentMode = FilterMode::IncludeExclude; // Switch to Include/Exclude + break; + case FilterMode::IncludeExclude: + currentMode = FilterMode::ExactMatch; // Switch to Exact Match + break; + } + + retranslateUi(); // Update button text based on the mode + emit filterModeChanged(currentMode); // Signal mode change + updateColorFilter(); // Reapply the filter based on the new mode + + blockSync = false; +} + +void VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel() +{ + blockSync = true; + QSet currentFilters; + + // Get current filters of type color + for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrColor)) { + if (filter->type() == CardFilter::Type::TypeAnd || filter->type() == CardFilter::Type::TypeOr) { + currentFilters.insert(filter->term()); + } + } + + QSet activeFilterList; + + // Iterate over the activeColors map and collect the active colors as strings + for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) { + if (it.value()) { // Only add active colors + activeFilterList.insert(QString(it.key())); + } + } + + // Check if the filters in the model match the active filters + if (currentFilters == activeFilterList) { + return; + } + + // Remove filters that are in the UI but not in the model + for (const auto &color : activeFilterList) { + if (!currentFilters.contains(color)) { + activeColors[color[0]] = false; // Disable the color + } + } + + // Add filters that are in the model but not in the UI + for (const auto &color : currentFilters) { + if (!activeFilterList.contains(color)) { + activeColors[color[0]] = true; // Enable the color + } + } + + QList manaSymbolWidgets = findChildren(); + + for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) { + manaSymbolWidget->setColorActive(activeColors[manaSymbolWidget->getSymbolChar()]); + } + + updateColorFilter(); + blockSync = false; +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.h new file mode 100644 index 000000000..191d00eee --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.h @@ -0,0 +1,63 @@ +#ifndef VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H +#define VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H + +#include "../../../../game/filters/filter_tree_model.h" + +#include +#include +#include + +class VisualDatabaseDisplayColorFilterCircleWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VisualDatabaseDisplayColorFilterCircleWidget(QChar color, QWidget *parent = nullptr); + void setColorActive(bool active); + bool isColorActive() const; + QChar getColorChar() const; + +signals: + void colorToggled(QChar color, bool active); + +private: + QChar colorChar; + bool isActive; + int circleDiameter; +}; + +enum class FilterMode +{ + ExactMatch, // Only selected colors are included, all others are excluded. + Includes, // Include selected colors (OR condition). + IncludeExclude // Include selected colors (OR) and exclude unselected colors (AND NOT). +}; + +class VisualDatabaseDisplayColorFilterWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VisualDatabaseDisplayColorFilterWidget(QWidget *parent, FilterTreeModel *filterModel); + void retranslateUi(); + +signals: + void filterModeChanged(FilterMode filterMode); + void activeColorsChanged(); + +private slots: + void handleColorToggled(QChar color, bool active); + void updateColorFilter(); + void updateFilterMode(); + void syncWithFilterModel(); + +private: + FilterTreeModel *filterModel; + QHBoxLayout *layout; + QPushButton *toggleButton; + QMap activeColors; + FilterMode currentMode = FilterMode::Includes; // Default mode + bool blockSync = false; +}; + +#endif // VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp new file mode 100644 index 000000000..1a099501e --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp @@ -0,0 +1,153 @@ +#include "visual_database_display_filter_save_load_widget.h" + +#include "../../../../game/filters/filter_tree.h" +#include "../../../../settings/cache_settings.h" +#include "visual_database_filter_display_widget.h" + +#include +#include +#include +#include + +VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWidget(QWidget *parent, + FilterTreeModel *_filterModel) + : QWidget(parent), filterModel(_filterModel) +{ + setMinimumWidth(300); + setMaximumHeight(300); + + layout = new QVBoxLayout(this); + setLayout(layout); + + // Input for filter filename + filenameInput = new QLineEdit(this); + layout->addWidget(filenameInput); + + // Save button + saveButton = new QPushButton(this); + layout->addWidget(saveButton); + connect(saveButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter); + + // File list container + fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + layout->addWidget(fileListWidget); + + refreshFilterList(); // Populate the file list on startup + retranslateUi(); +} + +void VisualDatabaseDisplayFilterSaveLoadWidget::retranslateUi() +{ + saveButton->setText(tr("Save Filter")); + filenameInput->setPlaceholderText(tr("Enter filename...")); +} + +void VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter() +{ + QString filename = filenameInput->text().trimmed(); + if (filename.isEmpty()) + return; + + QString filePath = SettingsCache::instance().getFiltersPath() + QDir::separator() + filename + ".json"; + + // Serialize the filter model to JSON + QJsonArray filtersArray; + QList cardFilters = filterModel->allFilters(); + + for (const CardFilter *filter : cardFilters) { + filtersArray.append(filter->toJson()); + } + + QJsonObject root; + root["filters"] = filtersArray; + + QFile file(filePath); + if (file.open(QIODevice::WriteOnly)) { + file.write(QJsonDocument(root).toJson(QJsonDocument::Indented)); + file.close(); + } + + filenameInput->clear(); + refreshFilterList(); // Update the file list +} + +void VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter(const QString &filename) +{ + QString filePath = SettingsCache::instance().getFiltersPath() + QDir::separator() + filename; + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) + return; + + QByteArray jsonData = file.readAll(); + file.close(); + + QJsonDocument doc = QJsonDocument::fromJson(jsonData); + if (!doc.isObject()) + return; + + QJsonObject root = doc.object(); + if (!root.contains("filters") || !root["filters"].isArray()) + return; + + QJsonArray filtersArray = root["filters"].toArray(); + + filterModel->clear(); + + filterModel->blockSignals(true); + filterModel->filterTree()->blockSignals(true); + for (const QJsonValue &value : filtersArray) { + if (value.isObject()) { + QJsonObject filterObj = value.toObject(); + CardFilter *filter = CardFilter::fromJson(filterObj); + if (filter) { + filterModel->addFilter(filter); + } + } + } + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); +} + +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 + + // Refresh the filter file list + QDir dir(SettingsCache::instance().getFiltersPath()); + QStringList filterFiles = dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Time); + + // 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); + } + } +} diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h new file mode 100644 index 000000000..52b2af2bf --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h @@ -0,0 +1,43 @@ +#ifndef VISUAL_DATABASE_DISPLAY_FILTER_SAVE_LOAD_WIDGET_H +#define VISUAL_DATABASE_DISPLAY_FILTER_SAVE_LOAD_WIDGET_H + +#include "../../../../game/filters/filter_tree_model.h" +#include "../general/layout_containers/flow_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class VisualDatabaseDisplayFilterSaveLoadWidget : public QWidget +{ + Q_OBJECT +public: + explicit VisualDatabaseDisplayFilterSaveLoadWidget(QWidget *parent, FilterTreeModel *filterModel); + + void saveFilter(); + void loadFilter(const QString &filename); + void refreshFilterList(); + void deleteFilter(const QString &filename, QPushButton *deleteButton); + +public slots: + void retranslateUi(); + +private: + FilterTreeModel *filterModel; + + QVBoxLayout *layout; + QLineEdit *filenameInput; + QPushButton *saveButton; + FlowWidget *fileListWidget; + + QMap> fileButtons; +}; + +#endif // VISUAL_DATABASE_DISPLAY_FILTER_SAVE_LOAD_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp new file mode 100644 index 000000000..688f8f4d2 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp @@ -0,0 +1,196 @@ +#include "visual_database_display_main_type_filter_widget.h" + +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../game/filters/filter_tree.h" +#include "../../../../game/filters/filter_tree_model.h" + +#include +#include +#include + +VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWidget(QWidget *parent, + FilterTreeModel *_filterModel) + : QWidget(parent), filterModel(_filterModel) +{ + allMainCardTypesWithCount = CardDatabaseManager::getInstance()->getAllMainCardTypesWithCount(); + // Get all main card types with their count + + 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::of(&QSpinBox::valueChanged), this, + &VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility); + + // Create the toggle button for Exact Match/Includes mode + toggleButton = new QPushButton(this); + toggleButton->setCheckable(true); + layout->addWidget(toggleButton); + connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode); + connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { + QTimer::singleShot(100, this, &VisualDatabaseDisplayMainTypeFilterWidget::syncWithFilterModel); + }); + + createMainTypeButtons(); // Populate buttons initially + updateFilterMode(false); // Initialize toggle button text +} + +void VisualDatabaseDisplayMainTypeFilterWidget::createMainTypeButtons() +{ + // Iterate through main types and create buttons + for (auto it = allMainCardTypesWithCount.begin(); it != allMainCardTypesWithCount.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); + typeButtons[it.key()] = button; + + // Connect toggle signal + connect(button, &QPushButton::toggled, this, + [this, mainType = it.key()](bool checked) { handleMainTypeToggled(mainType, checked); }); + } + updateMainTypeButtonsVisibility(); // Ensure visibility is updated initially +} + +void VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility() +{ + int threshold = spinBox->value(); // Get the current spinbox value + + // Iterate through buttons and hide/disable those below the threshold + for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) { + bool visible = allMainCardTypesWithCount[it.key()] >= threshold; + it.value()->setVisible(visible); + it.value()->setEnabled(visible); + } +} + +int VisualDatabaseDisplayMainTypeFilterWidget::getMaxMainTypeCount() const +{ + int maxCount = 1; + for (auto it = allMainCardTypesWithCount.begin(); it != allMainCardTypesWithCount.end(); ++it) { + maxCount = qMax(maxCount, it.value()); + } + return maxCount; +} + +void VisualDatabaseDisplayMainTypeFilterWidget::handleMainTypeToggled(const QString &mainType, bool active) +{ + activeMainTypes[mainType] = active; + + if (typeButtons.contains(mainType)) { + typeButtons[mainType]->setChecked(active); + } + + updateMainTypeFilter(); +} + +void VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeFilter() +{ + // Clear existing filters related to main type + filterModel->blockSignals(true); + filterModel->filterTree()->blockSignals(true); + filterModel->clearFiltersOfType(CardFilter::Attr::AttrMainType); + + if (exactMatchMode) { + // Exact Match: Only selected main types are allowed + QSet selectedTypes; + for (const auto &type : activeMainTypes.keys()) { + if (activeMainTypes[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::AttrMainType)); + } + + // Exclude any other types (TypeAndNot) + for (const auto &type : typeButtons.keys()) { + if (!selectedTypes.contains(type)) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrMainType)); + } + } + } + } else { + // Default Includes Mode (TypeOr) - match any selected main types + for (const auto &type : activeMainTypes.keys()) { + if (activeMainTypes[type]) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrMainType)); + } + } + } + + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); +} + +void VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode(bool checked) +{ + exactMatchMode = checked; + toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); + updateMainTypeFilter(); +} + +void VisualDatabaseDisplayMainTypeFilterWidget::syncWithFilterModel() +{ + // Temporarily block signals for each button to prevent toggling while updating button states + for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) { + it.value()->blockSignals(true); + } + + // Uncheck all buttons + for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) { + it.value()->setChecked(false); + } + + // Get active filters for main types + QSet activeTypes; + for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrMainType)) { + if (filter->type() == CardFilter::Type::TypeAnd) { + activeTypes.insert(filter->term()); + } + } + + // Check the buttons for active types + for (const auto &type : activeTypes) { + activeMainTypes[type] = true; + if (typeButtons.contains(type)) { + typeButtons[type]->setChecked(true); + } + } + + // Re-enable signal emissions for each button + for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) { + it.value()->blockSignals(false); + } + + // Update the visibility of buttons + updateMainTypeButtonsVisibility(); +} diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h new file mode 100644 index 000000000..b13071825 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h @@ -0,0 +1,42 @@ +#ifndef VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H +#define VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H + +#include "../../../../game/filters/filter_tree_model.h" +#include "../general/layout_containers/flow_widget.h" + +#include +#include +#include +#include +#include +#include + +class VisualDatabaseDisplayMainTypeFilterWidget : public QWidget +{ + Q_OBJECT +public: + explicit VisualDatabaseDisplayMainTypeFilterWidget(QWidget *parent, FilterTreeModel *filterModel); + void createMainTypeButtons(); + void updateMainTypeButtonsVisibility(); + int getMaxMainTypeCount() const; + + void handleMainTypeToggled(const QString &mainType, bool active); + void updateMainTypeFilter(); + void updateFilterMode(bool checked); + void syncWithFilterModel(); + +private: + FilterTreeModel *filterModel; + QMap allMainCardTypesWithCount; + QSpinBox *spinBox; + QHBoxLayout *layout; + FlowWidget *flowWidget; + QPushButton *toggleButton; // Mode switch button + + QMap activeMainTypes; // Track active filters + QMap typeButtons; // Store toggle buttons + + bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes" +}; + +#endif // VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp new file mode 100644 index 000000000..69e782b66 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -0,0 +1,160 @@ +#include "visual_database_display_name_filter_widget.h" + +#include "../../../tabs/abstract_tab_deck_editor.h" + +#include + +VisualDatabaseDisplayNameFilterWidget::VisualDatabaseDisplayNameFilterWidget(QWidget *parent, + AbstractTabDeckEditor *_deckEditor, + FilterTreeModel *_filterModel) + : QWidget(parent), deckEditor(_deckEditor), filterModel(_filterModel) +{ + setMinimumWidth(300); + setMaximumHeight(300); + + layout = new QVBoxLayout(this); + setLayout(layout); + + searchBox = new QLineEdit(this); + layout->addWidget(searchBox); + + connect(searchBox, &QLineEdit::returnPressed, this, [this]() { + QString text = searchBox->text().trimmed(); + if (!text.isEmpty() && !activeFilters.contains(text)) { + createNameFilter(text); + searchBox->clear(); + updateFilterModel(); + } + }); + + // Create container for active filters + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + layout->addWidget(flowWidget); + + loadFromDeckButton = new QPushButton(this); + layout->addWidget(loadFromDeckButton); + + connect(loadFromDeckButton, &QPushButton::clicked, this, &VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck); + connect(filterModel, &FilterTreeModel::layoutChanged, this, + [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel); }); + + retranslateUi(); +} + +void VisualDatabaseDisplayNameFilterWidget::retranslateUi() +{ + searchBox->setPlaceholderText(tr("Filter by name...")); + loadFromDeckButton->setText(tr("Load from Deck")); +} + +void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() +{ + DeckListModel *deckListModel = deckEditor->deckDockWidget->deckModel; + + if (!deckListModel) + return; + DeckList *decklist = deckListModel->getDeckList(); + if (!decklist) + return; + InnerDecklistNode *listRoot = decklist->getRoot(); + if (!listRoot) + return; + + 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; + createNameFilter(currentCard->getName()); + } + } + updateFilterModel(); +} + +void VisualDatabaseDisplayNameFilterWidget::createNameFilter(const QString &name) +{ + if (activeFilters.contains(name)) + return; + + // Create a button for the filter + auto *button = new QPushButton(name, flowWidget); + button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" + "QPushButton:hover { background-color: red; color: white; }"); + + connect(button, &QPushButton::clicked, this, [this, name]() { removeNameFilter(name); }); + + flowWidget->addWidget(button); + activeFilters[name] = button; +} + +void VisualDatabaseDisplayNameFilterWidget::removeNameFilter(const QString &name) +{ + if (activeFilters.contains(name)) { + activeFilters[name]->deleteLater(); // Safe deletion + activeFilters.remove(name); + + QTimer::singleShot(0, this, + &VisualDatabaseDisplayNameFilterWidget::updateFilterModel); // Avoid concurrent modification + } +} + +void VisualDatabaseDisplayNameFilterWidget::updateFilterModel() +{ + // Clear existing name filters + emit filterModel->layoutAboutToBeChanged(); + filterModel->clearFiltersOfType(CardFilter::Attr::AttrName); + + 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->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); +} + +void VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel() +{ + QStringList currentFilters; + for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrName)) { + if (filter->type() == CardFilter::Type::TypeOr) { + currentFilters.append(filter->term()); + } + } + + QStringList activeFilterList = activeFilters.keys(); + + // Sort lists for efficient comparison + std::sort(currentFilters.begin(), currentFilters.end()); + std::sort(activeFilterList.begin(), activeFilterList.end()); + + if (currentFilters == activeFilterList) { + return; + } + + // Remove filters that are in the UI but not in the model + for (const auto &name : activeFilterList) { + if (!currentFilters.contains(name)) { + removeNameFilter(name); + } + } + + // Add filters that are in the model but not in the UI + for (const auto &name : currentFilters) { + if (!activeFilters.contains(name)) { + createNameFilter(name); + } + } +} diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.h new file mode 100644 index 000000000..91ad56160 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.h @@ -0,0 +1,45 @@ +#ifndef VISUAL_DATABASE_DISPLAY_NAME_FILTER_WIDGET_H +#define VISUAL_DATABASE_DISPLAY_NAME_FILTER_WIDGET_H + +#include "../../../../game/filters/filter_tree_model.h" +#include "../../../tabs/abstract_tab_deck_editor.h" +#include "../general/layout_containers/flow_widget.h" + +#include +#include +#include +#include +#include + +class VisualDatabaseDisplayNameFilterWidget : public QWidget +{ + Q_OBJECT +public: + explicit VisualDatabaseDisplayNameFilterWidget(QWidget *parent, + AbstractTabDeckEditor *deckEditor, + FilterTreeModel *filterModel); + + void createNameFilter(const QString &name); + void removeNameFilter(const QString &name); + void updateFilterList(); + void updateFilterModel(); + void syncWithFilterModel(); + +public slots: + void retranslateUi(); + +private: + AbstractTabDeckEditor *deckEditor; + FilterTreeModel *filterModel; + QVBoxLayout *layout; + QLineEdit *searchBox; + FlowWidget *flowWidget; + QPushButton *loadFromDeckButton; + + QMap activeFilters; // Store active name filter buttons + +private slots: + void actLoadFromDeck(); +}; + +#endif // VISUAL_DATABASE_DISPLAY_NAME_FILTER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp new file mode 100644 index 000000000..795c83d68 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp @@ -0,0 +1,195 @@ +#include "visual_database_display_set_filter_widget.h" + +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../game/filters/filter_tree.h" +#include "../../../../game/filters/filter_tree_model.h" + +#include +#include +#include +#include + +VisualDatabaseDisplaySetFilterWidget::VisualDatabaseDisplaySetFilterWidget(QWidget *parent, + FilterTreeModel *_filterModel) + : QWidget(parent), filterModel(_filterModel) +{ + setMinimumWidth(300); + setMaximumHeight(300); + + layout = new QVBoxLayout(this); + setLayout(layout); + + searchBox = new QLineEdit(this); + searchBox->setPlaceholderText(tr("Search sets...")); + layout->addWidget(searchBox); + connect(searchBox, &QLineEdit::textChanged, this, + &VisualDatabaseDisplaySetFilterWidget::updateSetButtonsVisibility); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + layout->addWidget(flowWidget); + + // Create the toggle button for Exact Match/Includes mode + toggleButton = new QPushButton(this); + toggleButton->setCheckable(true); + layout->addWidget(toggleButton); + connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplaySetFilterWidget::updateFilterMode); + connect(filterModel, &FilterTreeModel::layoutChanged, this, + [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel); }); + + createSetButtons(); // Populate buttons initially + updateFilterMode(false); // Initialize toggle button text +} + +void VisualDatabaseDisplaySetFilterWidget::createSetButtons() +{ + SetList shared_pointerses = CardDatabaseManager::getInstance()->getSetList(); + + // Sort by release date + std::sort(shared_pointerses.begin(), shared_pointerses.end(), + [](const auto &a, const auto &b) { return a->getReleaseDate() > b->getReleaseDate(); }); + + int setsToPreactivate = 10; + int setsActivated = 0; + + for (const auto &shared_pointer : shared_pointerses) { + QString shortName = shared_pointer->getShortName(); + QString longName = shared_pointer->getLongName(); + + auto *button = new QPushButton(longName + " (" + shortName + ")", 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); + setButtons[shortName] = button; + + // Connect toggle signal + connect(button, &QPushButton::toggled, this, + [this, shortName](bool checked) { handleSetToggled(shortName, checked); }); + if (setsActivated < setsToPreactivate) { + setsActivated++; + activeSets[shortName] = true; + button->setChecked(true); + } + } + updateSetFilter(); + updateSetButtonsVisibility(); // Ensure visibility is updated initially +} + +void VisualDatabaseDisplaySetFilterWidget::updateSetButtonsVisibility() +{ + QString filterText = searchBox->text().trimmed().toLower(); + + for (const QString &setName : setButtons.keys()) { + QPushButton *buttonForSet = setButtons[setName]; + QString shortName = setName.toLower(); + QString longName = buttonForSet->text().toLower(); + bool alwaysVisible = activeSets.contains(setName) && activeSets[setName]; + bool visible = + alwaysVisible || filterText.isEmpty() || shortName.contains(filterText) || longName.contains(filterText); + buttonForSet->setVisible(visible); + } +} + +void VisualDatabaseDisplaySetFilterWidget::handleSetToggled(const QString &setShortName, bool active) +{ + activeSets[setShortName] = active; + + if (setButtons.contains(setShortName)) { + setButtons[setShortName]->setChecked(active); + } + + updateSetFilter(); +} + +void VisualDatabaseDisplaySetFilterWidget::updateSetFilter() +{ + // Clear existing filters related to sets + filterModel->blockSignals(true); + filterModel->filterTree()->blockSignals(true); + filterModel->clearFiltersOfType(CardFilter::Attr::AttrSet); + + if (exactMatchMode) { + // Exact Match: Only selected sets are allowed + QSet selectedSets; + for (const auto &set : activeSets.keys()) { + if (activeSets[set]) { + selectedSets.insert(set); + } + } + + if (!selectedSets.isEmpty()) { + // Require all selected sets (TypeAnd) + for (const auto &set : selectedSets) { + QString setString = set; + filterModel->addFilter(new CardFilter(setString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrSet)); + } + + // Exclude any other sets (TypeAndNot) + for (const auto &set : setButtons.keys()) { + if (!selectedSets.contains(set)) { + QString setString = set; + filterModel->addFilter( + new CardFilter(setString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrSet)); + } + } + } + } else { + // Default Includes Mode (TypeOr) - match any selected sets + for (const auto &set : activeSets.keys()) { + if (activeSets[set]) { + QString setString = set; + filterModel->addFilter(new CardFilter(setString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrSet)); + } + } + } + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); +} + +void VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel() +{ + // Clear activeSets + activeSets.clear(); + + // Read the current filters in filterModel + auto currentFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrSet); + + // Determine if we're in Exact Match mode or Includes mode + QSet selectedSets; + QSet excludedSets; + for (const auto &filter : currentFilters) { + if (filter->type() == CardFilter::Type::TypeAnd) { + selectedSets.insert(filter->term()); + } else if (filter->type() == CardFilter::Type::TypeAndNot) { + excludedSets.insert(filter->term()); + } else if (filter->type() == CardFilter::Type::TypeOr) { + selectedSets.insert(filter->term()); + } + } + + // Determine exact match mode based on filter structure + bool newExactMatchMode = !excludedSets.isEmpty(); + + if (newExactMatchMode != exactMatchMode) { + exactMatchMode = newExactMatchMode; + toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); + } + + // Sync button states with selected sets + for (const auto &key : setButtons.keys()) { + bool active = selectedSets.contains(key); + activeSets[key] = active; + setButtons[key]->setChecked(active); + } +} + +void VisualDatabaseDisplaySetFilterWidget::updateFilterMode(bool checked) +{ + exactMatchMode = checked; + toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); + updateSetFilter(); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.h new file mode 100644 index 000000000..41da57ef8 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.h @@ -0,0 +1,43 @@ +#ifndef VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H +#define VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H + +#include "../../../../game/filters/filter_tree_model.h" +#include "../general/layout_containers/flow_widget.h" + +#include +#include +#include +#include +#include +#include + +class VisualDatabaseDisplaySetFilterWidget : public QWidget +{ + Q_OBJECT +public: + explicit VisualDatabaseDisplaySetFilterWidget(QWidget *parent, FilterTreeModel *filterModel); + void createSetButtons(); + void updateSetButtonsVisibility(); + void handleSetToggled(const QString &setShortName, bool active); + + void updateSetFilter(); + void syncWithFilterModel(); + void updateFilterMode(bool checked); + +private: + FilterTreeModel *filterModel; + QMap allMainCardTypesWithCount; + QVBoxLayout *layout; + QLineEdit *searchBox; + FlowWidget *flowWidget; + QPushButton *toggleButton; // Mode switch button + + QMap activeMainTypes; // Track active filters + QMap typeButtons; // Store toggle buttons + QMap setButtons; // Store set filter buttons + QMap activeSets; // Track active set filters + + bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes" +}; + +#endif // VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp new file mode 100644 index 000000000..4b8fc21d8 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp @@ -0,0 +1,203 @@ +#include "visual_database_display_sub_type_filter_widget.h" + +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../game/filters/filter_tree.h" +#include "../../../../game/filters/filter_tree_model.h" + +#include +#include +#include +#include + +VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidget(QWidget *parent, + FilterTreeModel *_filterModel) + : QWidget(parent), filterModel(_filterModel) +{ + allSubCardTypesWithCount = CardDatabaseManager::getInstance()->getAllSubCardTypesWithCount(); + + setMinimumWidth(300); + + layout = new QVBoxLayout(this); + setLayout(layout); + + // Create and setup the spinbox + spinBox = new QSpinBox(this); + spinBox->setMinimum(1); + spinBox->setMaximum(getMaxSubTypeCount()); + spinBox->setValue(150); + layout->addWidget(spinBox); + connect(spinBox, QOverload::of(&QSpinBox::valueChanged), this, + &VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility); + + // Create search box + searchBox = new QLineEdit(this); + searchBox->setPlaceholderText(tr("Search subtypes...")); + layout->addWidget(searchBox); + connect(searchBox, &QLineEdit::textChanged, this, + &VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + flowWidget->setMaximumHeight(300); + layout->addWidget(flowWidget); + + // Toggle button setup (Exact Match / Includes mode) + toggleButton = new QPushButton(this); + toggleButton->setCheckable(true); + layout->addWidget(toggleButton); + connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplaySubTypeFilterWidget::updateFilterMode); + connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { + QTimer::singleShot(100, this, &VisualDatabaseDisplaySubTypeFilterWidget::syncWithFilterModel); + }); + + createSubTypeButtons(); // Populate buttons initially + updateFilterMode(false); // Initialize the toggle button text +} + +void VisualDatabaseDisplaySubTypeFilterWidget::createSubTypeButtons() +{ + // Iterate through sub types and create buttons + for (auto it = allSubCardTypesWithCount.begin(); it != allSubCardTypesWithCount.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); + typeButtons[it.key()] = button; + + // Connect toggle signal for each button + connect(button, &QPushButton::toggled, this, + [this, subType = it.key()](bool checked) { handleSubTypeToggled(subType, checked); }); + } + updateSubTypeButtonsVisibility(); // Ensure visibility is updated initially +} + +void VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility() +{ + int threshold = spinBox->value(); + QString filterText = searchBox->text().trimmed().toLower(); + + // Iterate through buttons and hide/disable those below the threshold + for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) { + QString subType = it.key().toLower(); + bool isActive = activeSubTypes.value(it.key(), false); + bool visible = isActive || (allSubCardTypesWithCount[it.key()] >= threshold && + (filterText.isEmpty() || subType.contains(filterText))); + + it.value()->setVisible(visible); + it.value()->setEnabled(visible); + } +} + +int VisualDatabaseDisplaySubTypeFilterWidget::getMaxSubTypeCount() const +{ + int maxCount = 1; + for (auto it = allSubCardTypesWithCount.begin(); it != allSubCardTypesWithCount.end(); ++it) { + maxCount = qMax(maxCount, it.value()); + } + return maxCount; +} + +void VisualDatabaseDisplaySubTypeFilterWidget::handleSubTypeToggled(const QString &subType, bool active) +{ + activeSubTypes[subType] = active; + + if (typeButtons.contains(subType)) { + typeButtons[subType]->setChecked(active); + } + + updateSubTypeFilter(); +} + +void VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeFilter() +{ + filterModel->blockSignals(true); + // Clear existing filters related to sub types + filterModel->clearFiltersOfType(CardFilter::Attr::AttrSubType); + + if (exactMatchMode) { + // Exact Match: Only selected sub types are allowed + QSet selectedTypes; + for (const auto &type : activeSubTypes.keys()) { + if (activeSubTypes[type]) { + selectedTypes.insert(type); + } + } + + if (!selectedTypes.isEmpty()) { + // Require all selected subtypes (TypeAnd) + for (const auto &type : selectedTypes) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrSubType)); + } + + // Exclude any other types (TypeAndNot) + for (const auto &type : typeButtons.keys()) { + if (!selectedTypes.contains(type)) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrSubType)); + } + } + } + } else { + // Default Includes Mode (TypeOr) - match any selected subtypes + for (const auto &type : activeSubTypes.keys()) { + if (activeSubTypes[type]) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrSubType)); + } + } + } + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); +} + +void VisualDatabaseDisplaySubTypeFilterWidget::updateFilterMode(bool checked) +{ + exactMatchMode = checked; + toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); + updateSubTypeFilter(); +} + +void VisualDatabaseDisplaySubTypeFilterWidget::syncWithFilterModel() +{ + // Temporarily block signals for each button to prevent toggling while updating button states + for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) { + it.value()->blockSignals(true); + } + + // Uncheck all buttons + for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) { + it.value()->setChecked(false); + } + + // Get active filters for sub types + QSet activeTypes; + for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrSubType)) { + if (filter->type() == CardFilter::Type::TypeAnd) { + activeTypes.insert(filter->term()); + } + } + + // Check the buttons for active types + for (const auto &type : activeTypes) { + activeSubTypes[type] = true; + if (typeButtons.contains(type)) { + typeButtons[type]->setChecked(true); + } + } + + // Re-enable signal emissions for each button + for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) { + it.value()->blockSignals(false); + } + + // Update the visibility of buttons + updateSubTypeButtonsVisibility(); +} diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h new file mode 100644 index 000000000..72a0a25bb --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h @@ -0,0 +1,43 @@ +#ifndef VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H +#define VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H + +#include "../../../../game/filters/filter_tree_model.h" +#include "../general/layout_containers/flow_widget.h" + +#include +#include +#include +#include +#include +#include + +class VisualDatabaseDisplaySubTypeFilterWidget : public QWidget +{ + Q_OBJECT +public: + explicit VisualDatabaseDisplaySubTypeFilterWidget(QWidget *parent, FilterTreeModel *filterModel); + void createSubTypeButtons(); + void updateSubTypeButtonsVisibility(); + int getMaxSubTypeCount() const; + + void handleSubTypeToggled(const QString &mainType, bool active); + void updateSubTypeFilter(); + void updateFilterMode(bool checked); + void syncWithFilterModel(); + +private: + FilterTreeModel *filterModel; + QMap allSubCardTypesWithCount; + QSpinBox *spinBox; + QVBoxLayout *layout; + QLineEdit *searchBox; + FlowWidget *flowWidget; + QPushButton *toggleButton; // Mode switch button + + QMap activeSubTypes; // Track active filters + QMap typeButtons; // Store toggle buttons + + bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes" +}; + +#endif // VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp new file mode 100644 index 000000000..d1e4b2db5 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp @@ -0,0 +1,313 @@ +#include "visual_database_display_widget.h" + +#include "../../../../deck/custom_line_edit.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../game/filters/filter_tree_model.h" +#include "../../../../game/filters/syntax_help.h" +#include "../../../../settings/cache_settings.h" +#include "../../../../utility/card_info_comparator.h" +#include "../../pixel_map_generator.h" +#include "../cards/card_info_picture_with_text_overlay_widget.h" +#include "../quick_settings/settings_button_widget.h" +#include "visual_database_display_color_filter_widget.h" +#include "visual_database_display_filter_save_load_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 + +VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, + AbstractTabDeckEditor *_deckEditor, + CardDatabaseModel *database_model, + CardDatabaseDisplayModel *database_display_model) + : QWidget(parent), deckEditor(_deckEditor), databaseModel(database_model), + databaseDisplayModel(database_display_model) +{ + cards = new QList; + connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this, + &VisualDatabaseDisplayWidget::modelDirty); + + // Set up main layout and widgets + setMinimumSize(0, 0); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + mainLayout = new QVBoxLayout(this); + setLayout(mainLayout); + mainLayout->setSpacing(1); + mainLayout->setContentsMargins(0, 0, 0, 0); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarPolicy::ScrollBarAsNeeded); + cardSizeWidget = new CardSizeWidget(this, flowWidget); + + searchContainer = new QWidget(this); + searchLayout = new QHBoxLayout(searchContainer); + searchContainer->setLayout(searchLayout); + + searchEdit = new SearchLineEdit(); + searchEdit->setObjectName("searchEdit"); + searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)")); + searchEdit->setClearButtonEnabled(true); + searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); + auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); + connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); }); + searchEdit->installEventFilter(&searchKeySignals); + + setFocusProxy(searchEdit); + setFocusPolicy(Qt::ClickFocus); + + filterModel = new FilterTreeModel(); + filterModel->setObjectName("filterModel"); + databaseDisplayModel->setFilterTree(filterModel->filterTree()); + + connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::searchModelChanged); + + searchKeySignals.setObjectName("searchKeySignals"); + connect(searchEdit, &QLineEdit::textChanged, this, &VisualDatabaseDisplayWidget::updateSearch); + /*connect(&searchKeySignals, SIGNAL(onEnter()), this, SLOT(actAddCard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actAddCard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltRBracket()), this, SLOT(actAddCardToSideboard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrementCard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard())); + connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard())); + connect(&searchKeySignals, SIGNAL(onCtrlC()), this, SLOT(copyDatabaseCellContents()));*/ + + databaseView = new QTreeView(this); + databaseView->setObjectName("databaseView"); + databaseView->setFocusProxy(searchEdit); + databaseView->setRootIsDecorated(false); + databaseView->setItemDelegate(nullptr); + databaseView->setSortingEnabled(true); + databaseView->sortByColumn(0, Qt::AscendingOrder); + databaseView->setModel(databaseDisplayModel); + databaseView->setVisible(false); + + searchEdit->setTreeView(databaseView); + + colorFilterWidget = new VisualDatabaseDisplayColorFilterWidget(this, filterModel); + + filterContainer = new QWidget(this); + filterContainerLayout = new QHBoxLayout(filterContainer); + filterContainer->setLayout(filterContainerLayout); + + clearFilterWidget = new QToolButton(); + clearFilterWidget->setFixedSize(32, 32); + clearFilterWidget->setIcon(QPixmap("theme:icons/delete")); + connect(clearFilterWidget, &QToolButton::clicked, this, [this] { filterModel->clear(); }); + + quickFilterSaveLoadWidget = new SettingsButtonWidget(this); + quickFilterSaveLoadWidget->setButtonIcon(QPixmap("theme:icons/lock")); + + saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel); + quickFilterNameWidget = new SettingsButtonWidget(this); + quickFilterNameWidget->setButtonIcon(QPixmap("theme:icons/rename")); + nameFilterWidget = new VisualDatabaseDisplayNameFilterWidget(this, deckEditor, filterModel); + mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel); + quickFilterSubTypeWidget = new SettingsButtonWidget(this); + quickFilterSubTypeWidget->setButtonIcon(QPixmap("theme:icons/player")); + subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel); + quickFilterSetWidget = new SettingsButtonWidget(this); + quickFilterSetWidget->setButtonIcon(QPixmap("theme:icons/scales")); + setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel); + filterContainer->setMaximumHeight(80); + + quickFilterSaveLoadWidget->addSettingsWidget(saveLoadWidget); + quickFilterNameWidget->addSettingsWidget(nameFilterWidget); + quickFilterSubTypeWidget->addSettingsWidget(subTypeFilterWidget); + quickFilterSetWidget->addSettingsWidget(setFilterWidget); + + filterContainerLayout->addWidget(mainTypeFilterWidget); + filterContainerLayout->addWidget(quickFilterSaveLoadWidget); + filterContainerLayout->addWidget(quickFilterNameWidget); + filterContainerLayout->addWidget(quickFilterSubTypeWidget); + filterContainerLayout->addWidget(quickFilterSetWidget); + + searchLayout->addWidget(colorFilterWidget); + searchLayout->addWidget(clearFilterWidget); + searchLayout->addWidget(searchEdit); + + mainLayout->addWidget(searchContainer); + + mainLayout->addWidget(filterContainer); + + mainLayout->addWidget(flowWidget); + + mainLayout->addWidget(cardSizeWidget); + + debounceTimer = new QTimer(this); + debounceTimer->setSingleShot(true); // Ensure it only fires once after the timeout + + connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::searchModelChanged); + + loadCardsTimer = new QTimer(this); + loadCardsTimer->setSingleShot(true); // Ensure it only fires once after the timeout + + connect(loadCardsTimer, &QTimer::timeout, this, [this]() { loadCurrentPage(); }); + loadCardsTimer->start(5000); +} + +void VisualDatabaseDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + loadCurrentPage(); +} + +void VisualDatabaseDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance) +{ + emit cardClickedDatabaseDisplay(event, instance); +} + +void VisualDatabaseDisplayWidget::onHover(const CardInfoPtr &hoveredCard) +{ + emit cardHoveredDatabaseDisplay(hoveredCard); +} + +void VisualDatabaseDisplayWidget::addCard(const CardInfoPtr &cardToAdd) +{ + cards->append(cardToAdd); + auto *display = new CardInfoPictureWithTextOverlayWidget(flowWidget, false); + display->setScaleFactor(cardSizeWidget->getSlider()->value()); + display->setCard(cardToAdd); + flowWidget->addWidget(display); + connect(display, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, &VisualDatabaseDisplayWidget::onClick); + connect(display, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &VisualDatabaseDisplayWidget::onHover); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, display, &CardInfoPictureWidget::setScaleFactor); +} + +void VisualDatabaseDisplayWidget::populateCards() +{ + int rowCount = databaseDisplayModel->rowCount(); + cards->clear(); + + // Calculate the start and end indices for the current page + int start = currentPage * cardsPerPage; + int end = qMin(start + cardsPerPage, rowCount); + + qCDebug(VisualDatabaseDisplayLog) << "Fetching from " << start << " to " << end << " cards"; + // Load more cards if we are at the end of the current list and can fetch more + if (end >= rowCount && databaseDisplayModel->canFetchMore(QModelIndex())) { + qCDebug(VisualDatabaseDisplayLog) << "We gotta load more"; + databaseDisplayModel->fetchMore(QModelIndex()); + } + + for (int row = start; row < end; ++row) { + qCDebug(VisualDatabaseDisplayLog) << "Adding " << row; + QModelIndex index = databaseDisplayModel->index(row, CardDatabaseModel::NameColumn); + QVariant name = databaseDisplayModel->data(index, Qt::DisplayRole); + qCDebug(VisualDatabaseDisplayLog) << name.toString(); + if (CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(name.toString())) { + addCard(info); + } else { + qCDebug(VisualDatabaseDisplayLog) << "Card not found in database!"; + } + } + currentPage++; +} + +void VisualDatabaseDisplayWidget::updateSearch(const QString &search) const +{ + databaseDisplayModel->setStringFilter(search); + QModelIndexList sel = databaseView->selectionModel()->selectedRows(); + if (sel.isEmpty() && databaseDisplayModel->rowCount()) + databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0), + QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); +} + +void VisualDatabaseDisplayWidget::searchModelChanged() +{ + // Clear the current page and prepare for new data + flowWidget->clearLayout(); // Clear existing cards + cards->clear(); // Clear the card list + // Reset scrollbar position to the top after loading new cards + if (QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar()) { + scrollBar->setValue(0); // Reset scrollbar to top + } + + currentPage = 0; + loadCurrentPage(); + qCDebug(VisualDatabaseDisplayLog) << "Search model changed"; +} + +void VisualDatabaseDisplayWidget::loadNextPage() +{ + // Calculate the start and end indices for the next page + int rowCount = databaseDisplayModel->rowCount(); + int start = currentPage * cardsPerPage; + int end = qMin(start + cardsPerPage, rowCount); + + // Load more cards if we are at the end of the current list and can fetch more + if (end >= rowCount && databaseDisplayModel->canFetchMore(QModelIndex())) { + databaseDisplayModel->fetchMore(QModelIndex()); + } + + // Load the next page of cards and add them to the flow widget + for (int row = start; row < end; ++row) { + QModelIndex index = databaseDisplayModel->index(row, CardDatabaseModel::NameColumn); + QVariant name = databaseDisplayModel->data(index, Qt::DisplayRole); + if (CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(name.toString())) { + addCard(info); + } else { + qCDebug(VisualDatabaseDisplayLog) << "Card " << name.toString() << " not found in database!"; + } + } + + // Update the current page + currentPage++; +} + +void VisualDatabaseDisplayWidget::loadCurrentPage() +{ + // Ensure only the initial page is loaded + if (currentPage == 0) { + // Only load the first page initially + qCDebug(VisualDatabaseDisplayLog) << "Loading the first page"; + populateCards(); + } else { + // If not the first page, just load the next page and append to the flow widget + loadNextPage(); + } +} + +void VisualDatabaseDisplayWidget::modelDirty() const +{ + debounceTimer->start(debounceTime); +} + +void VisualDatabaseDisplayWidget::sortCardList(const QStringList &properties, + Qt::SortOrder order = Qt::AscendingOrder) const +{ + CardInfoComparator comparator(properties, order); + std::sort(cards->begin(), cards->end(), comparator); +} + +void VisualDatabaseDisplayWidget::databaseDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + (void)topLeft; + (void)bottomRight; + qCDebug(VisualDatabaseDisplayLog) << "Database Data changed"; +} + +void VisualDatabaseDisplayWidget::wheelEvent(QWheelEvent *event) +{ + int totalRows = databaseDisplayModel->rowCount(); // Total number of cards + int nextPageStartIndex = (currentPage + 1) * cardsPerPage; + + // Handle scrolling down + if (event->angleDelta().y() < 0) { + // Check if the next page has any cards to load + if (nextPageStartIndex < totalRows) { + loadCurrentPage(); // Load the next page + event->accept(); // Accept the event as valid + return; + } + qCDebug(VisualDatabaseDisplayLog) << nextPageStartIndex << ":" << totalRows; + } + + // Prevent overscrolling when there's no more data to load + event->ignore(); +} diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.h new file mode 100644 index 000000000..6d75698dc --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.h @@ -0,0 +1,108 @@ +#ifndef VISUAL_DATABASE_DISPLAY_WIDGET_H +#define VISUAL_DATABASE_DISPLAY_WIDGET_H + +#include "../../../../deck/custom_line_edit.h" +#include "../../../../deck/deck_list_model.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_model.h" +#include "../../../../game/filters/filter_tree_model.h" +#include "../../../game_logic/key_signals.h" +#include "../../../tabs/abstract_tab_deck_editor.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" +#include "../general/layout_containers/overlap_control_widget.h" +#include "visual_database_display_color_filter_widget.h" +#include "visual_database_display_filter_save_load_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 + +inline Q_LOGGING_CATEGORY(VisualDatabaseDisplayLog, "visual_database_display"); + +class VisualDatabaseDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VisualDatabaseDisplayWidget(QWidget *parent, + AbstractTabDeckEditor *deckEditor, + CardDatabaseModel *database_model, + CardDatabaseDisplayModel *database_display_model); + + void adjustCardsPerPage(); + void populateCards(); + void loadNextPage(); + void loadCurrentPage(); + void sortCardList(const QStringList &properties, Qt::SortOrder order) const; + void setDeckList(const DeckList &new_deck_list_model); + + QWidget *searchContainer; + QHBoxLayout *searchLayout; + SearchLineEdit *searchEdit; + FilterTreeModel *filterModel; + VisualDatabaseDisplayColorFilterWidget *colorFilterWidget; + +public slots: + void searchModelChanged(); + +signals: + void cardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + void cardHoveredDatabaseDisplay(CardInfoPtr hoveredCard); + +protected slots: + void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + void onHover(const CardInfoPtr &hoveredCard); + void addCard(const CardInfoPtr &cardToAdd); + void databaseDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void wheelEvent(QWheelEvent *event) override; + void modelDirty() const; + void updateSearch(const QString &search) const; + +private: + QToolButton *clearFilterWidget; + QWidget *filterContainer; + QHBoxLayout *filterContainerLayout; + SettingsButtonWidget *quickFilterSaveLoadWidget; + VisualDatabaseDisplayFilterSaveLoadWidget *saveLoadWidget; + SettingsButtonWidget *quickFilterNameWidget; + VisualDatabaseDisplayNameFilterWidget *nameFilterWidget; + VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget; + SettingsButtonWidget *quickFilterSubTypeWidget; + VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget; + SettingsButtonWidget *quickFilterSetWidget; + VisualDatabaseDisplaySetFilterWidget *setFilterWidget; + KeySignals searchKeySignals; + AbstractTabDeckEditor *deckEditor; + CardDatabaseModel *databaseModel; + CardDatabaseDisplayModel *databaseDisplayModel; + QTreeView *databaseView; + QList *cards; + QVBoxLayout *mainLayout; + QScrollArea *scrollArea; + FlowWidget *flowWidget; + QWidget *overlapCategories; + QVBoxLayout *overlapCategoriesLayout; + OverlapControlWidget *overlapControlWidget; + CardSizeWidget *cardSizeWidget; + QTimer *debounceTimer; + QTimer *loadCardsTimer; + + int debounceTime = 300; // in Ms + int currentPage = 0; // Current page index + int cardsPerPage = 100; // Number of cards per page + +protected: + void resizeEvent(QResizeEvent *event) override; +}; + +#endif // VISUAL_DATABASE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_filter_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_filter_display_widget.cpp new file mode 100644 index 000000000..a9d15fd1a --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_filter_display_widget.cpp @@ -0,0 +1,80 @@ +#include "visual_database_filter_display_widget.h" + +#include "../../../../settings/cache_settings.h" + +#include +#include +#include +#include +#include +#include + +FilterDisplayWidget::FilterDisplayWidget(QWidget *parent, const QString &filename, FilterTreeModel *_filterModel) + : QWidget(parent), filterFilename(filename), filterModel(_filterModel) +{ + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + // Create layout + auto *layout = new QHBoxLayout(this); + layout->setContentsMargins(5, 5, 5, 5); + layout->setSpacing(5); + + // Filter button (displays filename) + filterButton = new QPushButton(filterFilename, this); + layout->addWidget(filterButton); + + // Create close button (inside the filter button) + closeButton = new QPushButton("x", this); + closeButton->setFixedSize(20, 20); // Small square close button + closeButton->setFocusPolicy(Qt::NoFocus); + layout->addWidget(closeButton); + + // Connect the filter button for loading the filter + connect(filterButton, &QPushButton::clicked, this, &FilterDisplayWidget::loadFilter); + + // Connect the close button for deleting the filter + connect(closeButton, &QPushButton::clicked, this, &FilterDisplayWidget::deleteFilter); +} + +QSize FilterDisplayWidget::sizeHint() const +{ + // Calculate the size based on the filter name and the close button + QFontMetrics fm(font()); + int textWidth = fm.horizontalAdvance(filterFilename); + int width = textWidth + 30 + 16; // Space for the filename and close button + int height = fm.height() + 10; // Height based on font size + padding + + return QSize(width, height); +} + +void FilterDisplayWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + emit filterClicked(); + } + QWidget::mousePressEvent(event); +} + +void FilterDisplayWidget::loadFilter() +{ + // Trigger the loading of the filter + emit filterLoadRequested(filterFilename); +} + +void FilterDisplayWidget::deleteFilter() +{ + // Show confirmation dialog before deleting the filter + QMessageBox::StandardButton reply = QMessageBox::question( + this, tr("Confirm Delete"), tr("Are you sure you want to delete the filter '%1'?").arg(filterFilename), + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) { + // If confirmed, delete the filter + QString filePath = SettingsCache::instance().getFiltersPath() + QDir::separator() + filterFilename; + QFile file(filePath); + if (file.remove()) { + emit filterDeleted(filterFilename); // Emit signal for deletion + delete this; // Delete this widget + } else { + QMessageBox::warning(this, tr("Delete Failed"), tr("Failed to delete filter '%1'.").arg(filterFilename)); + } + } +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_filter_display_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_filter_display_widget.h new file mode 100644 index 000000000..f8aabd144 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_filter_display_widget.h @@ -0,0 +1,40 @@ +#ifndef VISUAL_DATABASE_FILTER_DISPLAY_WIDGET_H +#define VISUAL_DATABASE_FILTER_DISPLAY_WIDGET_H + +#include +#include +#include +#include + +class FilterTreeModel; + +class FilterDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit FilterDisplayWidget(QWidget *parent, const QString &filename, FilterTreeModel *_filterModel); + ~FilterDisplayWidget() = default; + + QSize sizeHint() const override; + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private slots: + void loadFilter(); + void deleteFilter(); + +signals: + void filterClicked(); + void filterLoadRequested(const QString &filename); + void filterDeleted(const QString &filename); + +private: + QString filterFilename; + FilterTreeModel *filterModel; + QPushButton *filterButton; // Button for the filter text + QPushButton *closeButton; // Close button to delete the filter +}; + +#endif // VISUAL_DATABASE_FILTER_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp new file mode 100644 index 000000000..f6b73b0d5 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -0,0 +1,105 @@ +#include "visual_deck_editor_sample_hand_widget.h" + +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../cards/card_info_picture_widget.h" + +#include + +VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + resetButton = new QPushButton(this); + connect(resetButton, SIGNAL(clicked()), this, SLOT(updateDisplay())); + layout->addWidget(resetButton); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + layout->addWidget(flowWidget); + + for (CardInfoPtr card : getRandomCards(7)) { + auto displayWidget = new CardInfoPictureWidget(this); + displayWidget->setCard(card); + flowWidget->addWidget(displayWidget); + } +} + +void VisualDeckEditorSampleHandWidget::retranslateUi() +{ + resetButton->setText(tr("Reset")); +} + +void VisualDeckEditorSampleHandWidget::setDeckModel(DeckListModel *deckModel) +{ + deckListModel = deckModel; + // connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorSampleHandWidget::updateDisplay); + updateDisplay(); +} + +void VisualDeckEditorSampleHandWidget::updateDisplay() +{ + flowWidget->clearLayout(); + for (CardInfoPtr card : getRandomCards(7)) { + auto displayWidget = new CardInfoPictureWidget(this); + displayWidget->setCard(card); + flowWidget->addWidget(displayWidget); + } +} + +QList VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGet) +{ + QList mainDeckCards; + QList randomCards; + if (!deckListModel) + return randomCards; + DeckList *decklist = deckListModel->getDeckList(); + if (!decklist) + return randomCards; + InnerDecklistNode *listRoot = decklist->getRoot(); + if (!listRoot) + return randomCards; + + // Collect all cards in the main deck, allowing duplicates based on their count + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + if (!currentZone) + continue; + if (currentZone->getName() != DECK_ZONE_MAIN) + continue; // Only process the main deck + + 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) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + currentCard->getName(), currentCard->getCardProviderId()); + if (info) { + mainDeckCards.append(info); + } + } + } + } + + if (mainDeckCards.isEmpty()) + return randomCards; + + // Shuffle the deck + std::random_device rd; + std::mt19937 rng(rd()); + std::shuffle(mainDeckCards.begin(), mainDeckCards.end(), rng); + + // Select amountToGet cards + + for (int i = 0; i < qMin(amountToGet, mainDeckCards.size()); ++i) { + randomCards.append(mainDeckCards.at(i)); + } + + std::sort(randomCards.begin(), randomCards.end(), + [](const CardInfoPtr &a, const CardInfoPtr &b) { return a->getCmc() < b->getCmc(); }); + + return randomCards; +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h new file mode 100644 index 000000000..44dc04cab --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h @@ -0,0 +1,29 @@ +#ifndef VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H +#define VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../general/layout_containers/flow_widget.h" + +#include +#include + +class VisualDeckEditorSampleHandWidget : public QWidget +{ + Q_OBJECT +public: + VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *deckListModel); + QList getRandomCards(int amountToGet); + +public slots: + void updateDisplay(); + void setDeckModel(DeckListModel *deckModel); + void retranslateUi(); + +private: + DeckListModel *deckListModel; + QVBoxLayout *layout; + QPushButton *resetButton; + FlowWidget *flowWidget; +}; + +#endif // VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp new file mode 100644 index 000000000..cf9c95071 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp @@ -0,0 +1,315 @@ +#include "visual_deck_editor_widget.h" + +#include "../../../../deck/deck_list_model.h" +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_completer_proxy_model.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../game/cards/card_database_model.h" +#include "../../../../game/cards/card_search_model.h" +#include "../../../../main.h" +#include "../../../../utility/card_info_comparator.h" +#include "../../layouts/overlap_layout.h" +#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 +#include +#include +#include +#include +#include +#include + +VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_deckListModel) + : QWidget(parent), deckListModel(_deckListModel) +{ + connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorWidget::decklistDataChanged); + + // The Main Widget and Main Layout, which contain a single Widget: The Scroll Area + setMinimumSize(0, 0); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + mainLayout = new QVBoxLayout(this); + setLayout(mainLayout); + mainLayout->setContentsMargins(9, 0, 9, 5); + mainLayout->setSpacing(0); + + searchContainer = new QWidget(this); + searchLayout = new QHBoxLayout(searchContainer); + searchContainer->setLayout(searchLayout); + + searchBar = new QLineEdit(this); + connect(searchBar, &QLineEdit::returnPressed, this, [=, this]() { + if (!searchBar->hasFocus()) + return; + + CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(searchBar->text()); + if (card) { + emit cardAdditionRequested(card); + } + }); + + setFocusProxy(searchBar); + setFocusPolicy(Qt::ClickFocus); + + cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this); + cardDatabaseDisplayModel = new CardDatabaseDisplayModel(this); + cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel); + CardSearchModel *searchModel = new CardSearchModel(cardDatabaseDisplayModel, this); + + proxyModel = new CardCompleterProxyModel(this); + proxyModel->setSourceModel(searchModel); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setFilterRole(Qt::DisplayRole); + + completer = new QCompleter(proxyModel, this); + completer->setCompletionRole(Qt::DisplayRole); + completer->setCompletionMode(QCompleter::PopupCompletion); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setFilterMode(Qt::MatchContains); + completer->setMaxVisibleItems(15); + searchBar->setCompleter(completer); + + // Update suggestions dynamically + connect(searchBar, &QLineEdit::textEdited, searchModel, &CardSearchModel::updateSearchResults); + connect(searchBar, &QLineEdit::textEdited, this, [=, this](const QString &text) { + // Ensure substring matching + QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; + proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + + if (!text.isEmpty()) { + completer->complete(); // Force the dropdown to appear + } + }); + + connect(completer, static_cast(&QCompleter::activated), this, + [=, this](const QString &completion) { + // Prevent the text from changing automatically when navigating with arrow keys + if (searchBar->text() != completion) { + searchBar->setText(completion); // Set the completion explicitly + searchBar->setCursorPosition(searchBar->text().length()); // Move cursor to the end + } + }); + + // Ensure that the text stays consistent during selection + connect(searchBar, &QLineEdit::textEdited, this, [=, this](const QString &text) { + if (searchBar->hasFocus() && !searchBar->completer()->popup()->isVisible()) { + // Allow text to change when typing, but not when navigating the completer + QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; + proxyModel->setFilterRegularExpression( + QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + } + }); + + // Search button functionality + searchPushButton = new QPushButton(this); + connect(searchPushButton, &QPushButton::clicked, this, [=, this]() { + CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(searchBar->text()); + if (card) { + emit cardAdditionRequested(card); + } + }); + + searchLayout->addWidget(searchBar); + searchLayout->addWidget(searchPushButton); + + mainLayout->addWidget(searchContainer); + + groupAndSortContainer = new QWidget(this); + groupAndSortLayout = new QHBoxLayout(groupAndSortContainer); + groupAndSortLayout->setAlignment(Qt::AlignLeft); + groupAndSortContainer->setLayout(groupAndSortLayout); + + groupByComboBox = new QComboBox(); + QStringList groupProperties = {"maintype", "colors", "cmc", "name"}; + groupByComboBox->addItems(groupProperties); + groupByComboBox->setMinimumWidth(300); + connect(groupByComboBox, QOverload::of(&QComboBox::currentTextChanged), this, + &VisualDeckEditorWidget::actChangeActiveGroupCriteria); + actChangeActiveGroupCriteria(); + + 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(groupByComboBox); + groupAndSortLayout->addWidget(sortCriteriaButton); + groupAndSortLayout->addWidget(displayTypeButton); + + scrollArea = new QScrollArea(); + scrollArea->setWidgetResizable(true); + scrollArea->setMinimumSize(0, 0); + + // Set scrollbar policies + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + zoneContainer = new QWidget(scrollArea); + zoneContainerLayout = new QVBoxLayout(zoneContainer); + zoneContainer->setLayout(zoneContainerLayout); + scrollArea->addScrollBarWidget(zoneContainer, Qt::AlignHCenter); + scrollArea->setWidget(zoneContainer); + + updateZoneWidgets(); + + cardSizeWidget = new CardSizeWidget(this); + + mainLayout->addWidget(groupAndSortContainer); + mainLayout->addWidget(scrollArea); + mainLayout->addWidget(cardSizeWidget); + + retranslateUi(); +} + +void VisualDeckEditorWidget::retranslateUi() +{ + sortLabel->setText(tr("Click and drag to change the sort order within the groups")); + searchPushButton->setText(tr("Quick search and add card")); + displayTypeButton->setText(tr("Flat Layout")); +} + +void VisualDeckEditorWidget::updateZoneWidgets() +{ + addZoneIfDoesNotExist(); + deleteZoneIfDoesNotExist(); +} + +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: + emit displayTypeChanged("flat"); + displayTypeButton->setText(tr("Flat Layout")); + break; + case DisplayType::Overlap: + emit displayTypeChanged("overlap"); + displayTypeButton->setText(tr("Overlap Layout")); + break; + } +} + +void VisualDeckEditorWidget::addZoneIfDoesNotExist() +{ + QList cardZoneDisplayWidgets = + zoneContainer->findChildren(); + for (const QString &zone : *deckListModel->getZones()) { + bool found = false; + for (DeckCardZoneDisplayWidget *displayWidget : cardZoneDisplayWidgets) { + if (displayWidget->zoneName == zone) { + found = true; + break; + } + } + + if (found) { + continue; + } + DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( + zoneContainer, deckListModel, zone, activeGroupCriteria, activeSortCriteria, 20, 10, cardSizeWidget); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); + connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); + connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); + connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::refreshDisplayType); + zoneContainerLayout->addWidget(zoneDisplayWidget); + } +} + +void VisualDeckEditorWidget::deleteZoneIfDoesNotExist() +{ + QList cardZoneDisplayWidgets = + zoneContainer->findChildren(); + for (DeckCardZoneDisplayWidget *displayWidget : cardZoneDisplayWidgets) { + bool found = false; + for (const QString &zone : *deckListModel->getZones()) { + if (displayWidget->zoneName == zone) { + found = true; + break; + } + } + + if (!found) { + zoneContainerLayout->removeWidget(displayWidget); + } + } +} + +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::decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight) +{ + // Might use these at some point. + Q_UNUSED(topLeft); + Q_UNUSED(bottomRight); + // Necessary to delay this in this manner else the updateDisplay will nuke widgets while their onClick event + // hasn't returned yet. Interval of 0 means QT will schedule this after the current event loop has finished. + updateZoneWidgets(); +} + +void VisualDeckEditorWidget::onHover(CardInfoPtr hoveredCard) +{ + emit activeCardChanged(hoveredCard); +} + +void VisualDeckEditorWidget::onCardClick(QMouseEvent *event, + CardInfoPictureWithTextOverlayWidget *instance, + QString zoneName) +{ + emit cardClicked(event, instance, zoneName); +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h new file mode 100644 index 000000000..3b644ff1e --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -0,0 +1,88 @@ +#ifndef VISUAL_DECK_EDITOR_H +#define VISUAL_DECK_EDITOR_H + +#include "../../../../deck/deck_list_model.h" +#include "../../../../game/cards/card_completer_proxy_model.h" +#include "../../../../game/cards/card_database.h" +#include "../../../../game/cards/card_database_model.h" +#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" + +#include +#include +#include +#include +#include + +enum class DisplayType +{ + Flat, + Overlap +}; + +class VisualDeckEditorWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VisualDeckEditorWidget(QWidget *parent, DeckListModel *deckListModel); + void retranslateUi(); + void resizeEvent(QResizeEvent *event) override; + + void setDeckList(const DeckList &_deckListModel); + + QLineEdit *searchBar; + CardSizeWidget *cardSizeWidget; + +public slots: + void decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight); + void updateZoneWidgets(); + void updateDisplayType(); + void addZoneIfDoesNotExist(); + void deleteZoneIfDoesNotExist(); + +signals: + void activeCardChanged(CardInfoPtr activeCard); + void activeGroupCriteriaChanged(QString activeGroupCriteria); + void activeSortCriteriaChanged(QStringList activeSortCriteria); + void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); + void cardAdditionRequested(CardInfoPtr card); + void displayTypeChanged(QString displayType); + +protected slots: + void onHover(CardInfoPtr hoveredCard); + void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); + void actChangeActiveGroupCriteria(); + void actChangeActiveSortCriteria(); + +private: + DeckListModel *deckListModel; + QVBoxLayout *mainLayout; + QWidget *searchContainer; + QHBoxLayout *searchLayout; + CardDatabaseModel *cardDatabaseModel; + CardDatabaseDisplayModel *cardDatabaseDisplayModel; + CardCompleterProxyModel *proxyModel; + QCompleter *completer; + QPushButton *searchPushButton; + DisplayType currentDisplayType = DisplayType::Overlap; + QPushButton *displayTypeButton; + QWidget *groupAndSortContainer; + QHBoxLayout *groupAndSortLayout; + QComboBox *groupByComboBox; + QString activeGroupCriteria = "maintype"; + SettingsButtonWidget *sortCriteriaButton; + QLabel *sortLabel; + QListWidget *sortByListWidget; + QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"}; + QScrollArea *scrollArea; + QWidget *zoneContainer; + QVBoxLayout *zoneContainerLayout; + // OverlapControlWidget *overlapControlWidget; + QWidget *container; +}; + +#endif // VISUAL_DECK_EDITOR_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp index 79fcc01fb..b048b2372 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp @@ -16,7 +16,7 @@ DeckPreviewColorIdentityFilterWidget::DeckPreviewColorIdentityFilterWidget(Visua QString fullColorIdentity = "WUBRG"; for (const QChar &color : fullColorIdentity) { auto *manaSymbol = new ManaSymbolWidget(this, color, false, true); - manaSymbol->setMaximumWidth(25); + manaSymbol->setFixedWidth(25); layout->addWidget(manaSymbol); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index 97825ff21..8c4723c37 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -106,16 +106,16 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() canAddTags = true; if (conversionDialog.dontAskAgain()) { - SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Unchecked); - SettingsCache::instance().setVisualDeckStorageAlwaysConvert(Qt::CheckState::Checked); + SettingsCache::instance().setVisualDeckStoragePromptForConversion(false); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true); } } else { - SettingsCache::instance().setVisualDeckStorageAlwaysConvert(Qt::CheckState::Unchecked); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false); if (conversionDialog.dontAskAgain()) { - SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Unchecked); + SettingsCache::instance().setVisualDeckStoragePromptForConversion(false); } else { - SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Checked); + SettingsCache::instance().setVisualDeckStoragePromptForConversion(true); } } } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index 35f434bdf..f3bebbb90 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -31,7 +31,8 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent, &VisualDeckStorageTagFilterWidget::refreshTags); deckLoader->loadFromFileAsync(filePath, DeckLoader::getFormatFromName(filePath), false); - bannerCardDisplayWidget = new DeckPreviewCardPictureWidget(this); + bannerCardDisplayWidget = + new DeckPreviewCardPictureWidget(this, false, visualDeckStorageWidget->deckPreviewSelectionAnimationEnabled); connect(bannerCardDisplayWidget, &DeckPreviewCardPictureWidget::imageClicked, this, &DeckPreviewWidget::imageClickedEvent); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp index a1c75644c..4e879bd65 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -125,6 +125,10 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare tagFilterWidget = new VisualDeckStorageTagFilterWidget(this); updateTagsVisibility(SettingsCache::instance().getVisualDeckStorageShowTagFilter()); + deckPreviewSelectionAnimationEnabled = SettingsCache::instance().getVisualDeckStorageSelectionAnimation(); + connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageSelectionAnimationChanged, this, + &VisualDeckStorageWidget::updateSelectionAnimationEnabled); + // deck area scrollArea = new QScrollArea(this); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); @@ -211,14 +215,18 @@ void VisualDeckStorageWidget::createRootFolderWidget() scrollArea->setWidget(folderWidget); // this automatically destroys the old folderWidget scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width()); scrollArea->widget()->adjustSize(); - reapplySortAndFilters(); + + /* We have to schedule a QTimer here so that the sorting logic doesn't try to access widgets that haven't been + * processed by the event loop yet. Otherwise, deck sorting will intermittently segfault on some systems. + */ + QTimer::singleShot(0, this, &VisualDeckStorageWidget::reapplySortAndFilters); } void VisualDeckStorageWidget::updateShowFolders(bool enabled) { if (folderWidget) { folderWidget->updateShowFolders(enabled); - reapplySortAndFilters(); + QTimer::singleShot(0, this, &VisualDeckStorageWidget::reapplySortAndFilters); } } @@ -266,4 +274,9 @@ void VisualDeckStorageWidget::updateTagsVisibility(const bool visible) } else { tagFilterWidget->setHidden(true); } +} + +void VisualDeckStorageWidget::updateSelectionAnimationEnabled(const bool enabled) +{ + deckPreviewSelectionAnimationEnabled = enabled; } \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h index acab16710..b52086a4f 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -31,6 +31,7 @@ public: CardSizeWidget *cardSizeWidget; VisualDeckStorageTagFilterWidget *tagFilterWidget; + bool deckPreviewSelectionAnimationEnabled; public slots: void createRootFolderWidget(); // Refresh the display of cards based on the current sorting option @@ -39,6 +40,7 @@ public slots: void updateColorFilter(); void updateSearchFilter(); void updateTagsVisibility(bool visible); + void updateSelectionAnimationEnabled(bool enabled); void updateSortOrder(); void resizeEvent(QResizeEvent *event) override; void showEvent(QShowEvent *event) override; diff --git a/cockatrice/src/client/ui/window_main.cpp b/cockatrice/src/client/ui/window_main.cpp index 89a686f61..7dabc96c6 100644 --- a/cockatrice/src/client/ui/window_main.cpp +++ b/cockatrice/src/client/ui/window_main.cpp @@ -574,6 +574,9 @@ void MainWindow::registerError(Response::ResponseCode r, QString reasonStr, quin case Response::RespRegistrationFailed: QMessageBox::critical(this, tr("Error"), tr("Registration failed for a technical problem on the server.")); break; + case Response::RespNotConnected: + QMessageBox::critical(this, tr("Error"), tr("The connection to the server has been lost.")); + break; default: QMessageBox::critical(this, tr("Error"), tr("Unknown registration error: %1").arg(static_cast(r)) + diff --git a/cockatrice/src/deck/deck_list_model.cpp b/cockatrice/src/deck/deck_list_model.cpp index 0005777c9..5d8cb60f3 100644 --- a/cockatrice/src/deck/deck_list_model.cpp +++ b/cockatrice/src/deck/deck_list_model.cpp @@ -263,6 +263,8 @@ bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, con emitRecursiveUpdates(index); deckList->refreshDeckHash(); + + emit dataChanged(index, index); return true; } @@ -292,7 +294,7 @@ bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent) } else { emitRecursiveUpdates(parent); } - + emit dataChanged(parent, parent); return true; } @@ -483,6 +485,94 @@ void DeckListModel::setDeckList(DeckLoader *_deck) rebuildTree(); } +QList DeckListModel::getCardsAsCardInfoPtrs() 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) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + currentCard->getName(), currentCard->getCardProviderId()); + if (info) { + cards.append(info); + } else { + qDebug() << "Card not found in database!"; + } + } + } + } + return cards; +} + +QList DeckListModel::getCardsAsCardInfoPtrsForZone(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) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + currentCard->getName(), currentCard->getCardProviderId()); + if (info) { + cards.append(info); + } else { + qDebug() << "Card not found in database!"; + } + } + } + } + } + return cards; +} + +QList *DeckListModel::getZones() const +{ + QList *zones = new QList(); + DeckList *decklist = getDeckList(); + if (!decklist) { + return zones; + } + InnerDecklistNode *listRoot = decklist->getRoot(); + if (!listRoot) + return zones; + + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + if (!currentZone) + continue; + zones->append(currentZone->getName()); + } + return zones; +} + void DeckListModel::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node) { const int totalColumns = 2; diff --git a/cockatrice/src/deck/deck_list_model.h b/cockatrice/src/deck/deck_list_model.h index c7194a5da..94b323e18 100644 --- a/cockatrice/src/deck/deck_list_model.h +++ b/cockatrice/src/deck/deck_list_model.h @@ -1,7 +1,7 @@ #ifndef DECKLISTMODEL_H #define DECKLISTMODEL_H -#include "../game/cards/card_database.h" +#include "../game/cards/card_info.h" #include "decklist.h" #include @@ -110,6 +110,9 @@ public: return deckList; } void setDeckList(DeckLoader *_deck); + QList getCardsAsCardInfoPtrs() const; + QList getCardsAsCardInfoPtrsForZone(QString zoneName) const; + QList *getZones() const; private: DeckLoader *deckList; diff --git a/cockatrice/src/dialogs/dlg_connect.cpp b/cockatrice/src/dialogs/dlg_connect.cpp index 496e26326..d8cf11487 100644 --- a/cockatrice/src/dialogs/dlg_connect.cpp +++ b/cockatrice/src/dialogs/dlg_connect.cpp @@ -34,11 +34,12 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) btnRefreshServers->setToolTip(tr("Refresh the server list with known public servers")); btnRefreshServers->setFixedWidth(30); - connect(hps, SIGNAL(sigPublicServersDownloadedSuccessfully()), this, SLOT(rebuildComboBoxList())); - connect(hps, SIGNAL(sigPublicServersDownloadedUnsuccessfully(int)), this, SLOT(rebuildComboBoxList(int))); - connect(btnRefreshServers, SIGNAL(released()), this, SLOT(downloadThePublicServers())); + connect(hps, &HandlePublicServers::sigPublicServersDownloadedSuccessfully, this, [this] { rebuildComboBoxList(); }); + connect(hps, &HandlePublicServers::sigPublicServersDownloadedUnsuccessfully, this, + &DlgConnect::rebuildComboBoxList); + connect(btnRefreshServers, &QPushButton::released, this, &DlgConnect::downloadThePublicServers); - connect(this, SIGNAL(sigPublicServersDownloaded()), this, SLOT(rebuildComboBoxList())); + connect(this, &DlgConnect::sigPublicServersDownloaded, this, [this] { rebuildComboBoxList(); }); preRebuildComboBoxList(); newHostButton = new QRadioButton(tr("New Host"), this); @@ -102,17 +103,17 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) btnForgotPassword->setIcon(QPixmap("theme:icons/forgot_password")); btnForgotPassword->setToolTip(tr("Reset Password")); btnForgotPassword->setFixedWidth(30); - connect(btnForgotPassword, SIGNAL(released()), this, SLOT(actForgotPassword())); + connect(btnForgotPassword, &QPushButton::released, this, &DlgConnect::actForgotPassword); forgotPasswordLabel = new QLabel(tr("Forgot password?")); forgotPasswordLabel->setBuddy(btnForgotPassword); btnConnect = new QPushButton(tr("&Connect")); - connect(btnConnect, SIGNAL(released()), this, SLOT(actOk())); + connect(btnConnect, &QPushButton::released, this, &DlgConnect::actOk); auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel); buttonBox->addButton(btnConnect, QDialogButtonBox::AcceptRole); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgConnect::reject); newHolderLayout = new QHBoxLayout; newHolderLayout->addWidget(previousHosts); @@ -171,12 +172,12 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) setFixedHeight(sizeHint().height()); setMinimumWidth(300); - connect(previousHostButton, SIGNAL(toggled(bool)), this, SLOT(previousHostSelected(bool))); - connect(newHostButton, SIGNAL(toggled(bool)), this, SLOT(newHostSelected(bool))); + connect(previousHostButton, &QRadioButton::toggled, this, &DlgConnect::previousHostSelected); + connect(newHostButton, &QRadioButton::toggled, this, &DlgConnect::newHostSelected); previousHostButton->setChecked(true); - connect(previousHosts, SIGNAL(currentTextChanged(const QString &)), this, SLOT(updateDisplayInfo(const QString &))); + connect(previousHosts, &QComboBox::currentTextChanged, this, &DlgConnect::updateDisplayInfo); playernameEdit->setFocus(); } diff --git a/cockatrice/src/dialogs/dlg_create_game.cpp b/cockatrice/src/dialogs/dlg_create_game.cpp index 02f55447b..e601b1b95 100644 --- a/cockatrice/src/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/dialogs/dlg_create_game.cpp @@ -117,7 +117,7 @@ void DlgCreateGame::sharedCtor() grid->addWidget(rememberGameSettings, 3, 0); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateGame::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(grid); @@ -158,8 +158,8 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap &_gameType clearButton = new QPushButton(tr("&Clear")); buttonBox->addButton(QDialogButtonBox::Cancel); buttonBox->addButton(clearButton, QDialogButtonBox::ActionRole); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOK())); - connect(clearButton, SIGNAL(clicked()), this, SLOT(actReset())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateGame::actOK); + connect(clearButton, &QPushButton::clicked, this, &DlgCreateGame::actReset); setWindowTitle(tr("Create game")); } @@ -204,7 +204,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMapsetChecked(types.contains(gameTypeIterator.key())); } - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateGame::accept); setWindowTitle(tr("Game information")); } @@ -279,7 +279,7 @@ void DlgCreateGame::actOK() SettingsCache::instance().setGameTypes(_gameTypes); } PendingCommand *pend = room->prepareRoomCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(checkResponse(Response))); + connect(pend, &PendingCommand::finished, this, &DlgCreateGame::checkResponse); room->sendRoomCommand(pend); buttonBox->setEnabled(false); diff --git a/cockatrice/src/dialogs/dlg_create_token.cpp b/cockatrice/src/dialogs/dlg_create_token.cpp index 25d549868..7b3db2773 100644 --- a/cockatrice/src/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/dialogs/dlg_create_token.cpp @@ -36,7 +36,7 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa nameEdit->setFocus(); }); - connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateSearch(const QString &))); + connect(nameEdit, &QLineEdit::textChanged, this, &DlgCreateToken::updateSearch); nameLabel->setBuddy(nameEdit); colorLabel = new QLabel(tr("C&olor:")); @@ -82,9 +82,9 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel); chooseTokenFromAllRadioButton = new QRadioButton(tr("Show &all tokens")); - connect(chooseTokenFromAllRadioButton, SIGNAL(toggled(bool)), this, SLOT(actChooseTokenFromAll(bool))); + connect(chooseTokenFromAllRadioButton, &QRadioButton::toggled, this, &DlgCreateToken::actChooseTokenFromAll); chooseTokenFromDeckRadioButton = new QRadioButton(tr("Show tokens from this &deck")); - connect(chooseTokenFromDeckRadioButton, SIGNAL(toggled(bool)), this, SLOT(actChooseTokenFromDeck(bool))); + connect(chooseTokenFromDeckRadioButton, &QRadioButton::toggled, this, &DlgCreateToken::actChooseTokenFromDeck); QByteArray deckHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState(); chooseTokenView = new QTreeView; @@ -104,9 +104,9 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa chooseTokenView->header()->hideSection(1); // Sets chooseTokenView->header()->hideSection(2); // Mana Cost chooseTokenView->header()->setSectionResizeMode(5, QHeaderView::ResizeToContents); // Color(s) - connect(chooseTokenView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), this, - SLOT(tokenSelectionChanged(QModelIndex, QModelIndex))); - connect(chooseTokenView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(actOk())); + connect(chooseTokenView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, + &DlgCreateToken::tokenSelectionChanged); + connect(chooseTokenView, &QTreeView::doubleClicked, this, &DlgCreateToken::actOk); if (predefinedTokens.isEmpty()) { chooseTokenFromAllRadioButton->setChecked(true); @@ -135,8 +135,8 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa hbox->setColumnStretch(1, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(actReject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateToken::actOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateToken::actReject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(hbox); @@ -224,27 +224,11 @@ void DlgCreateToken::actReject() reject(); } -QString DlgCreateToken::getName() const +TokenInfo DlgCreateToken::getTokenInfo() const { - return nameEdit->text(); -} - -QString DlgCreateToken::getColor() const -{ - return QString(colorEdit->itemData(colorEdit->currentIndex()).toChar()); -} - -QString DlgCreateToken::getPT() const -{ - return ptEdit->text(); -} - -QString DlgCreateToken::getAnnotation() const -{ - return annotationEdit->text(); -} - -bool DlgCreateToken::getDestroy() const -{ - return destroyCheckBox->isChecked(); + return {.name = nameEdit->text(), + .color = colorEdit->itemData(colorEdit->currentIndex()).toString(), + .pt = ptEdit->text(), + .annotation = annotationEdit->text(), + .destroy = destroyCheckBox->isChecked()}; } diff --git a/cockatrice/src/dialogs/dlg_create_token.h b/cockatrice/src/dialogs/dlg_create_token.h index a226faaca..373e094f1 100644 --- a/cockatrice/src/dialogs/dlg_create_token.h +++ b/cockatrice/src/dialogs/dlg_create_token.h @@ -17,19 +17,24 @@ class CardDatabaseModel; class TokenDisplayModel; class CardInfoPictureWidget; +struct TokenInfo +{ + QString name; + QString color; + QString pt; + QString annotation; + bool destroy = true; +}; + class DlgCreateToken : public QDialog { Q_OBJECT public: explicit DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent = nullptr); - QString getName() const; - QString getColor() const; - QString getPT() const; - QString getAnnotation() const; - bool getDestroy() const; + TokenInfo getTokenInfo() const; protected: - void closeEvent(QCloseEvent *event); + void closeEvent(QCloseEvent *event) override; private slots: void tokenSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); void updateSearch(const QString &search); diff --git a/cockatrice/src/dialogs/dlg_edit_avatar.cpp b/cockatrice/src/dialogs/dlg_edit_avatar.cpp index 1c951e7cb..98a41bdfa 100644 --- a/cockatrice/src/dialogs/dlg_edit_avatar.cpp +++ b/cockatrice/src/dialogs/dlg_edit_avatar.cpp @@ -22,7 +22,7 @@ DlgEditAvatar::DlgEditAvatar(QWidget *parent) : QDialog(parent), image() textLabel = new QLabel(tr("To change your avatar, choose a new image.\nTo remove your current avatar, confirm " "without choosing a new image.")); browseButton = new QPushButton(tr("Browse...")); - connect(browseButton, SIGNAL(clicked()), this, SLOT(actBrowse())); + connect(browseButton, &QPushButton::clicked, this, &DlgEditAvatar::actBrowse); QGridLayout *grid = new QGridLayout; grid->addWidget(imageLabel, 0, 0, 1, 2); @@ -30,8 +30,8 @@ DlgEditAvatar::DlgEditAvatar(QWidget *parent) : QDialog(parent), image() grid->addWidget(browseButton, 1, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgEditAvatar::actOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgEditAvatar::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(grid); diff --git a/cockatrice/src/dialogs/dlg_edit_password.cpp b/cockatrice/src/dialogs/dlg_edit_password.cpp index e29169c31..bea94ae08 100644 --- a/cockatrice/src/dialogs/dlg_edit_password.cpp +++ b/cockatrice/src/dialogs/dlg_edit_password.cpp @@ -44,8 +44,8 @@ DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent) grid->addWidget(newPasswordEdit2, 2, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgEditPassword::actOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgEditPassword::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(grid); diff --git a/cockatrice/src/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/dialogs/dlg_edit_tokens.cpp index 208e9fe80..1248ee403 100644 --- a/cockatrice/src/dialogs/dlg_edit_tokens.cpp +++ b/cockatrice/src/dialogs/dlg_edit_tokens.cpp @@ -40,19 +40,19 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nul colorEdit->addItem(tr("multicolor"), QChar('m')); colorEdit->addItem(tr("colorless"), QChar()); colorLabel->setBuddy(colorEdit); - connect(colorEdit, SIGNAL(currentIndexChanged(int)), this, SLOT(colorChanged(int))); + connect(colorEdit, qOverload(&QComboBox::currentIndexChanged), this, &DlgEditTokens::colorChanged); ptLabel = new QLabel(tr("&P/T:")); ptEdit = new QLineEdit; ptEdit->setMaxLength(MAX_NAME_LENGTH); ptLabel->setBuddy(ptEdit); - connect(ptEdit, SIGNAL(textChanged(QString)), this, SLOT(ptChanged(QString))); + connect(ptEdit, &QLineEdit::textChanged, this, &DlgEditTokens::ptChanged); annotationLabel = new QLabel(tr("&Annotation:")); annotationEdit = new QLineEdit; annotationEdit->setMaxLength(MAX_NAME_LENGTH); annotationLabel->setBuddy(annotationEdit); - connect(annotationEdit, SIGNAL(textChanged(QString)), this, SLOT(annotationChanged(QString))); + connect(annotationEdit, &QLineEdit::textChanged, this, &DlgEditTokens::annotationChanged); auto *grid = new QGridLayout; grid->addWidget(nameLabel, 0, 0); @@ -87,15 +87,15 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nul chooseTokenView->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); chooseTokenView->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents); - connect(chooseTokenView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), this, - SLOT(tokenSelectionChanged(QModelIndex, QModelIndex))); + connect(chooseTokenView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, + &DlgEditTokens::tokenSelectionChanged); QAction *aAddToken = new QAction(tr("Add token"), this); aAddToken->setIcon(QPixmap("theme:icons/increment")); - connect(aAddToken, SIGNAL(triggered()), this, SLOT(actAddToken())); + connect(aAddToken, &QAction::triggered, this, &DlgEditTokens::actAddToken); QAction *aRemoveToken = new QAction(tr("Remove token"), this); aRemoveToken->setIcon(QPixmap("theme:icons/decrement")); - connect(aRemoveToken, SIGNAL(triggered()), this, SLOT(actRemoveToken())); + connect(aRemoveToken, &QAction::triggered, this, &DlgEditTokens::actRemoveToken); auto *databaseToolBar = new QToolBar; databaseToolBar->addAction(aAddToken); @@ -110,8 +110,8 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nul hbox->addWidget(tokenDataGroupBox); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgEditTokens::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgEditTokens::reject); auto *mainLayout = new QVBoxLayout; mainLayout->addLayout(hbox); diff --git a/cockatrice/src/dialogs/dlg_edit_tokens.h b/cockatrice/src/dialogs/dlg_edit_tokens.h index e5befe892..a008010bd 100644 --- a/cockatrice/src/dialogs/dlg_edit_tokens.h +++ b/cockatrice/src/dialogs/dlg_edit_tokens.h @@ -1,7 +1,7 @@ #ifndef DLG_EDIT_TOKENS_H #define DLG_EDIT_TOKENS_H -#include "../game/cards/card_database.h" +#include "../game/cards/card_info.h" #include diff --git a/cockatrice/src/dialogs/dlg_edit_user.cpp b/cockatrice/src/dialogs/dlg_edit_user.cpp index 029992d85..7b2af1445 100644 --- a/cockatrice/src/dialogs/dlg_edit_user.cpp +++ b/cockatrice/src/dialogs/dlg_edit_user.cpp @@ -48,8 +48,8 @@ DlgEditUser::DlgEditUser(QWidget *parent, QString email, QString country, QStrin grid->addWidget(realnameEdit, 3, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgEditUser::actOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgEditUser::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(grid); diff --git a/cockatrice/src/dialogs/dlg_filter_games.cpp b/cockatrice/src/dialogs/dlg_filter_games.cpp index f8e9aa7b2..a71a3aa55 100644 --- a/cockatrice/src/dialogs/dlg_filter_games.cpp +++ b/cockatrice/src/dialogs/dlg_filter_games.cpp @@ -117,7 +117,8 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, showOnlyIfSpectatorsCanWatch = new QCheckBox(tr("Show games only if &spectators can watch")); showOnlyIfSpectatorsCanWatch->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanWatch()); - connect(showOnlyIfSpectatorsCanWatch, SIGNAL(toggled(bool)), this, SLOT(toggleSpectatorCheckboxEnabledness(bool))); + connect(showOnlyIfSpectatorsCanWatch, &QCheckBox::toggled, this, + &DlgFilterGames::toggleSpectatorCheckboxEnabledness); showSpectatorPasswordProtected = new QCheckBox(tr("Show spectator password p&rotected games")); showSpectatorPasswordProtected->setChecked(gamesProxyModel->getShowSpectatorPasswordProtected()); @@ -158,8 +159,8 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, hbox->addLayout(rightColumn); auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgFilterGames::actOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgFilterGames::reject); auto *mainLayout = new QVBoxLayout; mainLayout->addLayout(hbox); diff --git a/cockatrice/src/dialogs/dlg_forgot_password_challenge.cpp b/cockatrice/src/dialogs/dlg_forgot_password_challenge.cpp index 57aa240ae..4ff237edf 100644 --- a/cockatrice/src/dialogs/dlg_forgot_password_challenge.cpp +++ b/cockatrice/src/dialogs/dlg_forgot_password_challenge.cpp @@ -78,8 +78,8 @@ DlgForgotPasswordChallenge::DlgForgotPasswordChallenge(QWidget *parent) : QDialo grid->addWidget(emailEdit, 4, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgForgotPasswordChallenge::actOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgForgotPasswordChallenge::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(grid); diff --git a/cockatrice/src/dialogs/dlg_forgot_password_request.cpp b/cockatrice/src/dialogs/dlg_forgot_password_request.cpp index 1370a1100..a07bda279 100644 --- a/cockatrice/src/dialogs/dlg_forgot_password_request.cpp +++ b/cockatrice/src/dialogs/dlg_forgot_password_request.cpp @@ -55,8 +55,8 @@ DlgForgotPasswordRequest::DlgForgotPasswordRequest(QWidget *parent) : QDialog(pa grid->addWidget(playernameEdit, 3, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgForgotPasswordRequest::actOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgForgotPasswordRequest::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(grid); diff --git a/cockatrice/src/dialogs/dlg_forgot_password_reset.cpp b/cockatrice/src/dialogs/dlg_forgot_password_reset.cpp index 12fe8eaca..81ab96b9b 100644 --- a/cockatrice/src/dialogs/dlg_forgot_password_reset.cpp +++ b/cockatrice/src/dialogs/dlg_forgot_password_reset.cpp @@ -93,8 +93,8 @@ DlgForgotPasswordReset::DlgForgotPasswordReset(QWidget *parent) : QDialog(parent grid->addWidget(newpasswordverifyEdit, 6, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgForgotPasswordReset::actOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgForgotPasswordReset::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(grid); diff --git a/cockatrice/src/dialogs/dlg_load_remote_deck.cpp b/cockatrice/src/dialogs/dlg_load_remote_deck.cpp index d64d3a6ee..5e2823b57 100644 --- a/cockatrice/src/dialogs/dlg_load_remote_deck.cpp +++ b/cockatrice/src/dialogs/dlg_load_remote_deck.cpp @@ -15,8 +15,8 @@ DlgLoadRemoteDeck::DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent) : buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgLoadRemoteDeck::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgLoadRemoteDeck::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(dirView); @@ -28,8 +28,8 @@ DlgLoadRemoteDeck::DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent) : setMinimumWidth(sizeHint().width()); resize(400, 600); - connect(dirView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, - SLOT(currentItemChanged(const QModelIndex &, const QModelIndex &))); + connect(dirView->selectionModel(), &QItemSelectionModel::currentChanged, this, + &DlgLoadRemoteDeck::currentItemChanged); } void DlgLoadRemoteDeck::currentItemChanged(const QModelIndex ¤t, const QModelIndex & /*previous*/) diff --git a/cockatrice/src/dialogs/dlg_manage_sets.cpp b/cockatrice/src/dialogs/dlg_manage_sets.cpp index 05984c05c..d0693da41 100644 --- a/cockatrice/src/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/dialogs/dlg_manage_sets.cpp @@ -39,28 +39,28 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) aTop->setIcon(QPixmap("theme:icons/arrow_top_green")); aTop->setToolTip(tr("Move selected set to the top")); aTop->setEnabled(false); - connect(aTop, SIGNAL(triggered()), this, SLOT(actTop())); + connect(aTop, &QAction::triggered, this, &WndSets::actTop); setsEditToolBar->addAction(aTop); aUp = new QAction(QString(), this); aUp->setIcon(QPixmap("theme:icons/arrow_up_green")); aUp->setToolTip(tr("Move selected set up")); aUp->setEnabled(false); - connect(aUp, SIGNAL(triggered()), this, SLOT(actUp())); + connect(aUp, &QAction::triggered, this, &WndSets::actUp); setsEditToolBar->addAction(aUp); aDown = new QAction(QString(), this); aDown->setIcon(QPixmap("theme:icons/arrow_down_green")); aDown->setToolTip(tr("Move selected set down")); aDown->setEnabled(false); - connect(aDown, SIGNAL(triggered()), this, SLOT(actDown())); + connect(aDown, &QAction::triggered, this, &WndSets::actDown); setsEditToolBar->addAction(aDown); aBottom = new QAction(QString(), this); aBottom->setIcon(QPixmap("theme:icons/arrow_bottom_green")); aBottom->setToolTip(tr("Move selected set to the bottom")); aBottom->setEnabled(false); - connect(aBottom, SIGNAL(triggered()), this, SLOT(actBottom())); + connect(aBottom, &QAction::triggered, this, &WndSets::actBottom); setsEditToolBar->addAction(aBottom); // search field @@ -74,7 +74,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) defaultSortButton = new QPushButton(tr("Default order")); defaultSortButton->setToolTip(tr("Restore original art priority order")); - connect(defaultSortButton, SIGNAL(clicked()), this, SLOT(actRestoreOriginalOrder())); + connect(defaultSortButton, &QPushButton::clicked, this, &WndSets::actRestoreOriginalOrder); filterBox = new QHBoxLayout; filterBox->addWidget(searchField); @@ -106,7 +106,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) view->setColumnHidden(SetsModel::PriorityCol, true); view->setRootIsDecorated(false); - connect(view->header(), SIGNAL(sectionClicked(int)), this, SLOT(actSort(int))); + connect(view->header(), &QHeaderView::sectionClicked, this, &WndSets::actSort); // bottom buttons enableAllButton = new QPushButton(tr("Enable all sets")); @@ -114,20 +114,15 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) enableSomeButton = new QPushButton(tr("Enable selected set(s)")); disableSomeButton = new QPushButton(tr("Disable selected set(s)")); - connect(enableAllButton, SIGNAL(clicked()), this, SLOT(actEnableAll())); - connect(disableAllButton, SIGNAL(clicked()), this, SLOT(actDisableAll())); - connect(enableSomeButton, SIGNAL(clicked()), this, SLOT(actEnableSome())); - connect(disableSomeButton, SIGNAL(clicked()), this, SLOT(actDisableSome())); - connect(view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, - SLOT(actToggleButtons(const QItemSelection &, const QItemSelection &))); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) - connect(searchField, SIGNAL(textChanged(const QString &)), displayModel, - SLOT(setFilterRegularExpression(const QString &))); -#else - connect(searchField, SIGNAL(textChanged(const QString &)), displayModel, SLOT(setFilterRegExp(const QString &))); -#endif - connect(view->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(actDisableSortButtons(int))); - connect(searchField, SIGNAL(textChanged(const QString &)), this, SLOT(actDisableResetButton(const QString &))); + connect(enableAllButton, &QPushButton::clicked, this, &WndSets::actEnableAll); + connect(disableAllButton, &QPushButton::clicked, this, &WndSets::actDisableAll); + connect(enableSomeButton, &QPushButton::clicked, this, &WndSets::actEnableSome); + connect(disableSomeButton, &QPushButton::clicked, this, &WndSets::actDisableSome); + connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &WndSets::actToggleButtons); + connect(searchField, &LineEditUnfocusable::textChanged, displayModel, + qOverload(&SetsDisplayModel::setFilterRegularExpression)); + connect(view->header(), &QHeaderView::sortIndicatorChanged, this, &WndSets::actDisableSortButtons); + connect(searchField, &LineEditUnfocusable::textChanged, this, &WndSets::actDisableResetButton); labNotes = new QLabel; labNotes->setWordWrap(true); @@ -158,7 +153,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) sortWarningButton->setText(tr("Use the current sorting as the set priority instead")); sortWarningButton->setToolTip(tr("Sorts the set priority using the same column")); sortWarningButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - connect(sortWarningButton, SIGNAL(released()), this, SLOT(actIgnoreWarning())); + connect(sortWarningButton, &QPushButton::released, this, &WndSets::actIgnoreWarning); sortWarningLayout->addWidget(sortWarningButton, 1, 0); sortWarning->setLayout(sortWarningLayout); sortWarning->setVisible(false); @@ -170,8 +165,8 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) connect(includeRebalancedCardsCheckBox, &QAbstractButton::toggled, this, &WndSets::includeRebalancedCardsChanged); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actSave())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(actRestore())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &WndSets::actSave); + connect(buttonBox, &QDialogButtonBox::rejected, this, &WndSets::actRestore); mainLayout = new QGridLayout; mainLayout->addLayout(filterBox, 0, 1, 1, 2); diff --git a/cockatrice/src/dialogs/dlg_register.cpp b/cockatrice/src/dialogs/dlg_register.cpp index 6abf9dd9f..05c73179e 100644 --- a/cockatrice/src/dialogs/dlg_register.cpp +++ b/cockatrice/src/dialogs/dlg_register.cpp @@ -343,8 +343,8 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) grid->addWidget(realnameEdit, 10, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgRegister::actOk); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgRegister::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(grid); diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index 0d5ea6d92..502662c14 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -74,13 +74,14 @@ GeneralSettingsPage::GeneralSettingsPage() advertiseTranslationPageLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); advertiseTranslationPageLabel.setOpenExternalLinks(true); - connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); + connect(&languageBox, qOverload(&QComboBox::currentIndexChanged), this, + &GeneralSettingsPage::languageBoxChanged); connect(&startupUpdateCheckCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setCheckUpdatesOnStartup); connect(&updateNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setNotifyAboutUpdate); connect(&newVersionOracleCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setNotifyAboutNewVersion); - connect(&showTipsOnStartup, SIGNAL(clicked(bool)), &settings, SLOT(setShowTipsOnStartup(bool))); + connect(&showTipsOnStartup, &QCheckBox::clicked, &settings, &SettingsCache::setShowTipsOnStartup); auto *personalGrid = new QGridLayout; personalGrid->addWidget(&languageLabel, 0, 0); @@ -99,32 +100,38 @@ GeneralSettingsPage::GeneralSettingsPage() deckPathEdit = new QLineEdit(settings.getDeckPath()); deckPathEdit->setReadOnly(true); QPushButton *deckPathButton = new QPushButton("..."); - connect(deckPathButton, SIGNAL(clicked()), this, SLOT(deckPathButtonClicked())); + connect(deckPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::deckPathButtonClicked); + + filtersPathEdit = new QLineEdit(settings.getFiltersPath()); + filtersPathEdit->setReadOnly(true); + QPushButton *filtersPathButton = new QPushButton("..."); + connect(filtersPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::filtersPathButtonClicked); replaysPathEdit = new QLineEdit(settings.getReplaysPath()); replaysPathEdit->setReadOnly(true); QPushButton *replaysPathButton = new QPushButton("..."); - connect(replaysPathButton, SIGNAL(clicked()), this, SLOT(replaysPathButtonClicked())); + connect(replaysPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::replaysPathButtonClicked); picsPathEdit = new QLineEdit(settings.getPicsPath()); picsPathEdit->setReadOnly(true); QPushButton *picsPathButton = new QPushButton("..."); - connect(picsPathButton, SIGNAL(clicked()), this, SLOT(picsPathButtonClicked())); + connect(picsPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::picsPathButtonClicked); cardDatabasePathEdit = new QLineEdit(settings.getCardDatabasePath()); cardDatabasePathEdit->setReadOnly(true); QPushButton *cardDatabasePathButton = new QPushButton("..."); - connect(cardDatabasePathButton, SIGNAL(clicked()), this, SLOT(cardDatabasePathButtonClicked())); + connect(cardDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::cardDatabasePathButtonClicked); customCardDatabasePathEdit = new QLineEdit(settings.getCustomCardDatabasePath()); customCardDatabasePathEdit->setReadOnly(true); QPushButton *customCardDatabasePathButton = new QPushButton("..."); - connect(customCardDatabasePathButton, SIGNAL(clicked()), this, SLOT(customCardDatabaseButtonClicked())); + connect(customCardDatabasePathButton, &QPushButton::clicked, this, + &GeneralSettingsPage::customCardDatabaseButtonClicked); tokenDatabasePathEdit = new QLineEdit(settings.getTokenDatabasePath()); tokenDatabasePathEdit->setReadOnly(true); QPushButton *tokenDatabasePathButton = new QPushButton("..."); - connect(tokenDatabasePathButton, SIGNAL(clicked()), this, SLOT(tokenDatabasePathButtonClicked())); + connect(tokenDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::tokenDatabasePathButtonClicked); // Required init here to avoid crashing on Portable builds resetAllPathsButton = new QPushButton; @@ -132,6 +139,7 @@ GeneralSettingsPage::GeneralSettingsPage() bool isPortable = settings.getIsPortableBuild(); if (isPortable) { deckPathEdit->setEnabled(false); + filtersPathEdit->setEnabled(false); replaysPathEdit->setEnabled(false); picsPathEdit->setEnabled(false); cardDatabasePathEdit->setEnabled(false); @@ -145,7 +153,7 @@ GeneralSettingsPage::GeneralSettingsPage() customCardDatabasePathButton->setVisible(false); tokenDatabasePathButton->setVisible(false); } else { - connect(resetAllPathsButton, SIGNAL(clicked()), this, SLOT(resetAllPathsClicked())); + connect(resetAllPathsButton, &QPushButton::clicked, this, &GeneralSettingsPage::resetAllPathsClicked); allPathsResetLabel = new QLabel(tr("All paths have been reset")); allPathsResetLabel->setVisible(false); } @@ -154,24 +162,27 @@ GeneralSettingsPage::GeneralSettingsPage() pathsGrid->addWidget(&deckPathLabel, 0, 0); pathsGrid->addWidget(deckPathEdit, 0, 1); pathsGrid->addWidget(deckPathButton, 0, 2); - pathsGrid->addWidget(&replaysPathLabel, 1, 0); - pathsGrid->addWidget(replaysPathEdit, 1, 1); - pathsGrid->addWidget(replaysPathButton, 1, 2); - pathsGrid->addWidget(&picsPathLabel, 2, 0); - pathsGrid->addWidget(picsPathEdit, 2, 1); - pathsGrid->addWidget(picsPathButton, 2, 2); - pathsGrid->addWidget(&cardDatabasePathLabel, 3, 0); - pathsGrid->addWidget(cardDatabasePathEdit, 3, 1); - pathsGrid->addWidget(cardDatabasePathButton, 3, 2); - pathsGrid->addWidget(&customCardDatabasePathLabel, 4, 0); - pathsGrid->addWidget(customCardDatabasePathEdit, 4, 1); - pathsGrid->addWidget(customCardDatabasePathButton, 4, 2); - pathsGrid->addWidget(&tokenDatabasePathLabel, 5, 0); - pathsGrid->addWidget(tokenDatabasePathEdit, 5, 1); - pathsGrid->addWidget(tokenDatabasePathButton, 5, 2); + pathsGrid->addWidget(&filtersPathLabel, 1, 0); + pathsGrid->addWidget(filtersPathEdit, 1, 1); + pathsGrid->addWidget(filtersPathButton, 1, 2); + pathsGrid->addWidget(&replaysPathLabel, 2, 0); + pathsGrid->addWidget(replaysPathEdit, 2, 1); + pathsGrid->addWidget(replaysPathButton, 2, 2); + pathsGrid->addWidget(&picsPathLabel, 3, 0); + pathsGrid->addWidget(picsPathEdit, 3, 1); + pathsGrid->addWidget(picsPathButton, 3, 2); + pathsGrid->addWidget(&cardDatabasePathLabel, 4, 0); + pathsGrid->addWidget(cardDatabasePathEdit, 4, 1); + pathsGrid->addWidget(cardDatabasePathButton, 4, 2); + pathsGrid->addWidget(&customCardDatabasePathLabel, 5, 0); + pathsGrid->addWidget(customCardDatabasePathEdit, 5, 1); + pathsGrid->addWidget(customCardDatabasePathButton, 5, 2); + pathsGrid->addWidget(&tokenDatabasePathLabel, 6, 0); + pathsGrid->addWidget(tokenDatabasePathEdit, 6, 1); + pathsGrid->addWidget(tokenDatabasePathButton, 6, 2); if (!isPortable) { - pathsGrid->addWidget(resetAllPathsButton, 6, 0); - pathsGrid->addWidget(allPathsResetLabel, 6, 1); + pathsGrid->addWidget(resetAllPathsButton, 7, 0); + pathsGrid->addWidget(allPathsResetLabel, 7, 1); } pathsGroupBox = new QGroupBox; pathsGroupBox->setLayout(pathsGrid); @@ -184,8 +195,8 @@ GeneralSettingsPage::GeneralSettingsPage() GeneralSettingsPage::retranslateUi(); // connect the ReleaseChannel combo box only after the entries are inserted in retranslateUi - connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), &settings, - SLOT(setUpdateReleaseChannelIndex(int))); + connect(&updateReleaseChannelBox, qOverload(&QComboBox::currentIndexChanged), &settings, + &SettingsCache::setUpdateReleaseChannelIndex); updateReleaseChannelBox.setCurrentIndex(settings.getUpdateReleaseChannelIndex()); setLayout(mainLayout); @@ -226,6 +237,16 @@ void GeneralSettingsPage::deckPathButtonClicked() SettingsCache::instance().setDeckPath(path); } +void GeneralSettingsPage::filtersPathButtonClicked() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), filtersPathEdit->text()); + if (path.isEmpty()) + return; + + filtersPathEdit->setText(path); + SettingsCache::instance().setFiltersPath(path); +} + void GeneralSettingsPage::replaysPathButtonClicked() { QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), replaysPathEdit->text()); @@ -307,6 +328,7 @@ void GeneralSettingsPage::retranslateUi() advertiseTranslationPageLabel.setText( QString("%2").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations"))); deckPathLabel.setText(tr("Decks directory:")); + filtersPathLabel.setText(tr("Filters directory:")); replaysPathLabel.setText(tr("Replays directory:")); picsPathLabel.setText(tr("Pictures directory:")); cardDatabasePathLabel.setText(tr("Card database:")); @@ -344,8 +366,8 @@ AppearanceSettingsPage::AppearanceSettingsPage() themeBox.setCurrentIndex(i); } - connect(&themeBox, SIGNAL(currentIndexChanged(int)), this, SLOT(themeBoxChanged(int))); - connect(&openThemeButton, SIGNAL(clicked()), this, SLOT(openThemeLocation())); + connect(&themeBox, qOverload(&QComboBox::currentIndexChanged), this, &AppearanceSettingsPage::themeBoxChanged); + connect(&openThemeButton, &QPushButton::clicked, this, &AppearanceSettingsPage::openThemeLocation); auto *themeGrid = new QGridLayout; themeGrid->addWidget(&themeLabel, 0, 0); @@ -389,8 +411,8 @@ AppearanceSettingsPage::AppearanceSettingsPage() verticalCardOverlapPercentBox.setValue(settings.getStackCardOverlapPercent()); verticalCardOverlapPercentBox.setRange(0, 80); - connect(&verticalCardOverlapPercentBox, SIGNAL(valueChanged(int)), &settings, - SLOT(setStackCardOverlapPercent(int))); + connect(&verticalCardOverlapPercentBox, qOverload(&QSpinBox::valueChanged), &settings, + &SettingsCache::setStackCardOverlapPercent); cardViewInitialRowsMaxBox.setRange(1, 999); cardViewInitialRowsMaxBox.setValue(SettingsCache::instance().getCardViewInitialRowsMax()); @@ -440,11 +462,12 @@ AppearanceSettingsPage::AppearanceSettingsPage() minPlayersForMultiColumnLayoutEdit.setMinimum(2); minPlayersForMultiColumnLayoutEdit.setValue(settings.getMinPlayersForMultiColumnLayout()); - connect(&minPlayersForMultiColumnLayoutEdit, SIGNAL(valueChanged(int)), &settings, - SLOT(setMinPlayersForMultiColumnLayout(int))); + connect(&minPlayersForMultiColumnLayoutEdit, qOverload(&QSpinBox::valueChanged), &settings, + &SettingsCache::setMinPlayersForMultiColumnLayout); minPlayersForMultiColumnLayoutLabel.setBuddy(&minPlayersForMultiColumnLayoutEdit); - connect(&maxFontSizeForCardsEdit, SIGNAL(valueChanged(int)), &settings, SLOT(setMaxFontSize(int))); + connect(&maxFontSizeForCardsEdit, qOverload(&QSpinBox::valueChanged), &settings, + &SettingsCache::setMaxFontSize); maxFontSizeForCardsEdit.setValue(settings.getMaxFontSize()); maxFontSizeForCardsLabel.setBuddy(&maxFontSizeForCardsEdit); maxFontSizeForCardsEdit.setMinimum(9); @@ -563,6 +586,13 @@ void AppearanceSettingsPage::retranslateUi() maxFontSizeForCardsLabel.setText(tr("Maximum font size for information displayed on cards:")); } +enum visualDeckStoragePromptForConversionIndex +{ + visualDeckStoragePromptForConversionIndexNone, + visualDeckStoragePromptForConversionIndexPrompt, + visualDeckStoragePromptForConversionIndexAlways +}; + UserInterfaceSettingsPage::UserInterfaceSettingsPage() { // general settings and notification settings @@ -642,24 +672,39 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&openDeckInNewTabCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setOpenDeckInNewTab); - visualDeckStoragePromptForConversionCheckBox.setChecked( - SettingsCache::instance().getVisualDeckStoragePromptForConversion()); - connect(&visualDeckStoragePromptForConversionCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStoragePromptForConversion); - - visualDeckStorageAlwaysConvertCheckBox.setChecked(SettingsCache::instance().getVisualDeckStorageAlwaysConvert()); - connect(&visualDeckStorageAlwaysConvertCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageAlwaysConvert); - visualDeckStorageInGameCheckBox.setChecked(SettingsCache::instance().getVisualDeckStorageInGame()); connect(&visualDeckStorageInGameCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageInGame); + visualDeckStorageSelectionAnimationCheckBox.setChecked( + SettingsCache::instance().getVisualDeckStorageSelectionAnimation()); + connect(&visualDeckStorageSelectionAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageSelectionAnimation); + + visualDeckStoragePromptForConversionSelector.addItem(""); // these will be set in retranslateUI + visualDeckStoragePromptForConversionSelector.addItem(""); + visualDeckStoragePromptForConversionSelector.addItem(""); + if (SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { + visualDeckStoragePromptForConversionSelector.setCurrentIndex(visualDeckStoragePromptForConversionIndexPrompt); + } else if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { + visualDeckStoragePromptForConversionSelector.setCurrentIndex(visualDeckStoragePromptForConversionIndexAlways); + } else { + visualDeckStoragePromptForConversionSelector.setCurrentIndex(visualDeckStoragePromptForConversionIndexNone); + } + connect(&visualDeckStoragePromptForConversionSelector, QOverload::of(&QComboBox::currentIndexChanged), this, + [](int index) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion( + index == visualDeckStoragePromptForConversionIndexPrompt); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert( + index == visualDeckStoragePromptForConversionIndexAlways); + }); + auto *deckEditorGrid = new QGridLayout; deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0); - deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionCheckBox, 1, 0); - deckEditorGrid->addWidget(&visualDeckStorageAlwaysConvertCheckBox, 2, 0); - deckEditorGrid->addWidget(&visualDeckStorageInGameCheckBox, 3, 0); + deckEditorGrid->addWidget(&visualDeckStorageInGameCheckBox, 1, 0); + deckEditorGrid->addWidget(&visualDeckStorageSelectionAnimationCheckBox, 2, 0); + deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionLabel, 3, 0); + deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionSelector, 3, 1); deckEditorGroupBox = new QGroupBox; deckEditorGroupBox->setLayout(deckEditorGrid); @@ -719,9 +764,16 @@ void UserInterfaceSettingsPage::retranslateUi() tapAnimationCheckBox.setText(tr("&Tap/untap animation")); deckEditorGroupBox->setTitle(tr("Deck editor/storage settings")); openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default")); - visualDeckStoragePromptForConversionCheckBox.setText(tr("Prompt before converting .txt decks to .cod format")); - visualDeckStorageAlwaysConvertCheckBox.setText(tr("Always convert if not prompted")); visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby")); + visualDeckStorageSelectionAnimationCheckBox.setText(tr("Use selection animation for Visual Deck Storage")); + visualDeckStoragePromptForConversionLabel.setText( + tr("When adding a tag in the visual deck storage to a .txt deck:")); + visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexNone, + tr("do nothing")); + visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexPrompt, + tr("ask to convert to .cod")); + visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexAlways, + tr("always convert to .cod")); replayGroupBox->setTitle(tr("Replay settings")); rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:")); rewindBufferingMsBox.setSuffix(" ms"); @@ -736,8 +788,9 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); urlLinkLabel.setOpenExternalLinks(true); - connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked())); - connect(&resetDownloadURLs, SIGNAL(clicked()), this, SLOT(resetDownloadedURLsButtonClicked())); + connect(&clearDownloadedPicsButton, &QPushButton::clicked, this, + &DeckEditorSettingsPage::clearDownloadedPicsButtonClicked); + connect(&resetDownloadURLs, &QPushButton::clicked, this, &DeckEditorSettingsPage::resetDownloadedURLsButtonClicked); auto *lpGeneralGrid = new QGridLayout; auto *lpSpoilerGrid = new QGridLayout; @@ -747,11 +800,11 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() mpSpoilerSavePathLineEdit = new QLineEdit(SettingsCache::instance().getSpoilerCardDatabasePath()); mpSpoilerSavePathLineEdit->setReadOnly(true); mpSpoilerPathButton = new QPushButton("..."); - connect(mpSpoilerPathButton, SIGNAL(clicked()), this, SLOT(spoilerPathButtonClicked())); + connect(mpSpoilerPathButton, &QPushButton::clicked, this, &DeckEditorSettingsPage::spoilerPathButtonClicked); updateNowButton = new QPushButton; updateNowButton->setFixedWidth(150); - connect(updateNowButton, SIGNAL(clicked()), this, SLOT(updateSpoilers())); + connect(updateNowButton, &QPushButton::clicked, this, &DeckEditorSettingsPage::updateSpoilers); // Update the GUI depending on if the box is ticked or not setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked()); @@ -761,22 +814,21 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() urlList->setAlternatingRowColors(true); urlList->setDragEnabled(true); urlList->setDragDropMode(QAbstractItemView::InternalMove); - connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this, - SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int))); + connect(urlList->model(), &QAbstractItemModel::rowsMoved, this, &DeckEditorSettingsPage::urlListChanged); urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); aAdd = new QAction(this); aAdd->setIcon(QPixmap("theme:icons/increment")); - connect(aAdd, SIGNAL(triggered()), this, SLOT(actAddURL())); + connect(aAdd, &QAction::triggered, this, &DeckEditorSettingsPage::actAddURL); aEdit = new QAction(this); aEdit->setIcon(QPixmap("theme:icons/pencil")); - connect(aEdit, SIGNAL(triggered()), this, SLOT(actEditURL())); + connect(aEdit, &QAction::triggered, this, &DeckEditorSettingsPage::actEditURL); aRemove = new QAction(this); aRemove->setIcon(QPixmap("theme:icons/decrement")); - connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL())); + connect(aRemove, &QAction::triggered, this, &DeckEditorSettingsPage::actRemoveURL); auto *urlToolBar = new QToolBar; urlToolBar->setOrientation(Qt::Vertical); @@ -843,14 +895,15 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() lpSpoilerGrid->addWidget(&infoOnSpoilersLabel, 3, 0, 1, 3, Qt::AlignTop); // On a change to the checkbox, hide/un-hide the other fields - connect(&mcDownloadSpoilersCheckBox, SIGNAL(toggled(bool)), &SettingsCache::instance(), - SLOT(setDownloadSpoilerStatus(bool))); - connect(&mcDownloadSpoilersCheckBox, SIGNAL(toggled(bool)), this, SLOT(setSpoilersEnabled(bool))); - connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), SLOT(setPixmapCacheSize(int))); - connect(&networkCacheEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), - SLOT(setNetworkCacheSizeInMB(int))); - connect(&networkRedirectCacheTtlEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), - SLOT(setNetworkRedirectCacheTtl(int))); + connect(&mcDownloadSpoilersCheckBox, &QCheckBox::toggled, &SettingsCache::instance(), + &SettingsCache::setDownloadSpoilerStatus); + connect(&mcDownloadSpoilersCheckBox, &QCheckBox::toggled, this, &DeckEditorSettingsPage::setSpoilersEnabled); + connect(&pixmapCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setPixmapCacheSize); + connect(&networkCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setNetworkCacheSizeInMB); + connect(&networkRedirectCacheTtlEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setNetworkRedirectCacheTtl); mpGeneralGroupBox = new QGroupBox; mpGeneralGroupBox->setLayout(lpGeneralGrid); @@ -972,8 +1025,8 @@ void DeckEditorSettingsPage::updateSpoilers() // Create a new SBU that will act as if the client was just reloaded auto *sbu = new SpoilerBackgroundUpdater(); - connect(sbu, SIGNAL(spoilerCheckerDone()), this, SLOT(unlockSettings())); - connect(sbu, SIGNAL(spoilersUpdatedSuccessfully()), this, SLOT(unlockSettings())); + connect(sbu, &SpoilerBackgroundUpdater::spoilerCheckerDone, this, &DeckEditorSettingsPage::unlockSettings); + connect(sbu, &SpoilerBackgroundUpdater::spoilersUpdatedSuccessfully, this, &DeckEditorSettingsPage::unlockSettings); } void DeckEditorSettingsPage::unlockSettings() @@ -1079,7 +1132,7 @@ MessagesSettingsPage::MessagesSettingsPage() mentionColor = new QLineEdit(); mentionColor->setText(SettingsCache::instance().getChatMentionColor()); updateMentionPreview(); - connect(mentionColor, SIGNAL(textChanged(QString)), this, SLOT(updateColor(QString))); + connect(mentionColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateColor); messagePopups.setChecked(SettingsCache::instance().getShowMessagePopup()); connect(&messagePopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), @@ -1094,8 +1147,7 @@ MessagesSettingsPage::MessagesSettingsPage() customAlertString = new QLineEdit(); customAlertString->setText(SettingsCache::instance().getHighlightWords()); - connect(customAlertString, SIGNAL(textChanged(QString)), &SettingsCache::instance(), - SLOT(setHighlightWords(QString))); + connect(customAlertString, &QLineEdit::textChanged, &SettingsCache::instance(), &SettingsCache::setHighlightWords); auto *chatGrid = new QGridLayout; chatGrid->addWidget(&chatMentionCheckBox, 0, 0); @@ -1114,7 +1166,7 @@ MessagesSettingsPage::MessagesSettingsPage() highlightColor = new QLineEdit(); highlightColor->setText(SettingsCache::instance().getChatHighlightColor()); updateHighlightPreview(); - connect(highlightColor, SIGNAL(textChanged(QString)), this, SLOT(updateHighlightColor(QString))); + connect(highlightColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateHighlightColor); auto *highlightNotice = new QGridLayout; highlightNotice->addWidget(highlightColor, 0, 2); @@ -1133,15 +1185,15 @@ MessagesSettingsPage::MessagesSettingsPage() aAdd = new QAction(this); aAdd->setIcon(QPixmap("theme:icons/increment")); - connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd())); + connect(aAdd, &QAction::triggered, this, &MessagesSettingsPage::actAdd); aEdit = new QAction(this); aEdit->setIcon(QPixmap("theme:icons/pencil")); - connect(aEdit, SIGNAL(triggered()), this, SLOT(actEdit())); + connect(aEdit, &QAction::triggered, this, &MessagesSettingsPage::actEdit); aRemove = new QAction(this); aRemove->setIcon(QPixmap("theme:icons/decrement")); - connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove())); + connect(aRemove, &QAction::triggered, this, &MessagesSettingsPage::actRemove); auto *messageToolBar = new QToolBar; messageToolBar->setOrientation(Qt::Vertical); @@ -1307,24 +1359,25 @@ SoundSettingsPage::SoundSettingsPage() themeBox.setCurrentIndex(i); } - connect(&themeBox, SIGNAL(currentIndexChanged(int)), this, SLOT(themeBoxChanged(int))); - connect(&soundTestButton, SIGNAL(clicked()), soundEngine, SLOT(testSound())); + connect(&themeBox, qOverload(&QComboBox::currentIndexChanged), this, &SoundSettingsPage::themeBoxChanged); + connect(&soundTestButton, &QPushButton::clicked, soundEngine, &SoundEngine::testSound); masterVolumeSlider = new QSlider(Qt::Horizontal); masterVolumeSlider->setMinimum(0); masterVolumeSlider->setMaximum(100); masterVolumeSlider->setValue(SettingsCache::instance().getMasterVolume()); masterVolumeSlider->setToolTip(QString::number(SettingsCache::instance().getMasterVolume())); - connect(&SettingsCache::instance(), SIGNAL(masterVolumeChanged(int)), this, SLOT(masterVolumeChanged(int))); - connect(masterVolumeSlider, SIGNAL(sliderReleased()), soundEngine, SLOT(testSound())); - connect(masterVolumeSlider, SIGNAL(valueChanged(int)), &SettingsCache::instance(), SLOT(setMasterVolume(int))); + connect(&SettingsCache::instance(), &SettingsCache::masterVolumeChanged, this, + &SoundSettingsPage::masterVolumeChanged); + connect(masterVolumeSlider, &QSlider::sliderReleased, soundEngine, &SoundEngine::testSound); + connect(masterVolumeSlider, &QSlider::valueChanged, &SettingsCache::instance(), &SettingsCache::setMasterVolume); masterVolumeSpinBox = new QSpinBox(); masterVolumeSpinBox->setMinimum(0); masterVolumeSpinBox->setMaximum(100); masterVolumeSpinBox->setValue(SettingsCache::instance().getMasterVolume()); - connect(masterVolumeSlider, SIGNAL(valueChanged(int)), masterVolumeSpinBox, SLOT(setValue(int))); - connect(masterVolumeSpinBox, SIGNAL(valueChanged(int)), masterVolumeSlider, SLOT(setValue(int))); + connect(masterVolumeSlider, &QSlider::valueChanged, masterVolumeSpinBox, &QSpinBox::setValue); + connect(masterVolumeSpinBox, qOverload(&QSpinBox::valueChanged), masterVolumeSlider, &QSlider::setValue); auto *soundGrid = new QGridLayout; soundGrid->addWidget(&soundEnabledCheckBox, 0, 0, 1, 3); @@ -1434,8 +1487,8 @@ ShortcutSettingsPage::ShortcutSettingsPage() setLayout(_mainLayout); - connect(btnResetAll, SIGNAL(clicked()), this, SLOT(resetShortcuts())); - connect(btnClearAll, SIGNAL(clicked()), this, SLOT(clearShortcuts())); + connect(btnResetAll, &QPushButton::clicked, this, &ShortcutSettingsPage::resetShortcuts); + connect(btnClearAll, &QPushButton::clicked, this, &ShortcutSettingsPage::clearShortcuts); connect(shortcutsTable, &ShortcutTreeView::currentItemChanged, this, &ShortcutSettingsPage::currentItemChanged); @@ -1506,7 +1559,7 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) auto rec = QGuiApplication::primaryScreen()->availableGeometry(); this->setMinimumSize(qMin(700, rec.width()), qMin(700, rec.height())); - connect(&SettingsCache::instance(), SIGNAL(langChanged()), this, SLOT(updateLanguage())); + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &DlgSettings::updateLanguage); contentsWidget = new QListWidget; contentsWidget->setViewMode(QListView::IconMode); @@ -1533,7 +1586,7 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) vboxLayout->addWidget(pagesWidget); auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(close())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgSettings::close); auto *mainLayout = new QVBoxLayout; mainLayout->addLayout(vboxLayout); @@ -1584,8 +1637,7 @@ void DlgSettings::createIcons() shortcutsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); shortcutsButton->setIcon(QPixmap("theme:config/shorcuts")); - connect(contentsWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, - SLOT(changePage(QListWidgetItem *, QListWidgetItem *))); + connect(contentsWidget, &QListWidget::currentItemChanged, this, &DlgSettings::changePage); } void DlgSettings::changePage(QListWidgetItem *current, QListWidgetItem *previous) diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index 95ae17979..6c9b8b062 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -46,6 +46,7 @@ public: private slots: void deckPathButtonClicked(); + void filtersPathButtonClicked(); void replaysPathButtonClicked(); void picsPathButtonClicked(); void cardDatabasePathButtonClicked(); @@ -58,6 +59,7 @@ private: QStringList findQmFiles(); QString languageName(const QString &lang); QLineEdit *deckPathEdit; + QLineEdit *filtersPathEdit; QLineEdit *replaysPathEdit; QLineEdit *picsPathEdit; QLineEdit *cardDatabasePathEdit; @@ -74,6 +76,7 @@ private: QComboBox updateReleaseChannelBox; QLabel languageLabel; QLabel deckPathLabel; + QLabel filtersPathLabel; QLabel replaysPathLabel; QLabel picsPathLabel; QLabel cardDatabasePathLabel; @@ -148,9 +151,10 @@ private: QCheckBox useTearOffMenusCheckBox; QCheckBox tapAnimationCheckBox; QCheckBox openDeckInNewTabCheckBox; - QCheckBox visualDeckStoragePromptForConversionCheckBox; - QCheckBox visualDeckStorageAlwaysConvertCheckBox; + QLabel visualDeckStoragePromptForConversionLabel; + QComboBox visualDeckStoragePromptForConversionSelector; QCheckBox visualDeckStorageInGameCheckBox; + QCheckBox visualDeckStorageSelectionAnimationCheckBox; QLabel rewindBufferingMsLabel; QSpinBox rewindBufferingMsBox; QGroupBox *generalGroupBox; diff --git a/cockatrice/src/dialogs/dlg_tip_of_the_day.cpp b/cockatrice/src/dialogs/dlg_tip_of_the_day.cpp index 53225f50c..c4b579492 100644 --- a/cockatrice/src/dialogs/dlg_tip_of_the_day.cpp +++ b/cockatrice/src/dialogs/dlg_tip_of_the_day.cpp @@ -53,7 +53,7 @@ DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent) } } - connect(this, SIGNAL(newTipRequested(int)), this, SLOT(updateTip(int))); + connect(this, &DlgTipOfTheDay::newTipRequested, this, &DlgTipOfTheDay::updateTip); newTipRequested(currentTip); content = new QVBoxLayout; @@ -69,14 +69,14 @@ DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent) buttonBox->addButton(nextButton, QDialogButtonBox::ActionRole); buttonBox->addButton(QDialogButtonBox::Ok); buttonBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(nextButton, SIGNAL(clicked()), this, SLOT(nextClicked())); - connect(previousButton, SIGNAL(clicked()), this, SLOT(previousClicked())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgTipOfTheDay::accept); + connect(nextButton, &QPushButton::clicked, this, &DlgTipOfTheDay::nextClicked); + connect(previousButton, &QPushButton::clicked, this, &DlgTipOfTheDay::previousClicked); showTipsOnStartupCheck = new QCheckBox("Show tips on startup"); showTipsOnStartupCheck->setChecked(SettingsCache::instance().getShowTipsOnStartup()); - connect(showTipsOnStartupCheck, SIGNAL(clicked(bool)), &SettingsCache::instance(), - SLOT(setShowTipsOnStartup(bool))); + connect(showTipsOnStartupCheck, &QCheckBox::clicked, &SettingsCache::instance(), + &SettingsCache::setShowTipsOnStartup); buttonBar = new QHBoxLayout(); buttonBar->addWidget(showTipsOnStartupCheck); buttonBar->addWidget(tipNumber); diff --git a/cockatrice/src/dialogs/dlg_update.cpp b/cockatrice/src/dialogs/dlg_update.cpp index 6144406af..7f4cdb56e 100644 --- a/cockatrice/src/dialogs/dlg_update.cpp +++ b/cockatrice/src/dialogs/dlg_update.cpp @@ -41,10 +41,11 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) enableUpdateButton(false); // Unless we know there's an update available, you can't install buttonBox->addButton(ok, QDialogButtonBox::AcceptRole); - connect(gotoDownload, SIGNAL(clicked()), this, SLOT(gotoDownloadPage())); - connect(manualDownload, SIGNAL(clicked()), this, SLOT(downloadUpdate())); - connect(stopDownload, SIGNAL(clicked()), this, SLOT(cancelDownload())); - connect(ok, SIGNAL(clicked()), this, SLOT(closeDialog())); + connect(gotoDownload, &QPushButton::clicked, this, &DlgUpdate::gotoDownloadPage); + // TODO: make reinstall button actually do something when clicked + // connect(manualDownload, &QPushButton::clicked, this, &DlgUpdate::downloadUpdate); + connect(stopDownload, &QPushButton::clicked, this, &DlgUpdate::cancelDownload); + connect(ok, &QPushButton::clicked, this, &DlgUpdate::closeDialog); auto *parentLayout = new QVBoxLayout(this); parentLayout->addWidget(descriptionLabel); @@ -68,9 +69,9 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) // Initialize the checker and downloader class uDownloader = new UpdateDownloader(this); - connect(uDownloader, SIGNAL(downloadSuccessful(QUrl)), this, SLOT(downloadSuccessful(QUrl))); - connect(uDownloader, SIGNAL(progressMade(qint64, qint64)), this, SLOT(downloadProgressMade(qint64, qint64))); - connect(uDownloader, SIGNAL(error(QString)), this, SLOT(downloadError(QString))); + connect(uDownloader, &UpdateDownloader::downloadSuccessful, this, &DlgUpdate::downloadSuccessful); + connect(uDownloader, &UpdateDownloader::progressMade, this, &DlgUpdate::downloadProgressMade); + connect(uDownloader, &UpdateDownloader::error, this, &DlgUpdate::downloadError); // Check for updates beginUpdateCheck(); @@ -108,9 +109,8 @@ void DlgUpdate::beginUpdateCheck() setLabel(tr("Checking for updates...")); auto checker = new ClientUpdateChecker(this); - connect(checker, SIGNAL(finishedCheck(bool, bool, Release *)), this, - SLOT(finishedUpdateCheck(bool, bool, Release *))); - connect(checker, SIGNAL(error(QString)), this, SLOT(updateCheckError(QString))); + connect(checker, &ClientUpdateChecker::finishedCheck, this, &DlgUpdate::finishedUpdateCheck); + connect(checker, &ClientUpdateChecker::error, this, &DlgUpdate::updateCheckError); checker->check(); } diff --git a/cockatrice/src/dialogs/dlg_view_log.cpp b/cockatrice/src/dialogs/dlg_view_log.cpp index 1e79b8282..dc569c92c 100644 --- a/cockatrice/src/dialogs/dlg_view_log.cpp +++ b/cockatrice/src/dialogs/dlg_view_log.cpp @@ -18,7 +18,7 @@ DlgViewLog::DlgViewLog(QWidget *parent) : QDialog(parent) coClearLog = new QCheckBox; coClearLog->setText(tr("Clear log when closing")); coClearLog->setChecked(SettingsCache::instance().servers().getClearDebugLogStatus(false)); - connect(coClearLog, SIGNAL(toggled(bool)), this, SLOT(actCheckBoxChanged(bool))); + connect(coClearLog, &QCheckBox::toggled, this, &DlgViewLog::actCheckBoxChanged); mainLayout->addWidget(coClearLog); setLayout(mainLayout); @@ -27,7 +27,7 @@ DlgViewLog::DlgViewLog(QWidget *parent) : QDialog(parent) resize(800, 500); loadInitialLogBuffer(); - connect(&Logger::getInstance(), SIGNAL(logEntryAdded(QString)), this, SLOT(logEntryAdded(QString))); + connect(&Logger::getInstance(), &Logger::logEntryAdded, this, &DlgViewLog::logEntryAdded); } void DlgViewLog::actCheckBoxChanged(bool abNewValue) diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index 512d1ab72..2be8fbdd8 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -2,7 +2,7 @@ #include "arrow_item.h" #include "../../settings/cache_settings.h" -#include "../cards/card_database.h" +#include "../cards/card_info.h" #include "../cards/card_item.h" #include "../player/player.h" #include "../player/player_target.h" diff --git a/cockatrice/src/game/cards/abstract_card_item.h b/cockatrice/src/game/cards/abstract_card_item.h index 51b345267..48f890701 100644 --- a/cockatrice/src/game/cards/abstract_card_item.h +++ b/cockatrice/src/game/cards/abstract_card_item.h @@ -2,7 +2,7 @@ #define ABSTRACTCARDITEM_H #include "../board/arrow_target.h" -#include "card_database.h" +#include "card_info.h" class Player; diff --git a/cockatrice/src/game/cards/card_completer_proxy_model.cpp b/cockatrice/src/game/cards/card_completer_proxy_model.cpp new file mode 100644 index 000000000..387eb454f --- /dev/null +++ b/cockatrice/src/game/cards/card_completer_proxy_model.cpp @@ -0,0 +1,18 @@ +#include "card_completer_proxy_model.h" + +CardCompleterProxyModel::CardCompleterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) +{ +} + +bool CardCompleterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if (filterRegularExpression().pattern().isEmpty()) { + return true; + } + + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + QString data = index.data(Qt::DisplayRole).toString(); + + // Ensure substring matching + return data.contains(filterRegularExpression()); +} diff --git a/cockatrice/src/game/cards/card_completer_proxy_model.h b/cockatrice/src/game/cards/card_completer_proxy_model.h new file mode 100644 index 000000000..73cf223ed --- /dev/null +++ b/cockatrice/src/game/cards/card_completer_proxy_model.h @@ -0,0 +1,16 @@ +#ifndef CARD_COMPLETER_PROXY_MODEL_H +#define CARD_COMPLETER_PROXY_MODEL_H + +#include + +class CardCompleterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit CardCompleterProxyModel(QObject *parent = nullptr); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; +}; + +#endif // CARD_COMPLETER_PROXY_MODEL_H diff --git a/cockatrice/src/game/cards/card_database.cpp b/cockatrice/src/game/cards/card_database.cpp index 044f2c623..7181ffd06 100644 --- a/cockatrice/src/game/cards/card_database.cpp +++ b/cockatrice/src/game/cards/card_database.cpp @@ -472,6 +472,45 @@ QStringList CardDatabase::getAllMainCardTypes() const return types.values(); } +QMap CardDatabase::getAllMainCardTypesWithCount() const +{ + QMap typeCounts; + QHashIterator cardIterator(cards); + + while (cardIterator.hasNext()) { + QString type = cardIterator.next().value()->getMainCardType(); + typeCounts[type]++; + } + + return typeCounts; +} + +QMap CardDatabase::getAllSubCardTypesWithCount() const +{ + QMap typeCounts; + QHashIterator cardIterator(cards); + + while (cardIterator.hasNext()) { + QString type = cardIterator.next().value()->getCardType(); + + 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]++; + } + } + } + + return typeCounts; +} + void CardDatabase::checkUnknownSets() { auto _sets = getSetList(); diff --git a/cockatrice/src/game/cards/card_database.h b/cockatrice/src/game/cards/card_database.h index 236278b30..2de5999ee 100644 --- a/cockatrice/src/game/cards/card_database.h +++ b/cockatrice/src/game/cards/card_database.h @@ -28,9 +28,6 @@ enum LoadStatus NoCards }; -typedef QHash CardNameMap; -typedef QHash SetNameMap; - class CardDatabase : public QObject { Q_OBJECT @@ -98,6 +95,8 @@ public: LoadStatus loadFromFile(const QString &fileName); bool saveCustomTokensToFile(); QStringList getAllMainCardTypes() const; + QMap getAllMainCardTypesWithCount() const; + QMap getAllSubCardTypesWithCount() const; LoadStatus getLoadStatus() const { return loadStatus; diff --git a/cockatrice/src/game/cards/card_database_model.h b/cockatrice/src/game/cards/card_database_model.h index 5a6b8feec..9f11c6611 100644 --- a/cockatrice/src/game/cards/card_database_model.h +++ b/cockatrice/src/game/cards/card_database_model.h @@ -87,6 +87,7 @@ public: void setIsToken(FilterBool _isToken) { isToken = _isToken; + emit modelDirty(); dirty(); } @@ -97,17 +98,20 @@ public: filterString = nullptr; } cardName = sanitizeCardName(_cardName, characterTranslation); + emit modelDirty(); dirty(); } void setStringFilter(const QString &_src) { delete filterString; filterString = new FilterString(_src); + emit modelDirty(); dirty(); } void setCardNameSet(const QSet &_cardNameSet) { cardNameSet = _cardNameSet; + emit modelDirty(); dirty(); } @@ -119,6 +123,8 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; 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; diff --git a/cockatrice/src/game/cards/card_database_parser/card_database_parser.h b/cockatrice/src/game/cards/card_database_parser/card_database_parser.h index 12da8efbb..b7ef36636 100644 --- a/cockatrice/src/game/cards/card_database_parser/card_database_parser.h +++ b/cockatrice/src/game/cards/card_database_parser/card_database_parser.h @@ -1,7 +1,7 @@ #ifndef CARDDATABASE_PARSER_H #define CARDDATABASE_PARSER_H -#include "../card_database.h" +#include "../card_info.h" #include #include diff --git a/cockatrice/src/game/cards/card_info.h b/cockatrice/src/game/cards/card_info.h index 9d06bc5f6..76397588f 100644 --- a/cockatrice/src/game/cards/card_info.h +++ b/cockatrice/src/game/cards/card_info.h @@ -25,6 +25,9 @@ typedef QSharedPointer CardInfoPtr; typedef QSharedPointer CardSetPtr; typedef QMap> CardInfoPerSetMap; +typedef QHash CardNameMap; +typedef QHash SetNameMap; + Q_DECLARE_METATYPE(CardInfoPtr) class CardSet : public QList diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp index c088a4ed1..40fa8d119 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/cards/card_item.cpp @@ -8,8 +8,8 @@ #include "../zones/card_zone.h" #include "../zones/table_zone.h" #include "../zones/view_zone.h" -#include "card_database.h" #include "card_drag_item.h" +#include "card_info.h" #include "pb/serverinfo_card.pb.h" #include diff --git a/cockatrice/src/game/cards/card_list.cpp b/cockatrice/src/game/cards/card_list.cpp index 3a353ce3f..0eda14626 100644 --- a/cockatrice/src/game/cards/card_list.cpp +++ b/cockatrice/src/game/cards/card_list.cpp @@ -1,6 +1,6 @@ #include "card_list.h" -#include "card_database.h" +#include "card_info.h" #include "card_item.h" #include diff --git a/cockatrice/src/game/cards/card_search_model.cpp b/cockatrice/src/game/cards/card_search_model.cpp new file mode 100644 index 000000000..56b5ea530 --- /dev/null +++ b/cockatrice/src/game/cards/card_search_model.cpp @@ -0,0 +1,71 @@ +#include "card_search_model.h" + +#include "../../utility/levenshtein.h" + +#include + +CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent) + : QAbstractListModel(parent), sourceModel(sourceModel) +{ +} + +int CardSearchModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return searchResults.size(); +} + +QVariant CardSearchModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= searchResults.size()) + return QVariant(); + + if (role == Qt::DisplayRole) { + return searchResults.at(index.row()).card->getName(); + } + + return QVariant(); +} + +void CardSearchModel::updateSearchResults(const QString &query) +{ + beginResetModel(); + searchResults.clear(); + + if (query.isEmpty() || !sourceModel) + return; + + // Set the filter for the display model + sourceModel->setCardName(query); + + // Collect matching cards and compute Levenshtein distance + for (int i = 0; i < sourceModel->rowCount(); ++i) { + QModelIndex modelIndex = sourceModel->index(i, 0); + QModelIndex sourceIndex = sourceModel->mapToSource(modelIndex); + CardDatabaseModel *sourceDbModel = qobject_cast(sourceModel->sourceModel()); + + if (!sourceDbModel || !sourceIndex.isValid()) + return; + + CardInfoPtr card = sourceDbModel->getCard(sourceIndex.row()); + + if (!card) + continue; + + int distance = levenshteinDistance(query.toLower(), card->getName().toLower()); + searchResults.append({card, distance}); + } + + // Sort by Levenshtein distance (lower distance = better match) + std::sort(searchResults.begin(), searchResults.end(), + [](const SearchResult &a, const SearchResult &b) { return a.distance < b.distance; }); + + // Keep only the top 5 results + if (searchResults.size() > 10) + searchResults = searchResults.mid(0, 10); + + emit dataChanged(index(0, 0), index(rowCount() - 1, 0)); + emit layoutChanged(); + + endResetModel(); +} diff --git a/cockatrice/src/game/cards/card_search_model.h b/cockatrice/src/game/cards/card_search_model.h new file mode 100644 index 000000000..c0575cb61 --- /dev/null +++ b/cockatrice/src/game/cards/card_search_model.h @@ -0,0 +1,30 @@ +#ifndef CARD_SEARCH_MODEL_H +#define CARD_SEARCH_MODEL_H + +#include "card_database_model.h" + +#include + +class CardSearchModel : public QAbstractListModel +{ + Q_OBJECT +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; + + void updateSearchResults(const QString &query); // Update results based on input + +private: + struct SearchResult + { + CardInfoPtr card; + int distance; + }; + + CardDatabaseDisplayModel *sourceModel; + QList searchResults; +}; + +#endif // CARD_SEARCH_MODEL_H diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index 41c6ed45a..15a8adcac 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -1,7 +1,7 @@ #include "deck_view.h" #include "../../client/ui/theme_manager.h" -#include "../../game/cards/card_database.h" +#include "../../game/cards/card_info.h" #include "../../settings/cache_settings.h" #include "decklist.h" diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index f7ec617ee..81ad8d322 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -57,19 +57,13 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) forceStartGameButton = new QPushButton; sideboardLockButton = new ToggleButton; - connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck())); - connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart())); + connect(loadLocalButton, &QPushButton::clicked, this, &DeckViewContainer::loadLocalDeck); + connect(loadRemoteButton, &QPushButton::clicked, this, &DeckViewContainer::loadRemoteDeck); + connect(readyStartButton, &QPushButton::clicked, this, &DeckViewContainer::readyStart); connect(unloadDeckButton, &QPushButton::clicked, this, &DeckViewContainer::unloadDeck); connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart); - connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked())); - connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText())); - - if (parentGame->getIsLocalGame()) { - loadRemoteButton->setVisible(false); - loadRemoteButton->setEnabled(false); - } else { - connect(loadRemoteButton, SIGNAL(clicked()), this, SLOT(loadRemoteDeck())); - } + connect(sideboardLockButton, &QPushButton::clicked, this, &DeckViewContainer::sideboardLockButtonClicked); + connect(sideboardLockButton, &ToggleButton::stateChanged, this, &DeckViewContainer::updateSideboardLockButtonText); auto *buttonHBox = new QHBoxLayout; buttonHBox->addWidget(loadLocalButton); @@ -83,8 +77,8 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) buttonHBox->addStretch(); deckView = new DeckView; - connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); - connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged())); + connect(deckView, &DeckView::newCardAdded, this, &DeckViewContainer::newCardAdded); + connect(deckView, &DeckView::sideboardPlanChanged, this, &DeckViewContainer::sideboardPlanChanged); deckViewLayout = new QVBoxLayout; deckViewLayout->addLayout(buttonHBox); @@ -93,7 +87,8 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) setLayout(deckViewLayout); retranslateUi(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &DeckViewContainer::refreshShortcuts); refreshShortcuts(); connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageInGameChanged, this, @@ -268,8 +263,7 @@ void DeckViewContainer::loadDeckFromFile(const QString &filePath) Command_DeckSelect cmd; cmd.set_deck(deckString.toStdString()); PendingCommand *pend = parentGame->prepareGameCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(deckSelectFinished(const Response &))); + connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished); parentGame->sendGameCommand(pend, playerId); } @@ -280,8 +274,7 @@ void DeckViewContainer::loadRemoteDeck() Command_DeckSelect cmd; cmd.set_deck_id(dlg.getDeckId()); PendingCommand *pend = parentGame->prepareGameCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(deckSelectFinished(const Response &))); + connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished); parentGame->sendGameCommand(pend, playerId); } } @@ -305,7 +298,7 @@ void DeckViewContainer::forceStart() { const auto msg = tr("Are you sure you want to force start?\nThis will kick all non-ready players from the game."); const auto res = - QMessageBox::warning(this, tr("Cockatrice"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + QMessageBox::question(this, tr("Cockatrice"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res == QMessageBox::No) { return; } diff --git a/cockatrice/src/game/filters/filter_card.cpp b/cockatrice/src/game/filters/filter_card.cpp index d91129147..d49a1b9cb 100644 --- a/cockatrice/src/game/filters/filter_card.cpp +++ b/cockatrice/src/game/filters/filter_card.cpp @@ -1,5 +1,44 @@ #include "filter_card.h" +#include + +QJsonObject CardFilter::toJson() const +{ + QJsonObject obj; + obj["term"] = trm; + obj["type"] = typeName(t); + obj["attr"] = attrName(a); + return obj; +} + +CardFilter *CardFilter::fromJson(const QJsonObject &obj) +{ + QString term = obj["term"].toString(); + QString typeStr = obj["type"].toString(); + QString attrStr = obj["attr"].toString(); + + Type type = TypeEnd; + Attr attr = AttrEnd; + + // Convert type string back to enum + for (int i = 0; i < TypeEnd; i++) { + if (typeName(static_cast(i)) == typeStr) { + type = static_cast(i); + break; + } + } + + // Convert attr string back to enum + for (int i = 0; i < AttrEnd; i++) { + if (attrName(static_cast(i)) == attrStr) { + attr = static_cast(i); + break; + } + } + + return new CardFilter(term, type, attr); +} + const QString CardFilter::typeName(Type t) { switch (t) { @@ -43,6 +82,10 @@ const QString CardFilter::attrName(Attr a) return tr("Loyalty"); case AttrFormat: return tr("Format"); + case AttrMainType: + return tr("Main Type"); + case AttrSubType: + return tr("Sub Type"); default: return QString(""); } diff --git a/cockatrice/src/game/filters/filter_card.h b/cockatrice/src/game/filters/filter_card.h index 7412d0f8d..210d5c062 100644 --- a/cockatrice/src/game/filters/filter_card.h +++ b/cockatrice/src/game/filters/filter_card.h @@ -34,8 +34,10 @@ public: AttrText, AttrTough, AttrType, + AttrMainType, + AttrSubType, AttrFormat, - AttrEnd + AttrEnd, }; private: @@ -59,6 +61,8 @@ public: return a; } + QJsonObject toJson() const; + static CardFilter *fromJson(const QJsonObject &json); static const QString typeName(Type t); static const QString attrName(Attr a); }; diff --git a/cockatrice/src/game/filters/filter_string.h b/cockatrice/src/game/filters/filter_string.h index a633a235b..b3877e54a 100644 --- a/cockatrice/src/game/filters/filter_string.h +++ b/cockatrice/src/game/filters/filter_string.h @@ -1,7 +1,7 @@ #ifndef FILTER_STRING_H #define FILTER_STRING_H -#include "../cards/card_database.h" +#include "../cards/card_info.h" #include "filter_tree.h" #include diff --git a/cockatrice/src/game/filters/filter_tree.cpp b/cockatrice/src/game/filters/filter_tree.cpp index d36a1d253..170fb35d8 100644 --- a/cockatrice/src/game/filters/filter_tree.cpp +++ b/cockatrice/src/game/filters/filter_tree.cpp @@ -158,6 +158,18 @@ bool FilterItem::acceptType(const CardInfoPtr info) const return info->getCardType().contains(term, Qt::CaseInsensitive); } +bool FilterItem::acceptMainType(const CardInfoPtr info) const +{ + const QStringList typeParts = info->getCardType().split(" — "); + return !typeParts.isEmpty() && typeParts[0].contains(term, Qt::CaseInsensitive); +} + +bool FilterItem::acceptSubType(const CardInfoPtr info) const +{ + const QStringList typeParts = info->getCardType().split(" — "); + return typeParts.size() > 1 && typeParts[1].contains(term, Qt::CaseInsensitive); +} + bool FilterItem::acceptColor(const CardInfoPtr info) const { QString converted_term = term.trimmed(); @@ -411,6 +423,10 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) c return acceptLoyalty(info); case CardFilter::AttrFormat: return acceptFormat(info); + case CardFilter::AttrMainType: + return acceptMainType(info); + case CardFilter::AttrSubType: + return acceptSubType(info); default: return true; /* ignore this attribute */ } @@ -504,9 +520,21 @@ bool FilterTree::acceptsCard(const CardInfoPtr info) const return true; } +void FilterTree::removeFiltersByAttr(CardFilter::Attr filterType) +{ + for (int i = childNodes.size() - 1; i >= 0; --i) { + auto *child = dynamic_cast(childNodes.at(i)); + + if (child && child->attr == filterType) { + deleteAt(i); + } + } +} + void FilterTree::clear() { while (childCount() > 0) { deleteAt(0); } + emit changed(); } diff --git a/cockatrice/src/game/filters/filter_tree.h b/cockatrice/src/game/filters/filter_tree.h index 08e76584f..895e1c1f7 100644 --- a/cockatrice/src/game/filters/filter_tree.h +++ b/cockatrice/src/game/filters/filter_tree.h @@ -97,6 +97,7 @@ protected: public: virtual ~FilterTreeBranch(); + void removeFiltersByAttr(CardFilter::Attr filterType); FilterTreeNode *nodeAt(int i) const override; void deleteAt(int i) override; int childCount() const override @@ -198,6 +199,8 @@ public: 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; @@ -266,6 +269,7 @@ public: } bool acceptsCard(CardInfoPtr info) const; + void removeFiltersByAttr(CardFilter::Attr filterType); void clear(); }; diff --git a/cockatrice/src/game/filters/filter_tree_model.cpp b/cockatrice/src/game/filters/filter_tree_model.cpp index 7541fc243..ec98f7c98 100644 --- a/cockatrice/src/game/filters/filter_tree_model.cpp +++ b/cockatrice/src/game/filters/filter_tree_model.cpp @@ -78,6 +78,62 @@ void FilterTreeModel::addFilter(const CardFilter *f) emit layoutChanged(); } +void FilterTreeModel::clearFiltersOfType(CardFilter::Attr filterType) +{ + emit layoutAboutToBeChanged(); + + // Recursively remove all nodes with the given filter type + fTree->removeFiltersByAttr(filterType); + + emit layoutChanged(); +} + +QList FilterTreeModel::getFiltersOfType(CardFilter::Attr filterType) const +{ + QList filters; + if (!fTree) { + return filters; + } + + std::function traverse; + traverse = [&](const FilterTreeNode *node) { + if (const auto *filterItem = dynamic_cast(node)) { + if (filterItem->attr() == filterType) { + QString text = filterItem->text(); + filters.append(new CardFilter(text, filterItem->type(), filterItem->attr())); + } + } + for (int i = 0; i < node->childCount(); ++i) { + traverse(node->nodeAt(i)); + } + }; + + traverse(fTree); + return filters; +} + +QList FilterTreeModel::allFilters() const +{ + QList filters; + if (!fTree) { + return filters; + } + + std::function traverse; + traverse = [&](const FilterTreeNode *node) { + if (const auto *filterItem = dynamic_cast(node)) { + QString text = filterItem->text(); + filters.append(new CardFilter(text, filterItem->type(), filterItem->attr())); + } + for (int i = 0; i < node->childCount(); ++i) { + traverse(node->nodeAt(i)); + } + }; + + traverse(fTree); + return filters; +} + int FilterTreeModel::rowCount(const QModelIndex &parent) const { const FilterTreeNode *node; @@ -257,3 +313,10 @@ bool FilterTreeModel::removeRows(int row, int count, const QModelIndex &parent) return true; } + +void FilterTreeModel::clear() +{ + emit layoutAboutToBeChanged(); + fTree->clear(); + emit layoutChanged(); +} diff --git a/cockatrice/src/game/filters/filter_tree_model.h b/cockatrice/src/game/filters/filter_tree_model.h index 3ea2c4013..4e98aa02a 100644 --- a/cockatrice/src/game/filters/filter_tree_model.h +++ b/cockatrice/src/game/filters/filter_tree_model.h @@ -1,6 +1,8 @@ #ifndef FILTERTREEMODEL_H #define FILTERTREEMODEL_H +#include "filter_card.h" + #include class FilterTree; @@ -15,6 +17,9 @@ private: public slots: void addFilter(const CardFilter *f); + void clearFiltersOfType(CardFilter::Attr filterType); + QList getFiltersOfType(CardFilter::Attr filterType) const; + QList allFilters() const; private slots: void proxyBeginInsertRow(const FilterTreeNode *, int); @@ -41,6 +46,7 @@ public: QModelIndex parent(const QModelIndex &ind) const override; QModelIndex index(int row, int column, const QModelIndex &parent) const override; bool removeRows(int row, int count, const QModelIndex &parent) override; + void clear(); }; #endif diff --git a/cockatrice/src/game/filters/syntax_help.cpp b/cockatrice/src/game/filters/syntax_help.cpp new file mode 100644 index 000000000..a4eaf9112 --- /dev/null +++ b/cockatrice/src/game/filters/syntax_help.cpp @@ -0,0 +1,62 @@ +#include "syntax_help.h" + +#include +#include +#include + +/** + * Creates the card search syntax help window + * + * @return the QTextBrowser + */ +static QTextBrowser *createBrowser() +{ + QFile file("theme:help/search.md"); + + if (!file.open(QFile::ReadOnly | QFile::Text)) { + return nullptr; + } + + QTextStream in(&file); + QString text = in.readAll(); + file.close(); + + // Poor Markdown Converter + auto opts = QRegularExpression::MultilineOption; + text = text.replace(QRegularExpression("^(###)(.*)", opts), "

\\2

") + .replace(QRegularExpression("^(##)(.*)", opts), "

\\2

") + .replace(QRegularExpression("^(#)(.*)", opts), "

\\2

") + .replace(QRegularExpression("^------*", opts), "
") + .replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(\1)"); + + auto browser = new QTextBrowser(); + browser->setParent(nullptr, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | + Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint | + Qt::WindowFullscreenButtonHint); + browser->setWindowTitle("Search Help"); + browser->setReadOnly(true); + browser->setMinimumSize({500, 600}); + + QString sheet = QString("a { text-decoration: underline; color: rgb(71,158,252) };"); + browser->document()->setDefaultStyleSheet(sheet); + browser->setHtml(text); + + browser->show(); + + return browser; +} + +/** + * Creates the card search syntax help window and connects its anchorClicked signal to the given QLineEdit. + * The window will automatically close when the QLineEdit is destroyed. + * + * @return the QTextBrowser + */ +QTextBrowser *createSearchSyntaxHelpWindow(QLineEdit *lineEdit) +{ + auto browser = createBrowser(); + QObject::connect(browser, &QTextBrowser::anchorClicked, + [lineEdit](const QUrl &link) { lineEdit->setText(link.fragment()); }); + QObject::connect(lineEdit, &QObject::destroyed, browser, &QTextBrowser::close); + return browser; +} \ No newline at end of file diff --git a/cockatrice/src/game/filters/syntax_help.h b/cockatrice/src/game/filters/syntax_help.h new file mode 100644 index 000000000..4016e02bb --- /dev/null +++ b/cockatrice/src/game/filters/syntax_help.h @@ -0,0 +1,9 @@ +#ifndef SEARCH_SYNTAX_HELP_H +#define SEARCH_SYNTAX_HELP_H + +#include +#include + +QTextBrowser *createSearchSyntaxHelpWindow(QLineEdit *lineEdit); + +#endif // SEARCH_SYNTAX_HELP_H diff --git a/cockatrice/src/game/game_view.cpp b/cockatrice/src/game/game_view.cpp index 7ce9dda3e..969e26e14 100644 --- a/cockatrice/src/game/game_view.cpp +++ b/cockatrice/src/game/game_view.cpp @@ -11,7 +11,7 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par { setBackgroundBrush(QBrush(QColor(0, 0, 0))); setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing); - setFocusPolicy(Qt::NoFocus); + setFocusPolicy(Qt::ClickFocus); setViewportUpdateMode(BoundingRectViewportUpdate); connect(scene, &GameScene::sceneRectChanged, this, &GameView::updateSceneRect); diff --git a/cockatrice/src/game/phase.cpp b/cockatrice/src/game/phase.cpp index a8498354a..32c9060ee 100644 --- a/cockatrice/src/game/phase.cpp +++ b/cockatrice/src/game/phase.cpp @@ -1,8 +1,16 @@ #include "phase.h" -Phase::Phase(const char *_name, QString _color, QString _soundFileName) : color(_color), soundFileName(_soundFileName) +Phase::Phase(const QString &_name, const QString &_color, const QString &_soundFileName) + : name(_name), color(_color), soundFileName(_soundFileName) { - name = tr(_name); +} + +/** + * @return The translated name for the phase + */ +QString Phase::getName() const +{ + return tr(name.toUtf8().data()); } Phase Phases::getPhase(int phase) diff --git a/cockatrice/src/game/phase.h b/cockatrice/src/game/phase.h index d882a0391..217f13f0f 100644 --- a/cockatrice/src/game/phase.h +++ b/cockatrice/src/game/phase.h @@ -1,16 +1,20 @@ #ifndef PHASE_H #define PHASE_H +#include #include -#include class Phase { Q_DECLARE_TR_FUNCTIONS(Phase) + QString name; + public: - QString name, color, soundFileName; - Phase(const char *_name, QString _color, QString _soundFileName); + QString color, soundFileName; + Phase(const QString &_name, const QString &_color, const QString &_soundFileName); + + QString getName() const; }; struct Phases diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index d82a6ee6e..26f34e977 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -4,7 +4,6 @@ #include "../../client/tabs/tab_game.h" #include "../../client/ui/theme_manager.h" #include "../../deck/deck_loader.h" -#include "../../dialogs/dlg_create_token.h" #include "../../dialogs/dlg_move_top_cards_until.h" #include "../../dialogs/dlg_roll_dice.h" #include "../../main.h" @@ -109,9 +108,9 @@ void PlayerArea::setPlayerZoneId(int _playerZoneId) } Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, TabGame *_parent) - : QObject(_parent), game(_parent), movingCardsUntil(false), shortcutsActive(false), lastTokenDestroy(true), - lastTokenTableRow(0), id(_id), active(false), local(_local), judge(_judge), mirrored(false), handVisible(false), - conceded(false), zoneId(0), dialogSemaphore(false), deck(nullptr) + : QObject(_parent), game(_parent), movingCardsUntil(false), shortcutsActive(false), lastTokenTableRow(0), id(_id), + active(false), local(_local), judge(_judge), mirrored(false), handVisible(false), conceded(false), zoneId(0), + dialogSemaphore(false), deck(nullptr) { userInfo = new ServerInfo_User; userInfo->CopyFrom(info); @@ -1826,37 +1825,35 @@ void Player::actCreateToken() return; } - lastTokenName = dlg.getName(); - lastTokenPT = dlg.getPT(); - CardInfoPtr correctedCard = CardDatabaseManager::getInstance()->guessCard(lastTokenName); + lastTokenInfo = dlg.getTokenInfo(); + + CardInfoPtr correctedCard = CardDatabaseManager::getInstance()->guessCard(lastTokenInfo.name); if (correctedCard) { - lastTokenName = correctedCard->getName(); + lastTokenInfo.name = correctedCard->getName(); lastTokenTableRow = TableZone::clampValidTableRow(2 - correctedCard->getTableRow()); - if (lastTokenPT.isEmpty()) { - lastTokenPT = correctedCard->getPowTough(); + if (lastTokenInfo.pt.isEmpty()) { + lastTokenInfo.pt = correctedCard->getPowTough(); } } - lastTokenColor = dlg.getColor(); - lastTokenAnnotation = dlg.getAnnotation(); - lastTokenDestroy = dlg.getDestroy(); + aCreateAnotherToken->setEnabled(true); - aCreateAnotherToken->setText(tr("C&reate another %1 token").arg(lastTokenName)); + aCreateAnotherToken->setText(tr("C&reate another %1 token").arg(lastTokenInfo.name)); actCreateAnotherToken(); } void Player::actCreateAnotherToken() { - if (lastTokenName.isEmpty()) { + if (lastTokenInfo.name.isEmpty()) { return; } Command_CreateToken cmd; cmd.set_zone("table"); - cmd.set_card_name(lastTokenName.toStdString()); - cmd.set_color(lastTokenColor.toStdString()); - cmd.set_pt(lastTokenPT.toStdString()); - cmd.set_annotation(lastTokenAnnotation.toStdString()); - cmd.set_destroy_on_zone_change(lastTokenDestroy); + cmd.set_card_name(lastTokenInfo.name.toStdString()); + cmd.set_color(lastTokenInfo.color.toStdString()); + cmd.set_pt(lastTokenInfo.pt.toStdString()); + cmd.set_annotation(lastTokenInfo.annotation.toStdString()); + cmd.set_destroy_on_zone_change(lastTokenInfo.destroy); cmd.set_x(-1); cmd.set_y(lastTokenTableRow); @@ -4180,12 +4177,13 @@ void Player::setLastToken(CardInfoPtr cardInfo) return; } - lastTokenName = cardInfo->getName(); - lastTokenColor = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().left(1).toLower(); - lastTokenPT = cardInfo->getPowTough(); - lastTokenAnnotation = SettingsCache::instance().getAnnotateTokens() ? cardInfo->getText() : ""; + lastTokenInfo = {.name = cardInfo->getName(), + .color = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().left(1).toLower(), + .pt = cardInfo->getPowTough(), + .annotation = SettingsCache::instance().getAnnotateTokens() ? cardInfo->getText() : "", + .destroy = true}; + lastTokenTableRow = TableZone::clampValidTableRow(2 - cardInfo->getTableRow()); - lastTokenDestroy = true; - aCreateAnotherToken->setText(tr("C&reate another %1 token").arg(lastTokenName)); + aCreateAnotherToken->setText(tr("C&reate another %1 token").arg(lastTokenInfo.name)); aCreateAnotherToken->setEnabled(true); } diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 26df3ae50..76365d50d 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -2,8 +2,9 @@ #define PLAYER_H #include "../../client/tearoff_menu.h" +#include "../../dialogs/dlg_create_token.h" #include "../board/abstract_graphics_item.h" -#include "../cards/card_database.h" +#include "../cards/card_info.h" #include "../filters/filter_string.h" #include "pb/card_attributes.pb.h" #include "pb/game_event.pb.h" @@ -291,9 +292,10 @@ private: int defaultNumberTopCardsToPlaceBelow = 1; int defaultNumberBottomCards = 1; int defaultNumberDieRoll = 20; - QString lastTokenName, lastTokenColor, lastTokenPT, lastTokenAnnotation; - bool lastTokenDestroy; + + TokenInfo lastTokenInfo; int lastTokenTableRow; + ServerInfo_User *userInfo; int id; bool active; diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index 71327b9ec..02a06ed2e 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -3,8 +3,8 @@ #include "../../client/ui/theme_manager.h" #include "../../settings/cache_settings.h" #include "../board/arrow_item.h" -#include "../cards/card_database.h" #include "../cards/card_drag_item.h" +#include "../cards/card_info.h" #include "../cards/card_item.h" #include "../player/player.h" #include "pb/command_move_card.pb.h" diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index 9e9ba6e81..9aec01db3 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -1,8 +1,8 @@ #include "view_zone.h" #include "../../server/pending_command.h" -#include "../cards/card_database.h" #include "../cards/card_drag_item.h" +#include "../cards/card_info.h" #include "../cards/card_item.h" #include "../player/player.h" #include "pb/command_dump_zone.pb.h" @@ -148,7 +148,16 @@ void ZoneViewZone::updateCardIds(CardAction action) // Because of boundingRect(), this function must not be called before the zone was added to a scene. void ZoneViewZone::reorganizeCards() { - CardList cardsToDisplay(cards); + // filter cards + CardList cardsToDisplay = CardList(cards.getContentsKnown()); + for (auto card : cards) { + if (filterString.check(card->getInfo())) { + card->show(); + cardsToDisplay.append(card); + } else { + card->hide(); + } + } // sort cards QList sortOptions; @@ -263,6 +272,12 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca } } +void ZoneViewZone::setFilterString(const QString &_filterString) +{ + filterString = FilterString(_filterString); + reorganizeCards(); +} + void ZoneViewZone::setGroupBy(CardList::SortOption _groupBy) { groupBy = _groupBy; diff --git a/cockatrice/src/game/zones/view_zone.h b/cockatrice/src/game/zones/view_zone.h index 3c69317aa..84fbe5ce6 100644 --- a/cockatrice/src/game/zones/view_zone.h +++ b/cockatrice/src/game/zones/view_zone.h @@ -1,6 +1,7 @@ #ifndef ZONEVIEWERZONE_H #define ZONEVIEWERZONE_H +#include "../filters/filter_string.h" #include "select_zone.h" #include @@ -35,6 +36,7 @@ private: int minRows, numberCards; CardZone *origZone; bool revealZone, writeableRevealZone; + FilterString filterString = FilterString(""); CardList::SortOption groupBy, sortBy; bool pileView; bool isReversed; @@ -96,6 +98,7 @@ public: } public slots: void close(); + void setFilterString(const QString &_filterString); void setGroupBy(CardList::SortOption _groupBy); void setSortBy(CardList::SortOption _sortBy); void setPileView(int _pileView); diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 7dd21b9d5..85a91afdf 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -1,7 +1,9 @@ #include "view_zone_widget.h" +#include "../../client/ui/pixel_map_generator.h" #include "../../settings/cache_settings.h" #include "../cards/card_item.h" +#include "../filters/syntax_help.h" #include "../game_scene.h" #include "../player/player.h" #include "pb/command_shuffle.pb.h" @@ -41,9 +43,24 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, setFlag(ItemIgnoresTransformations); QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical); + vbox->setSpacing(2); // If the number is < 0, then it means that we can give the option to make the area sorted if (numberCards < 0) { + // search edit + searchEdit.setFocusPolicy(Qt::ClickFocus); + searchEdit.setPlaceholderText(tr("Search by card name (or search expressions)")); + searchEdit.setClearButtonEnabled(true); + searchEdit.addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); + auto help = searchEdit.addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); + + connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(&searchEdit); }); + + QGraphicsProxyWidget *searchEditProxy = new QGraphicsProxyWidget; + searchEditProxy->setWidget(&searchEdit); + searchEditProxy->setZValue(2000000007); + vbox->addItem(searchEditProxy); + // top row QGraphicsLinearLayout *hTopRow = new QGraphicsLinearLayout(Qt::Horizontal); @@ -128,6 +145,8 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, if (CardList::NoSort == static_cast(groupBySelector.currentData().toInt())) { pileViewCheckBox.setEnabled(false); } + + connect(&searchEdit, &QLineEdit::textChanged, zone, &ZoneViewZone::setFilterString); } setLayout(vbox); diff --git a/cockatrice/src/game/zones/view_zone_widget.h b/cockatrice/src/game/zones/view_zone_widget.h index 177bcb92a..04662ebc9 100644 --- a/cockatrice/src/game/zones/view_zone_widget.h +++ b/cockatrice/src/game/zones/view_zone_widget.h @@ -7,6 +7,7 @@ #include #include #include +#include class QLabel; class QPushButton; @@ -47,6 +48,8 @@ private: QPushButton *closeButton; QScrollBar *scrollBar; ScrollableGraphicsProxyWidget *scrollBarProxy; + + QLineEdit searchEdit; QComboBox groupBySelector; QComboBox sortBySelector; QCheckBox shuffleCheckBox; diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 2122ccbd8..be720a34e 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -28,7 +28,6 @@ #include "client/ui/window_main.h" #include "dialogs/dlg_settings.h" #include "featureset.h" -#include "game/cards/card_database.h" #include "rng_sfmt.h" #include "settings/cache_settings.h" #include "utility/logger.h" diff --git a/cockatrice/src/server/chat_view/chat_view.cpp b/cockatrice/src/server/chat_view/chat_view.cpp index 33249829d..f204ea9b4 100644 --- a/cockatrice/src/server/chat_view/chat_view.cpp +++ b/cockatrice/src/server/chat_view/chat_view.cpp @@ -60,7 +60,7 @@ ChatView::ChatView(TabSupervisor *_tabSupervisor, TabGame *_game, bool _showTime setReadOnly(true); setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); setOpenLinks(false); - connect(this, SIGNAL(anchorClicked(const QUrl &)), this, SLOT(openLink(const QUrl &))); + connect(this, &ChatView::anchorClicked, this, &ChatView::openLink); } void ChatView::retranslateUi() diff --git a/cockatrice/src/server/handle_public_servers.cpp b/cockatrice/src/server/handle_public_servers.cpp index e54cc0cf0..cc44ba262 100644 --- a/cockatrice/src/server/handle_public_servers.cpp +++ b/cockatrice/src/server/handle_public_servers.cpp @@ -19,7 +19,7 @@ void HandlePublicServers::downloadPublicServers() { QUrl url(QString(PUBLIC_SERVERS_JSON)); reply = nam->get(QNetworkRequest(url)); - connect(reply, SIGNAL(finished()), this, SLOT(actFinishParsingDownloadedData())); + connect(reply, &QNetworkReply::finished, this, &HandlePublicServers::actFinishParsingDownloadedData); } void HandlePublicServers::actFinishParsingDownloadedData() diff --git a/cockatrice/src/server/local_client.cpp b/cockatrice/src/server/local_client.cpp index b4c1f41ee..734660af1 100644 --- a/cockatrice/src/server/local_client.cpp +++ b/cockatrice/src/server/local_client.cpp @@ -1,5 +1,6 @@ #include "local_client.h" +#include "debug_pb_message.h" #include "local_server_interface.h" #include "pb/session_commands.pb.h" @@ -9,7 +10,9 @@ LocalClient::LocalClient(LocalServerInterface *_lsi, QObject *parent) : AbstractClient(parent), lsi(_lsi) { - connect(lsi, SIGNAL(itemToClient(const ServerMessage &)), this, SLOT(itemFromServer(const ServerMessage &))); + connect(lsi, &LocalServerInterface::itemToClient, this, &LocalClient::itemFromServer); + + userName = _playerName; Command_Login loginCmd; loginCmd.set_user_name(_playerName.toStdString()); @@ -27,10 +30,14 @@ LocalClient::~LocalClient() void LocalClient::sendCommandContainer(const CommandContainer &cont) { + qCDebug(LocalClientLog).noquote() << userName << "OUT" << getSafeDebugString(cont); + lsi->itemFromClient(cont); } void LocalClient::itemFromServer(const ServerMessage &item) { + qCDebug(LocalClientLog).noquote() << userName << "IN" << getSafeDebugString(item); + processProtocolItem(item); } diff --git a/cockatrice/src/server/local_client.h b/cockatrice/src/server/local_client.h index 06084b4d4..679d43f2b 100644 --- a/cockatrice/src/server/local_client.h +++ b/cockatrice/src/server/local_client.h @@ -3,6 +3,10 @@ #include "../client/game_logic/abstract_client.h" +#include + +inline Q_LOGGING_CATEGORY(LocalClientLog, "local_client"); + class LocalServerInterface; class LocalClient : public AbstractClient diff --git a/cockatrice/src/server/message_log_widget.cpp b/cockatrice/src/server/message_log_widget.cpp index 568253e31..2c0174673 100644 --- a/cockatrice/src/server/message_log_widget.cpp +++ b/cockatrice/src/server/message_log_widget.cpp @@ -616,7 +616,7 @@ void MessageLogWidget::logSetActivePhase(int phaseNumber) soundEngine->playSound(phase.soundFileName); appendHtml("" + QDateTime::currentDateTime().toString("[hh:mm:ss] ") + - phase.name + ""); + phase.getName() + ""); } void MessageLogWidget::logSetActivePlayer(Player *player) @@ -820,42 +820,28 @@ void MessageLogWidget::appendHtmlServerMessage(const QString &html, bool optiona void MessageLogWidget::connectToPlayer(Player *player) { - connect(player, SIGNAL(logSay(Player *, QString)), this, SLOT(logSay(Player *, QString))); + connect(player, &Player::logSay, this, &MessageLogWidget::logSay); connect(player, &Player::logShuffle, this, &MessageLogWidget::logShuffle); - connect(player, SIGNAL(logRollDie(Player *, int, const QList &)), this, - SLOT(logRollDie(Player *, int, const QList &))); - connect(player, SIGNAL(logCreateArrow(Player *, Player *, QString, Player *, QString, bool)), this, - SLOT(logCreateArrow(Player *, Player *, QString, Player *, QString, bool))); - connect(player, SIGNAL(logCreateToken(Player *, QString, QString)), this, - SLOT(logCreateToken(Player *, QString, QString))); - connect(player, SIGNAL(logSetCounter(Player *, QString, int, int)), this, - SLOT(logSetCounter(Player *, QString, int, int))); - connect(player, SIGNAL(logSetCardCounter(Player *, QString, int, int, int)), this, - SLOT(logSetCardCounter(Player *, QString, int, int, int))); - connect(player, SIGNAL(logSetTapped(Player *, CardItem *, bool)), this, - SLOT(logSetTapped(Player *, CardItem *, bool))); - connect(player, SIGNAL(logSetDoesntUntap(Player *, CardItem *, bool)), this, - SLOT(logSetDoesntUntap(Player *, CardItem *, bool))); - connect(player, SIGNAL(logSetPT(Player *, CardItem *, QString)), this, - SLOT(logSetPT(Player *, CardItem *, QString))); - connect(player, SIGNAL(logSetAnnotation(Player *, CardItem *, QString)), this, - SLOT(logSetAnnotation(Player *, CardItem *, QString))); - connect(player, SIGNAL(logMoveCard(Player *, CardItem *, CardZone *, int, CardZone *, int)), this, - SLOT(logMoveCard(Player *, CardItem *, CardZone *, int, CardZone *, int))); - connect(player, SIGNAL(logFlipCard(Player *, QString, bool)), this, SLOT(logFlipCard(Player *, QString, bool))); - connect(player, SIGNAL(logDestroyCard(Player *, QString)), this, SLOT(logDestroyCard(Player *, QString))); - connect(player, SIGNAL(logAttachCard(Player *, QString, Player *, QString)), this, - SLOT(logAttachCard(Player *, QString, Player *, QString))); - connect(player, SIGNAL(logUnattachCard(Player *, QString)), this, SLOT(logUnattachCard(Player *, QString))); + connect(player, &Player::logRollDie, this, &MessageLogWidget::logRollDie); + connect(player, &Player::logCreateArrow, this, &MessageLogWidget::logCreateArrow); + connect(player, &Player::logCreateToken, this, &MessageLogWidget::logCreateToken); + connect(player, &Player::logSetCounter, this, &MessageLogWidget::logSetCounter); + connect(player, &Player::logSetCardCounter, this, &MessageLogWidget::logSetCardCounter); + connect(player, &Player::logSetTapped, this, &MessageLogWidget::logSetTapped); + connect(player, &Player::logSetDoesntUntap, this, &MessageLogWidget::logSetDoesntUntap); + connect(player, &Player::logSetPT, this, &MessageLogWidget::logSetPT); + connect(player, &Player::logSetAnnotation, this, &MessageLogWidget::logSetAnnotation); + connect(player, &Player::logMoveCard, this, &MessageLogWidget::logMoveCard); + connect(player, &Player::logFlipCard, this, &MessageLogWidget::logFlipCard); + connect(player, &Player::logDestroyCard, this, &MessageLogWidget::logDestroyCard); + connect(player, &Player::logAttachCard, this, &MessageLogWidget::logAttachCard); + connect(player, &Player::logUnattachCard, this, &MessageLogWidget::logUnattachCard); connect(player, &Player::logDumpZone, this, &MessageLogWidget::logDumpZone); - connect(player, SIGNAL(logDrawCards(Player *, int, bool)), this, SLOT(logDrawCards(Player *, int, bool))); - connect(player, SIGNAL(logUndoDraw(Player *, QString)), this, SLOT(logUndoDraw(Player *, QString))); - connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int, bool)), this, - SLOT(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int, bool))); - connect(player, SIGNAL(logAlwaysRevealTopCard(Player *, CardZone *, bool)), this, - SLOT(logAlwaysRevealTopCard(Player *, CardZone *, bool))); - connect(player, SIGNAL(logAlwaysLookAtTopCard(Player *, CardZone *, bool)), this, - SLOT(logAlwaysLookAtTopCard(Player *, CardZone *, bool))); + connect(player, &Player::logDrawCards, this, &MessageLogWidget::logDrawCards); + connect(player, &Player::logUndoDraw, this, &MessageLogWidget::logUndoDraw); + connect(player, &Player::logRevealCards, this, &MessageLogWidget::logRevealCards); + connect(player, &Player::logAlwaysRevealTopCard, this, &MessageLogWidget::logAlwaysRevealTopCard); + connect(player, &Player::logAlwaysLookAtTopCard, this, &MessageLogWidget::logAlwaysLookAtTopCard); } MessageLogWidget::MessageLogWidget(TabSupervisor *_tabSupervisor, TabGame *_game, QWidget *parent) diff --git a/cockatrice/src/server/remote/remote_client.cpp b/cockatrice/src/server/remote/remote_client.cpp index 5812483ad..a573765af 100644 --- a/cockatrice/src/server/remote/remote_client.cpp +++ b/cockatrice/src/server/remote/remote_client.cpp @@ -36,42 +36,43 @@ RemoteClient::RemoteClient(QObject *parent) int keepalive = SettingsCache::instance().getKeepAlive(); timer = new QTimer(this); timer->setInterval(keepalive * 1000); - connect(timer, SIGNAL(timeout()), this, SLOT(ping())); + connect(timer, &QTimer::timeout, this, &RemoteClient::ping); socket = new QTcpSocket(this); socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); - connect(socket, SIGNAL(connected()), this, SLOT(slotConnected())); - connect(socket, SIGNAL(readyRead()), this, SLOT(readData())); + connect(socket, &QTcpSocket::connected, this, &RemoteClient::slotConnected); + connect(socket, &QTcpSocket::readyRead, this, &RemoteClient::readData); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) connect(socket, &QTcpSocket::errorOccurred, this, &RemoteClient::slotSocketError); #else - connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(slotSocketError(QAbstractSocket::SocketError))); + connect(socket, qOverload(&QTcpSocket::error), this, &RemoteClient::slotSocketError); #endif websocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this); connect(websocket, &QWebSocket::binaryMessageReceived, this, &RemoteClient::websocketMessageReceived); connect(websocket, &QWebSocket::connected, this, &RemoteClient::slotConnected); - connect(websocket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(slotWebSocketError(QAbstractSocket::SocketError))); - connect(this, SIGNAL(serverIdentificationEventReceived(const Event_ServerIdentification &)), this, - SLOT(processServerIdentificationEvent(const Event_ServerIdentification &))); - connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this, - SLOT(processConnectionClosedEvent(Event_ConnectionClosed))); - connect(this, SIGNAL(sigConnectToServer(QString, unsigned int, QString, QString)), this, - SLOT(doConnectToServer(QString, unsigned int, QString, QString))); - connect(this, SIGNAL(sigDisconnectFromServer()), this, SLOT(doDisconnectFromServer())); - connect(this, SIGNAL(sigRegisterToServer(QString, unsigned int, QString, QString, QString, QString, QString)), this, - SLOT(doRegisterToServer(QString, unsigned int, QString, QString, QString, QString, QString))); - connect(this, SIGNAL(sigActivateToServer(QString)), this, SLOT(doActivateToServer(QString))); - connect(this, SIGNAL(sigRequestForgotPasswordToServer(QString, unsigned int, QString)), this, - SLOT(doRequestForgotPasswordToServer(QString, unsigned int, QString))); - connect(this, SIGNAL(sigSubmitForgotPasswordResetToServer(QString, unsigned int, QString, QString, QString)), this, - SLOT(doSubmitForgotPasswordResetToServer(QString, unsigned int, QString, QString, QString))); - connect(this, SIGNAL(sigSubmitForgotPasswordChallengeToServer(QString, unsigned int, QString, QString)), this, - SLOT(doSubmitForgotPasswordChallengeToServer(QString, unsigned int, QString, QString))); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) + connect(websocket, &QWebSocket::errorOccurred, this, &RemoteClient::slotWebSocketError); +#else + connect(websocket, qOverload(&QWebSocket::error), this, + &RemoteClient::slotWebSocketError); +#endif + + connect(this, &RemoteClient::serverIdentificationEventReceived, this, + &RemoteClient::processServerIdentificationEvent); + connect(this, &RemoteClient::connectionClosedEventReceived, this, &RemoteClient::processConnectionClosedEvent); + connect(this, &RemoteClient::sigConnectToServer, this, &RemoteClient::doConnectToServer); + connect(this, &RemoteClient::sigDisconnectFromServer, this, &RemoteClient::doDisconnectFromServer); + connect(this, &RemoteClient::sigRegisterToServer, this, &RemoteClient::doRegisterToServer); + connect(this, &RemoteClient::sigActivateToServer, this, &RemoteClient::doActivateToServer); + connect(this, &RemoteClient::sigRequestForgotPasswordToServer, this, + &RemoteClient::doRequestForgotPasswordToServer); + connect(this, &RemoteClient::sigSubmitForgotPasswordResetToServer, this, + &RemoteClient::doSubmitForgotPasswordResetToServer); + connect(this, &RemoteClient::sigSubmitForgotPasswordChallengeToServer, this, + &RemoteClient::doSubmitForgotPasswordChallengeToServer); } RemoteClient::~RemoteClient() @@ -124,8 +125,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica cmdForgotPasswordRequest.set_user_name(userName.toStdString()); cmdForgotPasswordRequest.set_clientid(getSrvClientID(lastHostname).toStdString()); PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordRequest); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(requestForgotPasswordResponse(Response))); + connect(pend, &PendingCommand::finished, this, &RemoteClient::requestForgotPasswordResponse); sendCommand(pend); return; } @@ -144,8 +144,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica cmdForgotPasswordReset.set_new_password(password.toStdString()); } PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordReset); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(submitForgotPasswordResetResponse(Response))); + connect(pend, &PendingCommand::finished, this, &RemoteClient::submitForgotPasswordResetResponse); sendCommand(pend); return; } @@ -156,8 +155,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica cmdForgotPasswordChallenge.set_clientid(getSrvClientID(lastHostname).toStdString()); cmdForgotPasswordChallenge.set_email(email.toStdString()); PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordChallenge); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(submitForgotPasswordChallengeResponse(Response))); + connect(pend, &PendingCommand::finished, this, &RemoteClient::submitForgotPasswordChallengeResponse); sendCommand(pend); return; } @@ -178,7 +176,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica cmdRegister.set_real_name(realName.toStdString()); cmdRegister.set_clientid(getSrvClientID(lastHostname).toStdString()); PendingCommand *pend = prepareSessionCommand(cmdRegister); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(registerResponse(Response))); + connect(pend, &PendingCommand::finished, this, &RemoteClient::registerResponse); sendCommand(pend); return; @@ -191,7 +189,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica cmdActivate.set_clientid(getSrvClientID(lastHostname).toStdString()); PendingCommand *pend = prepareSessionCommand(cmdActivate); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(activateResponse(Response))); + connect(pend, &PendingCommand::finished, this, &RemoteClient::activateResponse); sendCommand(pend); return; @@ -207,7 +205,7 @@ void RemoteClient::doRequestPasswordSalt() cmdRqSalt.set_user_name(userName.toStdString()); PendingCommand *pend = prepareSessionCommand(cmdRqSalt); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(passwordSaltResponse(Response))); + connect(pend, &PendingCommand::finished, this, &RemoteClient::passwordSaltResponse); sendCommand(pend); } @@ -246,7 +244,7 @@ void RemoteClient::doLogin() } PendingCommand *pend = prepareSessionCommand(cmdLogin); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response))); + connect(pend, &PendingCommand::finished, this, &RemoteClient::loginResponse); sendCommand(pend); } } @@ -259,7 +257,7 @@ void RemoteClient::doHashedLogin() cmdLogin.set_hashed_password(hashedPassword.toStdString()); PendingCommand *pend = prepareSessionCommand(cmdLogin); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response))); + connect(pend, &PendingCommand::finished, this, &RemoteClient::loginResponse); sendCommand(pend); } @@ -339,6 +337,11 @@ void RemoteClient::registerResponse(const Response &response) emit registerAcceptedNeedsActivate(); doLogin(); break; + case Response::RespNotConnected: + // this response is created by the client from doDisconnectFromServer, do not call it again! + emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), + static_cast(resp.denied_end_time())); + break; default: emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), static_cast(resp.denied_end_time())); diff --git a/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp b/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp index 40ea30da6..0e6872646 100644 --- a/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp +++ b/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp @@ -263,8 +263,7 @@ void RemoteDeckList_TreeModel::removeNode(RemoteDeckList_TreeModel::Node *node) void RemoteDeckList_TreeModel::refreshTree() { PendingCommand *pend = client->prepareSessionCommand(Command_DeckList()); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(deckListFinished(const Response &))); + connect(pend, &PendingCommand::finished, this, &RemoteDeckList_TreeModel::deckListFinished); client->sendCommand(pend); } @@ -298,7 +297,7 @@ RemoteDeckList_TreeWidget::RemoteDeckList_TreeWidget(AbstractClient *_client, QW proxyModel->setDynamicSortFilter(true); proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); setModel(proxyModel); - connect(treeModel, SIGNAL(treeRefreshed()), this, SLOT(expandAll())); + connect(treeModel, &RemoteDeckList_TreeModel::treeRefreshed, this, &RemoteDeckList_TreeWidget::expandAll); header()->setSectionResizeMode(QHeaderView::ResizeToContents); setUniformRowHeights(true); diff --git a/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp b/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp index 1f76a1f8b..953d1e6af 100644 --- a/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp +++ b/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp @@ -253,8 +253,7 @@ void RemoteReplayList_TreeModel::clearAll() void RemoteReplayList_TreeModel::refreshTree() { PendingCommand *pend = client->prepareSessionCommand(Command_ReplayList()); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(replayListFinished(const Response &))); + connect(pend, &PendingCommand::finished, this, &RemoteReplayList_TreeModel::replayListFinished); client->sendCommand(pend); } diff --git a/cockatrice/src/server/user/user_context_menu.cpp b/cockatrice/src/server/user/user_context_menu.cpp index 10955a160..a33b31632 100644 --- a/cockatrice/src/server/user/user_context_menu.cpp +++ b/cockatrice/src/server/user/user_context_menu.cpp @@ -116,7 +116,7 @@ void UserContextMenu::banUser_processUserInfoResponse(const Response &r) // The dialog needs to be non-modal in order to not block the event queue of the client. BanDialog *dlg = new BanDialog(response.user_info(), static_cast(parent())); - connect(dlg, SIGNAL(accepted()), this, SLOT(banUser_dialogFinished())); + connect(dlg, &QDialog::accepted, this, &UserContextMenu::banUser_dialogFinished); dlg->show(); } @@ -129,7 +129,7 @@ void UserContextMenu::warnUser_processGetWarningsListResponse(const Response &r) // The dialog needs to be non-modal in order to not block the event queue of the client. WarningDialog *dlg = new WarningDialog(user, clientid, static_cast(parent())); - connect(dlg, SIGNAL(accepted()), this, SLOT(warnUser_dialogFinished())); + connect(dlg, &QDialog::accepted, this, &UserContextMenu::warnUser_dialogFinished); if (response.warning_size() > 0) { for (int i = 0; i < response.warning_size(); ++i) { @@ -148,8 +148,7 @@ void UserContextMenu::warnUser_processUserInfoResponse(const Response &resp) cmd.set_user_name(userInfo.name()); cmd.set_user_clientid(userInfo.clientid()); PendingCommand *pend = client->prepareModeratorCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(warnUser_processGetWarningsListResponse(Response))); + connect(pend, &PendingCommand::finished, this, &UserContextMenu::warnUser_processGetWarningsListResponse); client->sendCommand(pend); } @@ -433,8 +432,7 @@ void UserContextMenu::showContextMenu(const QPoint &pos, cmd.set_user_name(userName.toStdString()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(gamesOfUserReceived(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &UserContextMenu::gamesOfUserReceived); client->sendCommand(pend); } else if (actionClicked == aAddToBuddyList) { @@ -471,8 +469,7 @@ void UserContextMenu::showContextMenu(const QPoint &pos, cmd.set_user_name(userName.toStdString()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(banUser_processUserInfoResponse(Response))); + connect(pend, &PendingCommand::finished, this, &UserContextMenu::banUser_processUserInfoResponse); client->sendCommand(pend); } else if (actionClicked == aPromoteToMod || actionClicked == aDemoteFromMod) { Command_AdjustMod cmd; @@ -480,8 +477,7 @@ void UserContextMenu::showContextMenu(const QPoint &pos, cmd.set_should_be_mod(actionClicked == aPromoteToMod); PendingCommand *pend = client->prepareAdminCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(adjustMod_processUserResponse(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &UserContextMenu::adjustMod_processUserResponse); client->sendCommand(pend); } else if (actionClicked == aPromoteToJudge || actionClicked == aDemoteFromJudge) { Command_AdjustMod cmd; @@ -489,36 +485,31 @@ void UserContextMenu::showContextMenu(const QPoint &pos, cmd.set_should_be_judge(actionClicked == aPromoteToJudge); PendingCommand *pend = client->prepareAdminCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(adjustMod_processUserResponse(Response, CommandContainer))); + connect(pend, &PendingCommand::finished, this, &UserContextMenu::adjustMod_processUserResponse); client->sendCommand(pend); } else if (actionClicked == aBanHistory) { Command_GetBanHistory cmd; cmd.set_user_name(userName.toStdString()); PendingCommand *pend = client->prepareModeratorCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(banUserHistory_processResponse(Response))); + connect(pend, &PendingCommand::finished, this, &UserContextMenu::banUserHistory_processResponse); client->sendCommand(pend); } else if (actionClicked == aWarnUser) { Command_GetUserInfo cmd; cmd.set_user_name(userName.toStdString()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(warnUser_processUserInfoResponse(Response))); + connect(pend, &PendingCommand::finished, this, &UserContextMenu::warnUser_processUserInfoResponse); client->sendCommand(pend); } else if (actionClicked == aWarnHistory) { Command_GetWarnHistory cmd; cmd.set_user_name(userName.toStdString()); PendingCommand *pend = client->prepareModeratorCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(warnUserHistory_processResponse(Response))); + connect(pend, &PendingCommand::finished, this, &UserContextMenu::warnUserHistory_processResponse); client->sendCommand(pend); } else if (actionClicked == aGetAdminNotes) { Command_GetAdminNotes cmd; cmd.set_user_name(userName.toStdString()); auto *pend = client->prepareModeratorCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(getAdminNotes_processResponse(Response))); + connect(pend, &PendingCommand::finished, this, &UserContextMenu::getAdminNotes_processResponse); client->sendCommand(pend); } else if (actionClicked == aCopyToClipBoard) { diff --git a/cockatrice/src/server/user/user_info_box.cpp b/cockatrice/src/server/user/user_info_box.cpp index 3f2381011..39babbf50 100644 --- a/cockatrice/src/server/user/user_info_box.cpp +++ b/cockatrice/src/server/user/user_info_box.cpp @@ -63,9 +63,9 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *paren buttonsLayout->addWidget(&avatarButton); mainLayout->addLayout(buttonsLayout, 7, 0, 1, 3); - connect(&editButton, SIGNAL(clicked()), this, SLOT(actEdit())); - connect(&passwordButton, SIGNAL(clicked()), this, SLOT(actPassword())); - connect(&avatarButton, SIGNAL(clicked()), this, SLOT(actAvatar())); + connect(&editButton, &QPushButton::clicked, this, &UserInfoBox::actEdit); + connect(&passwordButton, &QPushButton::clicked, this, &UserInfoBox::actPassword); + connect(&avatarButton, &QPushButton::clicked, this, &UserInfoBox::actAvatar); } setWindowTitle(tr("User Information")); @@ -185,8 +185,7 @@ void UserInfoBox::updateInfo(const QString &userName) cmd.set_user_name(userName.toStdString()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(processResponse(const Response &))); + connect(pend, &PendingCommand::finished, this, &UserInfoBox::processResponse); client->sendCommand(pend); } @@ -204,8 +203,7 @@ void UserInfoBox::actEdit() Command_GetUserInfo cmd; PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(actEditInternal(const Response &))); + connect(pend, &PendingCommand::finished, this, &UserInfoBox::actEditInternal); client->sendCommand(pend); } @@ -244,8 +242,7 @@ void UserInfoBox::actEditInternal(const Response &r) cmd.set_country(dlg.getCountry().toStdString()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(processEditResponse(const Response &))); + connect(pend, &PendingCommand::finished, this, &UserInfoBox::processEditResponse); client->sendCommand(pend); } @@ -291,8 +288,7 @@ void UserInfoBox::changePassword(const QString &oldPassword, const QString &newP } PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(processPasswordResponse(const Response &))); + connect(pend, &PendingCommand::finished, this, &UserInfoBox::processPasswordResponse); client->sendCommand(pend); } @@ -307,8 +303,7 @@ void UserInfoBox::actAvatar() cmd.set_image(dlg.getImage().data(), dlg.getImage().size()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(processAvatarResponse(const Response &))); + connect(pend, &PendingCommand::finished, this, &UserInfoBox::processAvatarResponse); client->sendCommand(pend); } diff --git a/cockatrice/src/server/user/user_list_widget.cpp b/cockatrice/src/server/user/user_list_widget.cpp index 988257a9d..b1bc4dab7 100644 --- a/cockatrice/src/server/user/user_list_widget.cpp +++ b/cockatrice/src/server/user/user_list_widget.cpp @@ -63,7 +63,7 @@ BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(par permanentRadio = new QRadioButton(tr("&permanent ban")); temporaryRadio = new QRadioButton(tr("&temporary ban")); temporaryRadio->setChecked(true); - connect(temporaryRadio, SIGNAL(toggled(bool)), this, SLOT(enableTemporaryEdits(bool))); + connect(temporaryRadio, &QRadioButton::toggled, this, &BanDialog::enableTemporaryEdits); daysLabel = new QLabel(tr("&Days:")); daysEdit = new QSpinBox; daysEdit->setMinimum(0); @@ -106,9 +106,9 @@ BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(par QPushButton *okButton = new QPushButton(tr("&OK")); okButton->setAutoDefault(true); - connect(okButton, SIGNAL(clicked()), this, SLOT(okClicked())); + connect(okButton, &QPushButton::clicked, this, &BanDialog::okClicked); QPushButton *cancelButton = new QPushButton(tr("&Cancel")); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + connect(cancelButton, &QPushButton::clicked, this, &BanDialog::reject); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); @@ -144,9 +144,9 @@ WarningDialog::WarningDialog(const QString userName, const QString clientID, QWi QPushButton *okButton = new QPushButton(tr("&OK")); okButton->setAutoDefault(true); - connect(okButton, SIGNAL(clicked()), this, SLOT(okClicked())); + connect(okButton, &QPushButton::clicked, this, &WarningDialog::okClicked); QPushButton *cancelButton = new QPushButton(tr("&Cancel")); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + connect(cancelButton, &QPushButton::clicked, this, &WarningDialog::reject); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); @@ -423,7 +423,7 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, { itemDelegate = new UserListItemDelegate(this); userContextMenu = new UserContextMenu(tabSupervisor, this); - connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); + connect(userContextMenu, &UserContextMenu::openMessageDialog, this, &UserListWidget::openMessageDialog); userTree = new QTreeWidget; userTree->setColumnCount(3); @@ -434,7 +434,7 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, userTree->setIconSize(QSize(20, 18)); userTree->setItemDelegate(itemDelegate); userTree->setAlternatingRowColors(true); - connect(userTree, SIGNAL(itemActivated(QTreeWidgetItem *, int)), this, SLOT(userClicked(QTreeWidgetItem *, int))); + connect(userTree, &QTreeWidget::itemActivated, this, &UserListWidget::userClicked); QVBoxLayout *vbox = new QVBoxLayout; vbox->addWidget(userTree); diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index d7a9dac75..096ab23b8 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -280,6 +280,8 @@ SettingsCache::SettingsCache() settings->value("interface/visualdeckstoragepromptforconversion", true).toBool(); visualDeckStorageAlwaysConvert = settings->value("interface/visualdeckstoragealwaysconvert", false).toBool(); visualDeckStorageInGame = settings->value("interface/visualdeckstorageingame", true).toBool(); + visualDeckStorageSelectionAnimation = + settings->value("interface/visualdeckstorageselectionanimation", true).toBool(); horizontalHand = settings->value("hand/horizontal", true).toBool(); invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt(); @@ -451,6 +453,12 @@ void SettingsCache::setDeckPath(const QString &_deckPath) settings->setValue("paths/decks", deckPath); } +void SettingsCache::setFiltersPath(const QString &_filtersPath) +{ + filtersPath = _filtersPath; + settings->setValue("paths/filters", filtersPath); +} + void SettingsCache::setReplaysPath(const QString &_replaysPath) { replaysPath = _replaysPath; @@ -736,13 +744,13 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visual emit visualDeckStorageUnusedColorIdentitiesOpacityChanged(visualDeckStorageUnusedColorIdentitiesOpacity); } -void SettingsCache::setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion) +void SettingsCache::setVisualDeckStoragePromptForConversion(bool _visualDeckStoragePromptForConversion) { visualDeckStoragePromptForConversion = _visualDeckStoragePromptForConversion; settings->setValue("interface/visualdeckstoragepromptforconversion", visualDeckStoragePromptForConversion); } -void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T _visualDeckStorageAlwaysConvert) +void SettingsCache::setVisualDeckStorageAlwaysConvert(bool _visualDeckStorageAlwaysConvert) { visualDeckStorageAlwaysConvert = _visualDeckStorageAlwaysConvert; settings->setValue("interface/visualdeckstoragealwaysconvert", visualDeckStorageAlwaysConvert); @@ -755,6 +763,13 @@ void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T value) emit visualDeckStorageInGameChanged(visualDeckStorageInGame); } +void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T value) +{ + visualDeckStorageSelectionAnimation = value; + settings->setValue("interface/visualdeckstorageselectionanimation", visualDeckStorageSelectionAnimation); + emit visualDeckStorageSelectionAnimationChanged(visualDeckStorageSelectionAnimation); +} + void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand) { horizontalHand = static_cast(_horizontalHand); @@ -1312,6 +1327,7 @@ void SettingsCache::loadPaths() { QString dataPath = getDataPath(); deckPath = getSafeConfigPath("paths/decks", dataPath + "/decks/"); + filtersPath = getSafeConfigPath("paths/filters", dataPath + "/filters/"); replaysPath = getSafeConfigPath("paths/replays", dataPath + "/replays/"); themesPath = getSafeConfigPath("paths/themes", dataPath + "/themes/"); picsPath = getSafeConfigPath("paths/pics", dataPath + "/pics/"); diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index 4440ca0ac..82625d9f7 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -68,6 +68,7 @@ signals: void visualDeckStorageDrawUnusedColorIdentitiesChanged(bool _visible); void visualDeckStorageUnusedColorIdentitiesOpacityChanged(bool value); void visualDeckStorageInGameChanged(bool enabled); + void visualDeckStorageSelectionAnimationChanged(bool enabled); void horizontalHandChanged(); void handJustificationChanged(); void invertVerticalCoordinateChanged(); @@ -103,7 +104,7 @@ private: QByteArray tokenDialogGeometry; QByteArray setsDialogGeometry; QString lang; - QString deckPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath, + QString deckPath, filtersPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath, customCardDatabasePath, themesPath, spoilerDatabasePath, tokenDatabasePath, themeName; bool tabVisualDeckStorageOpen, tabServerOpen, tabAccountOpen, tabDeckStorageOpen, tabReplaysOpen, tabAdminOpen, tabLogOpen; @@ -145,6 +146,7 @@ private: bool visualDeckStoragePromptForConversion; bool visualDeckStorageAlwaysConvert; bool visualDeckStorageInGame; + bool visualDeckStorageSelectionAnimation; bool horizontalHand; bool invertVerticalCoordinate; int minPlayersForMultiColumnLayout; @@ -233,6 +235,10 @@ public: { return deckPath; } + QString getFiltersPath() const + { + return filtersPath; + } QString getReplaysPath() const { return replaysPath; @@ -462,6 +468,10 @@ public: { return visualDeckStorageInGame; } + bool getVisualDeckStorageSelectionAnimation() const + { + return visualDeckStorageSelectionAnimation; + } bool getHorizontalHand() const { return horizontalHand; @@ -754,6 +764,7 @@ public slots: void setShowTipsOnStartup(bool _showTipsOnStartup); void setSeenTips(const QList &_seenTips); void setDeckPath(const QString &_deckPath); + void setFiltersPath(const QString &_filtersPath); void setReplaysPath(const QString &_replaysPath); void setThemesPath(const QString &_themesPath); void setCustomCardDatabasePath(const QString &_customCardDatabasePath); @@ -798,9 +809,10 @@ public slots: void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize); void setVisualDeckStorageDrawUnusedColorIdentities(QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities); void setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity); - void setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion); - void setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T _visualDeckStorageAlwaysConvert); + void setVisualDeckStoragePromptForConversion(bool _visualDeckStoragePromptForConversion); + void setVisualDeckStorageAlwaysConvert(bool _visualDeckStorageAlwaysConvert); void setVisualDeckStorageInGame(QT_STATE_CHANGED_T value); + void setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T value); void setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand); void setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate); void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); diff --git a/cockatrice/src/utility/card_info_comparator.cpp b/cockatrice/src/utility/card_info_comparator.cpp new file mode 100644 index 000000000..26d00420f --- /dev/null +++ b/cockatrice/src/utility/card_info_comparator.cpp @@ -0,0 +1,75 @@ +#include "card_info_comparator.h" + +CardInfoComparator::CardInfoComparator(const QStringList &properties, Qt::SortOrder order) + : m_properties(properties), m_order(order) +{ +} + +bool CardInfoComparator::operator()(const CardInfoPtr &a, const CardInfoPtr &b) const +{ + // Iterate over each property in the list + for (const QString &property : m_properties) { + QVariant valueA = getProperty(a, property); + QVariant valueB = getProperty(b, property); + + // Compare the current property + if (valueA != valueB) { + // If values differ, perform comparison + return compareVariants(valueA, valueB) ? (m_order == Qt::AscendingOrder) : (m_order == Qt::DescendingOrder); + } + } + + // If all properties are equal, return false (indicating they are considered equal for sorting purposes) + return false; +} + +bool CardInfoComparator::compareVariants(const QVariant &a, const QVariant &b) const +{ + // Determine the type of QVariant based on Qt version +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (a.typeId() != b.typeId()) { +#else + if (a.type() != b.type()) { +#endif + // If they are not the same type, compare as strings + return a.toString() < b.toString(); + } + + // Perform type-specific comparison +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + switch (a.typeId()) { +#else + switch (a.type()) { +#endif + case QMetaType::Int: + return a.toInt() < b.toInt(); + case QMetaType::Double: + return a.toDouble() < b.toDouble(); + case QMetaType::QString: + return a.toString() < b.toString(); + case QMetaType::Bool: + return a.toBool() < b.toBool(); + default: + // Default to comparing as strings + return a.toString() < b.toString(); + } +} + +QVariant CardInfoComparator::getProperty(const CardInfoPtr &card, const QString &property) const +{ + // Check if the property exists in the main fields of the class + if (property == "name") { + return card->getName(); + } else if (property == "text") { + return card->getText(); + } else if (property == "isToken") { + return card->getIsToken(); + } + + // Otherwise, check if it's a custom property in the QVariantHash + if (card->hasProperty(property)) { + return card->getProperty(property); + } + + return QVariant(); // Return an invalid variant if the property does not exist +} diff --git a/cockatrice/src/utility/card_info_comparator.h b/cockatrice/src/utility/card_info_comparator.h new file mode 100644 index 000000000..9402ed401 --- /dev/null +++ b/cockatrice/src/utility/card_info_comparator.h @@ -0,0 +1,24 @@ +#ifndef CARD_INFO_COMPARATOR_H +#define CARD_INFO_COMPARATOR_H + +#include "../game/cards/card_info.h" + +#include +#include +#include + +class CardInfoComparator +{ +public: + explicit CardInfoComparator(const QStringList &properties, Qt::SortOrder order = Qt::AscendingOrder); + bool operator()(const CardInfoPtr &a, const CardInfoPtr &b) const; + +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; +}; + +#endif // CARD_INFO_COMPARATOR_H diff --git a/cockatrice/src/utility/card_set_comparator.h b/cockatrice/src/utility/card_set_comparator.h index 107b618fb..ed4b3d5e1 100644 --- a/cockatrice/src/utility/card_set_comparator.h +++ b/cockatrice/src/utility/card_set_comparator.h @@ -1,7 +1,7 @@ #ifndef SET_PRIORITY_COMPARATOR_H #define SET_PRIORITY_COMPARATOR_H -#include "../game/cards/card_database.h" +#include "../game/cards/card_info.h" class SetPriorityComparator { diff --git a/cockatrice/src/utility/levenshtein.cpp b/cockatrice/src/utility/levenshtein.cpp new file mode 100644 index 000000000..cfb972f91 --- /dev/null +++ b/cockatrice/src/utility/levenshtein.cpp @@ -0,0 +1,25 @@ +#include "levenshtein.h" + +#include +#include + +int levenshteinDistance(const QString &s1, const QString &s2) +{ + int len1 = s1.size(); + int len2 = s2.size(); + std::vector> dp(len1 + 1, std::vector(len2 + 1)); + + for (int i = 0; i <= len1; i++) + dp[i][0] = i; + for (int j = 0; j <= len2; j++) + dp[0][j] = j; + + for (int i = 1; i <= len1; i++) { + for (int j = 1; j <= len2; j++) { + int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1; + dp[i][j] = std::min({dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost}); + } + } + + return dp[len1][len2]; +} diff --git a/cockatrice/src/utility/levenshtein.h b/cockatrice/src/utility/levenshtein.h new file mode 100644 index 000000000..b9b05a13c --- /dev/null +++ b/cockatrice/src/utility/levenshtein.h @@ -0,0 +1,8 @@ +#ifndef LEVENSHTEIN_H +#define LEVENSHTEIN_H + +#include + +int levenshteinDistance(const QString &s1, const QString &s2); + +#endif // LEVENSHTEIN_H diff --git a/cockatrice/translations/cockatrice_cs.ts b/cockatrice/translations/cockatrice_cs.ts index f020d125b..ec56917c9 100644 --- a/cockatrice/translations/cockatrice_cs.ts +++ b/cockatrice/translations/cockatrice_cs.ts @@ -542,17 +542,17 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_de.ts b/cockatrice/translations/cockatrice_de.ts index a903e177c..ee5c902c1 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -25,7 +25,7 @@ &Refresh - + &aktualisieren @@ -38,18 +38,19 @@ Open in new tab - + In neuem Reiter öffnen Are you sure? - + Sind Sie sicher? The decklist has been modified. Do you want to save the changes? - + Die Deckliste wurde bearbeitet. +Änderungen speichern? @@ -60,44 +61,45 @@ Do you want to save the changes? Error - + Fehler Could not open deck at %1 - + Deck an Stelle %1 konnte nicht geöffnet werden Could not save remote deck - + Remote-Deck konnte nicht gespeichert werden The deck could not be saved. Please check that the directory is writable and try again. - + Das Deck konnte nicht abgespeichert werden. +Bitte überprüfen Sie ob das Verzeichnis bearbeitet werden kann und versuchen Sie es erneut. Save deck - + Deck speichern The deck could not be saved. - + Das Deck konnte nicht abgespeichert werden. There are no cards in your deck to be exported - + Es gibt keine Karten in Ihrem Deck zum Exportieren No deck was selected to be exported. - + Es wurde kein Deck zum Exportieren ausgewählt. @@ -196,7 +198,7 @@ Please check that the directory is writable and try again. Use rounded card corners - + Abgerundete Karten Ecken verwenden @@ -542,17 +544,17 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Zugehörige Karten ansehen - + Add card to deck Karte zum Deck hinzufügen - + Mainboard Mainboard - + Sideboard Sideboard @@ -724,7 +726,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Card Info - + Karteninformation @@ -737,12 +739,12 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Add to Deck - + Zum Deck hinzufügen Add to Sideboard - + Zum Sideboard hinzufügen @@ -752,27 +754,27 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Show on EDHREC (Commander) - + Auf EDHREC anzeigen (Commander) Show on EDHREC (Card) - + Auf EDHREC anzeigen (Karte) Show Related cards - + Zeige ähnliche Karten Add card to &maindeck - + Karte zum &Hauptdeck hinzufügen Add card to &sideboard - + Karte zum &Sideboard hinzufügen @@ -780,7 +782,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Banner Card - + Bannerkarte @@ -790,7 +792,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Deck - + Deck @@ -800,12 +802,12 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic &Comments: - + &Kommentare: Hash: - + Hash: @@ -820,7 +822,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic &Remove row - + Reihe &entfernen @@ -833,7 +835,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Filters - + Filter @@ -843,7 +845,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Delete selected - + Auswahl löschen @@ -1169,7 +1171,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic All files (*.*) - + Alle Dateien (*.*) diff --git a/cockatrice/translations/cockatrice_el.ts b/cockatrice/translations/cockatrice_el.ts index 3f658051d..9a745aaa6 100644 --- a/cockatrice/translations/cockatrice_el.ts +++ b/cockatrice/translations/cockatrice_el.ts @@ -542,17 +542,17 @@ This is only saved for moderators and cannot be seen by the banned person. - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_en@pirate.ts b/cockatrice/translations/cockatrice_en@pirate.ts index 370271c77..d64dbe67d 100644 --- a/cockatrice/translations/cockatrice_en@pirate.ts +++ b/cockatrice/translations/cockatrice_en@pirate.ts @@ -542,17 +542,17 @@ This be only saved for captains and cannot be seen by t' brutally banned sc - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_en_US.ts b/cockatrice/translations/cockatrice_en_US.ts index 0723a8130..b27e2d8c9 100644 --- a/cockatrice/translations/cockatrice_en_US.ts +++ b/cockatrice/translations/cockatrice_en_US.ts @@ -542,17 +542,17 @@ This is only saved for moderators and cannot be seen by the banned person.View related cards - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_es.ts b/cockatrice/translations/cockatrice_es.ts index f2f206b4d..523095235 100644 --- a/cockatrice/translations/cockatrice_es.ts +++ b/cockatrice/translations/cockatrice_es.ts @@ -542,17 +542,17 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_et.ts b/cockatrice/translations/cockatrice_et.ts index c4984fcce..f3e31cd31 100644 --- a/cockatrice/translations/cockatrice_et.ts +++ b/cockatrice/translations/cockatrice_et.ts @@ -542,17 +542,17 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_fi.ts b/cockatrice/translations/cockatrice_fi.ts index a4afdfe6a..fc5e1f37f 100644 --- a/cockatrice/translations/cockatrice_fi.ts +++ b/cockatrice/translations/cockatrice_fi.ts @@ -542,17 +542,17 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_fr.ts b/cockatrice/translations/cockatrice_fr.ts index faa2744a1..34774eb64 100644 --- a/cockatrice/translations/cockatrice_fr.ts +++ b/cockatrice/translations/cockatrice_fr.ts @@ -542,17 +542,17 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index 928c46439..f310b5842 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -4,7 +4,7 @@ &Set counter... - Imposta segnalino + Imposta &contatore... @@ -12,12 +12,12 @@ Set counter - Imposta segnalino + Imposta contatore New value for counter '%1': - Nuovo valore per il segnalino '%1': + Nuovo valore per il contatore '%1': @@ -25,12 +25,12 @@ &Refresh - &Aggiorna + &Ricarica Parse Set Name and Number (if available) - Acquisisci nome set e numero (se disponibili) + Acquisisci set e numero (se disponibili) @@ -125,7 +125,7 @@ Controlla se la cartella è valida e prova ancora. Sideboard - Panchina + Sideboard @@ -158,12 +158,12 @@ Controlla se la cartella è valida e prova ancora. Menu settings - Menù impostazioni. + Impostazioni menù Show keyboard shortcuts in right-click menus - Mostra scorciatoie da tastiera nel menù del tasto destro del mouse. + Mostra scorciatoie da tastiera nel menù del tasto destro del mouse @@ -178,17 +178,17 @@ Controlla se la cartella è valida e prova ancora. Auto-Rotate cards with sideways layout - Ruota automaticamente carte con disposizione orizzontale. + Ruota automaticamente carte con disposizione orizzontale Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] - Sovrascrivi tutte le arti delle carte con set di preferenza personale (Comportamento di modifica dell'ID pre-provider) [Richiede il riavvio del Client] + Sovrascrivi tutte le immagini delle carte con set personale (Comportamento pre-modifica Provider ID) [richiede riavvio] Bump sets that the deck contains cards from to the top in the printing selector - Imposta le carte che il mazzo contiene dall'alto verso il basso nel selettore di stampa + Nel selettore di stampa, mostra per primi i set delle carte che il mazzo contiene @@ -198,28 +198,28 @@ Controlla se la cartella è valida e prova ancora. Use rounded card corners - + Usa i bordi delle carte arrotondati Minimum overlap percentage of cards on the stack and in vertical hand - Percentuale minima di sovrapposizione delle carte in pila e in mano verticale + Sovrapposizione % minima delle carte in pila e nella mano verticale Maximum initial height for card view window: - Altezza iniziale massima per la finestra di visualizzazione della scheda: + Altezza iniziale massima per la finestra di visualizzazione delle carte: rows - file + file Maximum expanded height for card view window: - Altezza massima per la finestra di visualizzazione della carta: + Altezza massima per la finestra di visualizzazione delle carte: @@ -272,7 +272,7 @@ Controlla se la cartella è valida e prova ancora. ban client I&D - banna client I&D + Banna client I&D @@ -432,25 +432,25 @@ Questa è visibile solo ai moderatori e non alla persona bannata. AND Logical conjunction operator used in card filter - E + AND OR Logical disjunction operator used in card filter - O + OR AND NOT Negated logical conjunction operator used in card filter - E NON + AND NOT OR NOT Negated logical disjunction operator used in card filter - O NON + OR NOT @@ -485,7 +485,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Mana Value - Valore di Mana + Valore di mana @@ -533,7 +533,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. View transformation - Mostra trasformazione + Visualizza trasformazione @@ -541,20 +541,20 @@ Questa è visibile solo ai moderatori e non alla persona bannata. View related cards - Mostra carte correlate + Guarda carte correlate - + Add card to deck - Aggiungi carta al &mazzo + Aggiungi la carta al &mazzo - + Mainboard Mazzo - + Sideboard Sideboard @@ -749,7 +749,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Select Printing - Seleziona Stampa + Seleziona stampa @@ -1025,12 +1025,12 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Redirect Cache TTL: - Reindirizza TTL della cache: + TTL cache dei reindirizzamenti: How long cached redirects for urls are valid for. - Per quanto tempo sono validi i reindirizzamenti memorizzati nella cache per gli URL. + Per quanto tempo sono validi i reindirizzamenti per gli URL memorizzati nella cache. @@ -1085,7 +1085,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Last Change - Ultimo cambiamento + Ultima modifica @@ -1105,7 +1105,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Download card pictures on the fly - Download immagini delle carte + Scarica immagini delle carte in tempo reale @@ -1138,7 +1138,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Count - Conteggio + Quantità @@ -1148,7 +1148,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Number - Quantità + Numero @@ -1192,7 +1192,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Edit tags ... - Modifica etichette... + Imposta etichette... @@ -1210,7 +1210,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Add a new tag (e.g., Aggro️) - Aggiungi una nuova etichetta (Esempio: Aggro️) + Aggiungi una nuova etichetta (Es.: Aggro️) @@ -1378,7 +1378,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Unload deck Unload deck... - + Deseleziona mazzo @@ -1662,12 +1662,12 @@ Vuoi convertire il mazzo al formato .cod? Starting life total: - Totale vita iniziale: + Punti vita iniziali: Game setup options - Opzioni di configurazione del gioco + Configurazione partita @@ -2491,7 +2491,7 @@ La tua email verrà utilizzata per verificare il tuo account. Number of sides: - Numero di lati: + Numero di facce: @@ -2643,7 +2643,7 @@ Desideri modificare l'impostazione della posizione del database? Shortcuts - Combinazioni di tasti + Scorciatoie @@ -2922,12 +2922,12 @@ You may have to build from source yourself. Join Game - + Entra nella partita Spectate Game - + Osserva partita @@ -2947,7 +2947,7 @@ You may have to build from source yourself. Please join the respective room first. - Si prega di entrare nella rispettiva stanza prima. + Si prega di entrare prima nella rispettiva stanza. @@ -3091,12 +3091,12 @@ You may have to build from source yourself. Reset all paths - Reimposta tutti gli indirizzi di download + Reimposta tutti i percorsi All paths have been reset - Gli indirizzi di download sono stati resettati + I percorsi sono stati resettati @@ -3156,7 +3156,7 @@ You may have to build from source yourself. Custom database directory: - Cartella database personalizzata. + Cartella database personalizzata: @@ -3688,7 +3688,7 @@ La tua versione è la %1, la versione online è la %2. &Restore password... - Recupera Password... + &Recupera password... @@ -3713,7 +3713,7 @@ La tua versione è la %1, la versione online è la %2. C&ard Database - Database delle c&arte + &Database delle carte @@ -3748,7 +3748,7 @@ La tua versione è la %1, la versione online è la %2. Tabs - Schede + &Schede @@ -3773,12 +3773,12 @@ La tua versione è la %1, la versione online è la %2. Check for Card Updates... - Cerca Aggiornamenti Carte... + Controlla aggiornamenti carte... View &Debug Log - Vedi Log %Debug + Vedi Registro di &debug @@ -3901,17 +3901,17 @@ Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti n Failed to start. The file might be missing, or permissions might be incorrect. - + Avvio non riuscito. Il file potrebbe essere assente, o i permessi errati. The process crashed some time after starting successfully. - + Il processo si è arrestato in modo anomalo poco dopo essersi avviato con successo. Timed out. The process took too long to respond. The last waitFor...() function timed out. - + Richiesta scaduta. Il processo ci ha messo troppo a rispondere. L'ultima funzione waitFor...() è scaduta. @@ -3921,18 +3921,19 @@ Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti n An error occurred when attempting to read from the process. For example, the process may not be running. - + Si è verificato un errore nel cercare di leggere dal processo. Ad esempio, il processo potrebbe non essere in esecuzione. Unknown error occurred. - + Si è verificato un errore sconosciuto. The card database updater exited with an error: %1 - + L'aggiornamento del database delle carte è terminato con un errore: +%1 @@ -4199,7 +4200,7 @@ Il database delle carte verrà ricaricato. %1 puts %2 into play%3 face down. - %1 mette %2 in gioco %3 a faccia in giù. + %1 mette %2 sul campo di battaglia %3 a faccia in giù. @@ -4229,7 +4230,7 @@ Il database delle carte verrà ricaricato. %1 puts %2%3 onto the bottom of their library. - %1 mette %2%3 in fondo alla propria libreria. + %1 mette %2%3 in fondo al proprio grimorio. @@ -4271,7 +4272,7 @@ Il database delle carte verrà ricaricato. %1 is looking at the %4 %3 card(s) %2. %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1 sta guardando una carta %4 %2.%1 sta guardando %3 carte %4 %2.%1 sta guardando %3 carte %4 %2. + %1 sta guardando %3 carta %4 %2.%1 sta guardando %3 carte %4 %2.%1 sta guardando %3 carte %4 %2. @@ -4393,12 +4394,12 @@ Il database delle carte verrà ricaricato. %1 peeks at face down card #%2. - %1 ha sbirciato la carta a faccia in giù #%2. + %1 sbircia la carta a faccia in giù #%2. %1 peeks at face down card #%2: %3. - %1 ha sbirciato la carta a faccia in giù #%2: %3. + %1 sbircia la carta a faccia in giù #%2: %3. @@ -4448,8 +4449,7 @@ Il database delle carte verrà ricaricato. %1 flips %2 coins. There are %3 heads and %4 tails. - %1 lancia %2 monete. Sono uscite %3 teste e %4 croci. -  + %1 lancia %2 monete. Sono uscite %3 teste e %4 croci. @@ -4564,7 +4564,7 @@ Il database delle carte verrà ricaricato. %1 shuffles the top %3 cards of %2. - %1 mescola le %3 carte in cima da %2. + %1 mescola le prime %3 carte da %2. @@ -4703,7 +4703,7 @@ Il database delle carte verrà ricaricato. Card Type - Tipo di carta + Tipo @@ -4779,7 +4779,7 @@ Il database delle carte verrà ricaricato. Untap - Stappa + STAP @@ -4789,7 +4789,7 @@ Il database delle carte verrà ricaricato. Draw - Pesca + Acquisizione @@ -4829,7 +4829,7 @@ Il database delle carte verrà ricaricato. End/Cleanup - Fine/Cancellazione + Finale / Cancellazione @@ -4872,7 +4872,7 @@ Il database delle carte verrà ricaricato. Combat damage step - Sottofase di danno da combattimento + Sottofase del danno da combattimento @@ -4963,14 +4963,14 @@ Il database delle carte verrà ricaricato. &Top of library - Cima al grimorio + &Cima al grimorio &Bottom of library - Fondo al grimorio + &Fondo al grimorio @@ -5003,7 +5003,7 @@ Il database delle carte verrà ricaricato. View &top cards of library... - Guarda &le prime carte del grimorio... + Guarda &le carte in cima al grimorio... @@ -5078,7 +5078,7 @@ Il database delle carte verrà ricaricato. Lend library to... - Presta la libreria a... + Presta il grimorio a... @@ -5094,12 +5094,12 @@ Il database delle carte verrà ricaricato. Shuffle top cards... - + Mescola le carte in cima... Shuffle bottom cards... - + Mescola le carte in fondo... @@ -5272,12 +5272,12 @@ Il database delle carte verrà ricaricato. Increase power and decrease toughness - Incrementa forza e decrementa costituzione + Aumenta forza e diminuisci costituzione Decrease power and increase toughness - Decrementa forza e incrementa costituzione + Diminuisci forza e aumenta costituzione @@ -5337,12 +5337,12 @@ Il database delle carte verrà ricaricato. &Bottom of library in random order - In fondo al grimorio in ordine casuale + In &fondo al grimorio in ordine casuale View top cards of library - Guarda le prima carte del grimorio + Guarda le carte in cima al grimorio @@ -5367,7 +5367,7 @@ Il database delle carte verrà ricaricato. (max. %1) - (max. %1) + (max %1) @@ -5408,7 +5408,7 @@ Il database delle carte verrà ricaricato. &Bottom of library... - &In fondo alla libreria... + &In fondo al grimorio... @@ -5488,7 +5488,7 @@ Il database delle carte verrà ricaricato. Move bottom cards to exile - Metti le ultime care in esilio + Metti le ultime carte in esilio @@ -5544,7 +5544,7 @@ Il database delle carte verrà ricaricato. View related cards - Vedi carte collegate + Guarda carte correlate @@ -5562,12 +5562,12 @@ Il database delle carte verrà ricaricato. Display Navigation Buttons - + Visualizza pulsanti di navigazione <b>Warning:</b> You appear to be using custom card art, which has known bugs when also using the printing selector in this version of Cockatrice. - + <b>Attenzione:</b> Stai utilizzando la cartella delle immagini personalizzate, che causa dei bug se usato insieme al selettore di stampa in questa versione di Cockatrice. @@ -5963,7 +5963,7 @@ Il database delle carte verrà ricaricato. Restore all default shortcuts - Ripristina tutte le scorciatoie predefinite + Ripristina scorciatoie predefinite @@ -5998,17 +5998,17 @@ Il database delle carte verrà ricaricato. How to set custom shortcuts - Come impostare scorciatoie predefinite + Come impostare scorciatoie personalizzate Clear all shortcuts - Rimuovi tutte le shortcuts + Elimina tutte le scorciatoie Search by shortcut name - Cerca per nome scorciatoia + Cerca per nome della scorciatoia @@ -6030,14 +6030,14 @@ Il database delle carte verrà ricaricato. Your configuration file contained invalid shortcuts. Please check your shortcut settings! - Il file di configurazione conteneva combinazioni di tasti non valide. + Il file di configurazione conteneva scorciatoie non valide. Controlla le impostazioni! The following shortcuts have been set to default: - Le seguenti combinazioni di tasti sono state reimpostate al valore predefinito: + Le seguenti scorciatoie sono state reimpostate al valore predefinito: @@ -6133,7 +6133,7 @@ Controlla le impostazioni! Last change: - Ultimo cambiamento: + Ultima modifica: @@ -6169,12 +6169,12 @@ Controlla le impostazioni! Add to Buddy List - Aggiungi a Lista Amici + Aggiungi a Lista amici Add to Ignore List - Aggiungi a Lista Ignorati + Aggiungi a Lista ignorati @@ -6503,7 +6503,7 @@ Please enter a name: Could not open deck at %1 - + Impossibile aprire il mazzo a %1 @@ -7494,7 +7494,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Copy hash to clipboard - Copia hash nella clipboard + Copia hash negli appunti @@ -7686,12 +7686,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u &Double-click cards to play them (instead of single-click) - &Doppio click sulle carte per giocarle (disabilitato un solo click) + &Doppio click sulle carte per giocarle (anzichè un solo click) &Clicking plays all selected cards (instead of just the clicked card) - Gioca tutte le carte selezionate + &Cliccare gioca tutte le carte selezionate (anziché solo quella cliccata) @@ -7701,7 +7701,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Close card view window when last card is removed - + Chiudi la finestra di visualizzazione carte quando l'ultima carta viene rimossa @@ -7711,7 +7711,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Use tear-off menus, allowing right click menus to persist on screen - Usa menù-a-strappo, permettendo ai menù del tasto destro del mouse di rimanere sullo schermo + Usa menù a strappo, permettendo ai menù del tasto destro del mouse di rimanere sullo schermo @@ -7726,12 +7726,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Notify in the taskbar for game events while you are spectating - Abilita notifiche anche per i giochi in cui si è solo uno spettatore + Notifica anche per le partite in cui si è solo uno spettatore Notify in the taskbar when users in your buddy list connect - Notifca nella barra delle applicazioni quando degli utenti nella tua lista amici si connetto + Notifca quando utenti nella tua lista amici si connettono @@ -7761,12 +7761,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Always convert if not prompted - + Converti automaticamente i mazzi senza chiedere Use visual deck storage in game lobby - + Usa galleria mazzi nella lobby @@ -7871,17 +7871,17 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Include Folder Names in Search - + Includi nomi delle cartelle nella ricerca Draw unused Color Identities - + Mostra identità di colore inutilizzate Unused Color Identities Opacity - + Opacità identità di colore inutilizzata @@ -8009,7 +8009,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - innanzitutto la cartella CUSTOM (%1), poi le Espansioni Abilitate in questo dialogo (da Cima a Fondo) + innanzitutto la cartella CUSTOM (%1), poi le Espansioni abilitate in questo dialogo (da cima a fondo) @@ -8067,7 +8067,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Ungrouped - Non raggruppato + Non raggruppare @@ -8077,7 +8077,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Group by Mana Value - Raggruppa per valore di mana + Raggruppa per costo @@ -8092,7 +8092,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Sort by Name - Ordina per nome  + Ordina per nome @@ -8102,7 +8102,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Sort by Mana Cost - Ordina per costo di mana + Ordina per costo @@ -8122,7 +8122,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u shuffle when closing - Mescola quando chiudi + Mescola alla chiusura @@ -8167,17 +8167,17 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Game Lobby - Lobby del gioco + Lobby della partita Card Counters - Segnalini Carta + Segnalini della carta Player Counters - Segnalini Giocatore + Contatori del giocatore @@ -8232,12 +8232,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Game Window - Finestra di Gioco + Finestra di gioco Load Deck from Clipboard - Carica mazzo dalla clipboard + Carica mazzo dagli appunti @@ -8253,7 +8253,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Check for Card Updates... - Controlla per aggiornamenti carte... + Controlla Aggiornamenti Carte... @@ -8273,7 +8273,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Full screen - Schermo intero + Schermo Intero @@ -8288,7 +8288,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Start a Local Game... - Inizia una partita in locale... + Inizia una Partita in Locale... @@ -8299,12 +8299,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Analyze Deck (deckstats.net) Analyze Deck - Analizza mazzo (deckstats.net) + Analizza Mazzo (deckstats.net) Analyze Deck (tappedout.net) - Analizza mazzo (tappedout.net) + Analizza Mazzo (tappedout.net) @@ -8354,27 +8354,27 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Load Deck... - Carica mazzo... + Carica Mazzo... Load Deck from Clipboard... - Carica Mazzo da Appunti... + Carica il Mazzo dagli Appunti... Edit Deck in Clipboard, Annotated - Modifica il mazzo negli appunti con annotazioni + Modifica Mazzo negli Appunti con Annotazioni Edit Deck in Clipboard - Modifica mazzo negli appunti + Modifica Mazzo negli Appunti New Deck - Nuovo mazzo + Nuovo Mazzo @@ -8384,7 +8384,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Print Deck... - Stampa mazzo... + Stampa Mazzo... @@ -8400,12 +8400,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Save Deck - Salva mazzo + Salva Mazzo Save Deck as... - Salva mazzo con nome + Salva Mazzo con Nome... @@ -8415,7 +8415,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Save Deck to Clipboard, Annotated (No Set Info) - Salva il mazzo negli appunti con annotazioni (senza info set) + Salva Mazzo negli Appunti con Annotazioni (Senza Info Set) @@ -8425,7 +8425,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Save Deck to Clipboard (No Set Info) - Salva il mazzo negli appunti (senza info set) + Salva Mazzo negli Appunti (Senza Info Set) @@ -8451,152 +8451,152 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Add Green Counter - Aggiungi Segnalino Verde + Aumenta Segnalino Verde Remove Green Counter - Rimuovi Segnalino Verde + Riduci Segnalino Verde Set Green Counters... - Imposta Segnalini Verdi... + Imposta Segnalino Verde... Add Yellow Counter - Aggiungi Segnalino Giallo + Aumenta Segnalino Giallo Remove Yellow Counter - Rimuovi Segnalino Giallo + Riduci Segnalino Giallo Set Yellow Counters... - Imposta Segnalini Gialli + Imposta Segnalino Giallo... Add Red Counter - Aggiungi Segnalino Rosso + Aumenta Segnalino Rosso Remove Red Counter - Rimuovi Segnalino Rosso + Riduci Segnalino Rosso Set Red Counters... - Imposta Segnalini Rossi... + Imposta Contatore Rosso... Add Life Counter - Aggiungi Segnalino Vita + Aumenta Contatore Vita Remove Life Counter - Rimuovi Segnalino Vita + Riduci Contatore Vita Set Life Counters... - Imposta Segnalini Vita... + Imposta Contatore Vita... Add White Counter - Aggiungi Segnalino Bianco + Aumenta Contatore Bianco Remove White Counter - Rimuovi Segnalino Bianco + Riduci Contatore Bianco Set White Counters... - Imposta Segnalini Bianchi + Imposta Contatore Bianco... Add Blue Counter - Aggiungi Contatore Blu + Aumenta Contatore Blu Remove Blue Counter - Rimuovi Segnalino Blu + Riduci Contatore Blu Set Blue Counters... - Imposta Segnalini Blu... + Imposta Contatore Blu... Add Black Counter - Aggiungi Segnalino Nero + Aumenta Contatore Nero Remove Black Counter - Rimuovi Segnalino Nero + Riduci Contatore Nero Set Black Counters... - Imposta Segnalino Nero... + Imposta Contatore Nero... Add Colorless Counter - Aggiungi Segnalino Incolore + Aumenta Contatore Incolore Remove Colorless Counter - Rimuovi Segnalino Incolore + Riduci Contatore Incolore Set Colorless Counters... - Imposta Segnalini Incolore... + Imposta Contatore Incolore... Add Other Counter - Aggiungi Altro Segnalino + Aumenta Contatore Altro Remove Other Counter - Rimuovi Altro Segnalino + Riduci Contatore Altro Set Other Counters... - Imposta Altri Segnalini... + Imposta Contatore Altro... Add Power (+1/+0) - Aggiungi Forza (+1/+0) + Aumenta Forza (+1/+0) Remove Power (-1/-0) - Rimuovi Forza (-1/-0) + Diminuisci Forza (-1/-0) @@ -8606,12 +8606,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Add Toughness (+0/+1) - Aggiungi Costituzione (+0/+1) + Aumenta Costituzione (+0/+1) Remove Toughness (-0/-1) - Rimuovi Costituzione (-0/-1) + Riduci Costituzione (-0/-1) @@ -8621,12 +8621,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Add Power and Toughness (+1/+1) - Aggiungi Forza e Costituzione (+1/+1) + Aumenta Forza e Costituzione (+1/+1) Remove Power and Toughness (-1/-1) - Rimuovi Forza e Costituzione (-1/-1) + Diminuisci Forza e Costituzione (-1/-1) @@ -8701,7 +8701,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Next Phase Action - Nuova Fase + Azione della Fase Successiva @@ -8711,7 +8711,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Hide Card in Reveal Window - Nascondi la carta nella finestra di visualizzazione + Nascondi Carta nella Finestra di Visualizzazione @@ -8721,7 +8721,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Untap All - Stappa Tutto + STAPpa Tutto @@ -8736,7 +8736,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Peek Card - Osserva Carta + Sbircia Carta @@ -8746,12 +8746,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Attach Card... - Attacca Carta... + Assegna Carta... Unattach Card - Stacca Carta + Togli Carta @@ -8776,28 +8776,28 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Set Annotation... - Imposta annotazione... + Imposta Note... Select All Cards in Zone - Seleziona tutte le carte nella zona + Seleziona Tutte le Carte nella Zona Select All Cards in Row - Seleziona tutte le carte nella riga + Seleziona Tutte le Carte nella Riga Select All Cards in Column - Seleziona tutte le carte nella colonna + Seleziona Tutte le Carte nella Colonna Bottom of Library - Fondo del mazzo + Fondo al Grimorio @@ -8825,19 +8825,19 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Top of Library - Vedi Cima della Libreria + Cima al Grimorio Battlefield, Face Down - Campo di battaglia, faccia in giù + Campo di Battaglia, faccia in giù Battlefield - Campo di battaglia + Campo di Battaglia @@ -8852,12 +8852,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Top Cards of Library - Vedi Prime Carte della Libreria + Carte in Cima al Grimorio Bottom Cards of Library - Carte in fondo al grimorio + Carte in Fondo al Grimorio @@ -8885,27 +8885,27 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Stack Until Found - Impila fino a trovare + Impila Fino a Trovare Draw Bottom Card - Pesca la carta in fondo al mazzo + Pesca Carta in Fondo Draw Multiple Cards from Bottom... - Pesca più carte dal fondo del mazzo... + Pesca Multiple Carte in Fondo Draw Arrow... - Disegna freccia... + Disegna Freccia... Remove Local Arrows - Rimuovi frecce locali + Rimuovi Frecce Locali @@ -8925,17 +8925,17 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Shuffle Library - Mischia il Mazzo + Mescola il Grimorio Shuffle Top Cards of Library - Mescola carte in cima al grimorio + Mescola Carte in Cima al Grimorio Shuffle Bottom Cards of Library - Mescola carte in fondo al grimorio + Mescola Carte in Fondo al Grimorio @@ -8950,37 +8950,37 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Draw Multiple Cards... - Pesca più carte... + Pesca più Carte... Undo Draw - Annulla Disegno + Annulla Pescata Always Reveal Top Card - Rivela sempre la carta in cima + Rivela Sempre la Carta in Cima Always Look At Top Card - Guarda sempre la carta in cima + Guarda Sempre la Carta in Cima Rotate View Clockwise - Ruota la vista in senso orario + Ruota Vista in Senso Orario Rotate View Counterclockwise - Ruota la vista in senso antiorario + Ruota Vista in Senso Antiorario Unfocus Text Box - Non focalizzare il Blocco di Testo + Togli Focus dalla Casella di Testo @@ -9000,22 +9000,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Skip Forward - Salta avanti + Salta Avanti Skip Backward - Salta indietro + Salta Indietro Skip Forward by a lot - Salta avanti di molto + Salta Avanti di Molto Skip Backward by a lot - Salta indietro di molto + Salta Indietro di Molto @@ -9025,7 +9025,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Toggle Fast Forward - Attiva/disattiva avanzamento rapido + Attiva/Disattiva Avanzamento Rapido diff --git a/cockatrice/translations/cockatrice_ja.ts b/cockatrice/translations/cockatrice_ja.ts index 0beb8fe29..bbe68ecc3 100644 --- a/cockatrice/translations/cockatrice_ja.ts +++ b/cockatrice/translations/cockatrice_ja.ts @@ -542,17 +542,17 @@ This is only saved for moderators and cannot be seen by the banned person.関連カードを見る - + Add card to deck デッキにカードを追加 - + Mainboard メインボード - + Sideboard サイドボード @@ -8217,38 +8217,38 @@ Please refrain from engaging in this activity or further actions may be taken ag Chat Room - + チャットルーム Game Window - + ゲームウィンドウ Load Deck from Clipboard - + クリップボードからデッキを開く Replays - + リプレイ Tabs - + タブ Check for Card Updates... - + カードデータベース更新... Connect... - + 接続... @@ -8268,43 +8268,43 @@ Please refrain from engaging in this activity or further actions may be taken ag Register... - + ユーザー登録... Settings... - + 設定... Start a Local Game... - + ローカルゲームを開始... Watch Replay... - + リプレイを見る... Analyze Deck (deckstats.net) Analyze Deck - + デッキを分析 (deckstats.net) Analyze Deck (tappedout.net) - + デッキを分析 (tappedout.net) Clear All Filters - + 全フィルタ解除 Clear Selected Filter - + 選択したフィルタを解除 @@ -8314,17 +8314,17 @@ Please refrain from engaging in this activity or further actions may be taken ag Remove Card - + カードを取り除く Manage Sets... - + セットを管理... Edit Custom Tokens... - + カスタムトークンを編集... @@ -8339,17 +8339,17 @@ Please refrain from engaging in this activity or further actions may be taken ag Add Card - + カードを追加 Load Deck... - + デッキを開く... Load Deck from Clipboard... - + クリップボードからデッキを開く... @@ -8364,38 +8364,38 @@ Please refrain from engaging in this activity or further actions may be taken ag New Deck - + 新しいデッキ Open Custom Pictures Folder - + カスタム画像フォルダを開く Print Deck... - + デッキを印刷... Delete Card - + カードを削除 Reset Layout - + レイアウトをリセット Save Deck - + デッキを保存 Save Deck as... - + 名前を付けて保存... @@ -8410,108 +8410,108 @@ Please refrain from engaging in this activity or further actions may be taken ag Save Deck to Clipboard - + クリップボードにデッキをコピー Save Deck to Clipboard (No Set Info) - + クリップボードにデッキをコピー(セット情報なし) Load Local Deck... - + デッキ読み込み... Load Remote Deck... - + サーバーのデッキを開く... Set Ready to Start - + 準備完了する Toggle Sideboard Lock - + サイドボードロックの切り替え Add Green Counter - + 緑カウンターを置く Remove Green Counter - + 緑カウンターを取り除く Set Green Counters... - + 緑カウンターの数を決める... Add Yellow Counter - + 黄カウンターを置く Remove Yellow Counter - + 黄カウンターを取り除く Set Yellow Counters... - + 黄カウンターの数を決める... Add Red Counter - + 赤カウンターを置く Remove Red Counter - + 赤カウンターを取り除く Set Red Counters... - + 赤カウンターの数を決める... Add Life Counter - + ライフを得る Remove Life Counter - + ライフを失う Set Life Counters... - + ライフ総量を決める... Add White Counter - + 白カウンターを置く Remove White Counter - + 白カウンターを取り除く diff --git a/cockatrice/translations/cockatrice_ko.ts b/cockatrice/translations/cockatrice_ko.ts index 94d30d6d4..93aa549ee 100644 --- a/cockatrice/translations/cockatrice_ko.ts +++ b/cockatrice/translations/cockatrice_ko.ts @@ -542,17 +542,17 @@ This is only saved for moderators and cannot be seen by the banned person. - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_nl.ts b/cockatrice/translations/cockatrice_nl.ts index 6a98be428..f7de64b8a 100644 --- a/cockatrice/translations/cockatrice_nl.ts +++ b/cockatrice/translations/cockatrice_nl.ts @@ -542,17 +542,17 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Bekijk gerelateerde kaarten - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_pl.ts b/cockatrice/translations/cockatrice_pl.ts index 4ee1342d1..9c00c5c20 100644 --- a/cockatrice/translations/cockatrice_pl.ts +++ b/cockatrice/translations/cockatrice_pl.ts @@ -542,17 +542,17 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_pt.ts b/cockatrice/translations/cockatrice_pt.ts index 8e242f776..58ca2eb1d 100644 --- a/cockatrice/translations/cockatrice_pt.ts +++ b/cockatrice/translations/cockatrice_pt.ts @@ -542,17 +542,17 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_pt_BR.ts b/cockatrice/translations/cockatrice_pt_BR.ts index ca00d401f..0e7420216 100644 --- a/cockatrice/translations/cockatrice_pt_BR.ts +++ b/cockatrice/translations/cockatrice_pt_BR.ts @@ -542,17 +542,17 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Visualizar cartas relacionadas - + Add card to deck Adicionar carta ao deck - + Mainboard Main - + Sideboard Side diff --git a/cockatrice/translations/cockatrice_ru.ts b/cockatrice/translations/cockatrice_ru.ts index f3ebed977..7abe3bdcc 100644 --- a/cockatrice/translations/cockatrice_ru.ts +++ b/cockatrice/translations/cockatrice_ru.ts @@ -542,17 +542,17 @@ This is only saved for moderators and cannot be seen by the banned person. - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_sr.ts b/cockatrice/translations/cockatrice_sr.ts index 6f2dd3122..f13b8bb0d 100644 --- a/cockatrice/translations/cockatrice_sr.ts +++ b/cockatrice/translations/cockatrice_sr.ts @@ -12,12 +12,12 @@ Set counter - + Podesi brojač New value for counter '%1': - + Nova vrednost za brojač '%1': @@ -38,12 +38,12 @@ Open in new tab - + Otvori u novoj kartici Are you sure? - + Da li ste sigurni? @@ -542,17 +542,17 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_sv.ts b/cockatrice/translations/cockatrice_sv.ts index a57f43d39..59955217f 100644 --- a/cockatrice/translations/cockatrice_sv.ts +++ b/cockatrice/translations/cockatrice_sv.ts @@ -542,17 +542,17 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - + Add card to deck - + Mainboard - + Sideboard @@ -8864,7 +8864,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Leave Game - + Lämna spelet @@ -8874,7 +8874,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Roll Dice... - + Kasta tärning @@ -8984,32 +8984,32 @@ Please refrain from engaging in this activity or further actions may be taken ag Visual Deck Storage - + Visuell Leklagring Deck Storage - + Leklagring Server - + Server Account - + Konto Administration - + Administratör Logs - + Loggar \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_yue.ts b/cockatrice/translations/cockatrice_yue.ts index 1299b55db..2b647eb2e 100644 --- a/cockatrice/translations/cockatrice_yue.ts +++ b/cockatrice/translations/cockatrice_yue.ts @@ -542,17 +542,17 @@ This is only saved for moderators and cannot be seen by the banned person.查看關聯卡牌 - + Add card to deck - + Mainboard - + Sideboard diff --git a/cockatrice/translations/cockatrice_zh-Hans.ts b/cockatrice/translations/cockatrice_zh-Hans.ts index cf65e3a9e..13ee81ee1 100644 --- a/cockatrice/translations/cockatrice_zh-Hans.ts +++ b/cockatrice/translations/cockatrice_zh-Hans.ts @@ -542,17 +542,17 @@ This is only saved for moderators and cannot be seen by the banned person.查看关联卡牌 - + Add card to deck - + Mainboard - + Sideboard diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 80a83e202..53260e1fd 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -106,6 +106,9 @@ void SettingsCache::setSeenTips(const QList & /* _seenTips */) void SettingsCache::setDeckPath(const QString & /* _deckPath */) { } +void SettingsCache::setFiltersPath(const QString & /*_filtersPath */) +{ +} void SettingsCache::setReplaysPath(const QString & /* _replaysPath */) { } @@ -234,16 +237,18 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) { } -void SettingsCache::setVisualDeckStoragePromptForConversion( - QT_STATE_CHANGED_T /* _visualDeckStoragePromptForConversion */) +void SettingsCache::setVisualDeckStoragePromptForConversion(bool /* _visualDeckStoragePromptForConversion */) { } -void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T /* _visualDeckStorageAlwaysConvert */) +void SettingsCache::setVisualDeckStorageAlwaysConvert(bool /* _visualDeckStorageAlwaysConvert */) { } void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T /* value */) { } +void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T /* value */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { } diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 405fb9bbd..de81cee3f 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -219,7 +219,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList static const QMap identifierProperties{{"multiverseId", "muid"}, {"scryfallId", "uuid"}}; int numCards = 0; - QMap> splitCards; + QMap, QString>> splitCards; QString ptSeparator("/"); QVariantMap card; QString layout, name, text, colors, colorIdentity, faceName; @@ -293,6 +293,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList const QChar lastChar = numProperty.isEmpty() ? QChar() : numProperty.back(); // Un-Sets do some wonky stuff. Split up these cards as individual entries. + // these cards will have a num with a letter (abc) behind it, put that letter into the name if (setsWithCardsWithSameNameButDifferentText.contains(currentSet->getShortName()) && allNameProps.contains(faceName) && layout == "normal" && lastChar.isLetter()) { numComponent = " (" + QString(lastChar).toLower() + ")"; @@ -337,13 +338,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList if (layout == "split" || layout == "aftermath" || layout == "adventure") { auto _faceName = getStringPropertyFromMap(card, "faceName"); SplitCardPart split(_faceName, text, properties, setInfo); - auto found_iter = splitCards.find(name); + auto found_iter = splitCards.find(name + numProperty); if (found_iter == splitCards.end()) { - splitCards.insert(name, {split}); + splitCards.insert(name + numProperty, {{split}, name}); } else if (layout == "adventure") { - found_iter->insert(0, split); + found_iter->first.insert(0, split); } else { - found_iter->append(split); + found_iter->first.append(split); } } else { // relations @@ -397,22 +398,15 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList static const QString splitCardTextSeparator = QString("\n\n---\n\n"); for (const QString &nameSplit : splitCards.keys()) { // get all parts for this specific card - QList splitCardParts = splitCards.value(nameSplit); - QSet done{}; + QList splitCardParts = splitCards.value(nameSplit).first; + name = splitCards.value(nameSplit).second; text.clear(); properties.clear(); relatedCards.clear(); for (const SplitCardPart &tmp : splitCardParts) { - // some sets have 2 different variations of the same split card, - // eg. Fire // Ice in WC02. Avoid adding duplicates. QString splitName = tmp.getName(); - if (done.contains(splitName)) { - continue; - } - done.insert(splitName); - if (!text.isEmpty()) { text.append(splitCardTextSeparator); } @@ -441,7 +435,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList } } } - CardInfoPtr newCard = addCard(nameSplit, text, isToken, properties, relatedCards, setInfo); + CardInfoPtr newCard = addCard(name, text, isToken, properties, relatedCards, setInfo); numCards++; } diff --git a/oracle/translations/oracle_de.ts b/oracle/translations/oracle_de.ts index 26e14b624..5288b42b6 100644 --- a/oracle/translations/oracle_de.ts +++ b/oracle/translations/oracle_de.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -86,7 +63,7 @@ Sets file (%1) Sets JSON file (%1) - + Sets Datei (%1) @@ -305,15 +282,6 @@ Falls die Datenbanken nicht automatisch neu geladen werden, starten Sie bitte Cockatrice neu. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -389,21 +357,6 @@ %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -439,34 +392,6 @@ Please check your shortcut settings! Die Datei konnte nicht in %1 gespeichert werden - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -612,7 +537,6 @@ Please check your shortcut settings! i18n - English Deutsch (German) diff --git a/oracle/translations/oracle_el.ts b/oracle/translations/oracle_el.ts index fbbd823b0..ce69ace7e 100644 --- a/oracle/translations/oracle_el.ts +++ b/oracle/translations/oracle_el.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ Δεν ήταν δυνατή η αποθήκευση του αρχείου στο %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Ελληνικά (Greek) diff --git a/oracle/translations/oracle_en_US.ts b/oracle/translations/oracle_en_US.ts index ae8f80c64..46b156d23 100644 --- a/oracle/translations/oracle_en_US.ts +++ b/oracle/translations/oracle_en_US.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ If the card databases don't reload automatically, restart the Cockatrice client. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ The file could not be saved to %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! The file could not be saved to %1 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English English diff --git a/oracle/translations/oracle_es.ts b/oracle/translations/oracle_es.ts index bf9cb083a..f0e70ff4f 100644 --- a/oracle/translations/oracle_es.ts +++ b/oracle/translations/oracle_es.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ Si las bases de datos de cartas no se recargan automáticamente, reinicia el cliente de Cockatrice. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ El archivo no ha podido ser guardado en %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! El archivo no ha podido ser guardado en %1 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Español (Spanish) diff --git a/oracle/translations/oracle_et.ts b/oracle/translations/oracle_et.ts index 6712479e9..d3389b3e4 100644 --- a/oracle/translations/oracle_et.ts +++ b/oracle/translations/oracle_et.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ Kui kaardi andmebaasid ei lae ise, taaskäivita Cockatrice’i klient. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ Faili salvestamine asukohta %1 ebaõnnestus - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! Faili salvestamine asukohta %1 nurjus - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Eesti Keel (Estonian) diff --git a/oracle/translations/oracle_fi.ts b/oracle/translations/oracle_fi.ts index 7b8e348c3..b7338f744 100644 --- a/oracle/translations/oracle_fi.ts +++ b/oracle/translations/oracle_fi.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ Jos korttitietokannat eivät päivity automaattisesti, käynnistä Cockatrice uudelleen. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ Tiedostoa ei voitu tallentaa osoitteeseen %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! Tiedostoa ei voitu tallentaa osoitteeseen %1 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Suomi (Finnish) diff --git a/oracle/translations/oracle_fr.ts b/oracle/translations/oracle_fr.ts index cc90599e2..01f5413a0 100644 --- a/oracle/translations/oracle_fr.ts +++ b/oracle/translations/oracle_fr.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ Si les bases de données de cartes ne rechargent pas automatiquement, redémarrez le client Cockatrice. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ Le fichier n'a pu être sauvegarder au chemin '%1' - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! Le fichier n'a pas pu être enregistré dans %1 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Français (French) diff --git a/oracle/translations/oracle_it.ts b/oracle/translations/oracle_it.ts index 0c8ea4ec1..fcf902cd0 100644 --- a/oracle/translations/oracle_it.ts +++ b/oracle/translations/oracle_it.ts @@ -369,7 +369,7 @@ e pedine che verranno usate da Cockatrice. The provided URL is not valid. - L'indirizzo URL non è valido. + L'indirizzo fornito non è valido. diff --git a/oracle/translations/oracle_ja.ts b/oracle/translations/oracle_ja.ts index 4ce2f652d..baee43d9a 100644 --- a/oracle/translations/oracle_ja.ts +++ b/oracle/translations/oracle_ja.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ カードデータベースが自動的に再読込されない場合は、Cockatriceを再起動して下さい。 - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ %1に保存できませんでした。 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! %1に保存できませんでした。 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English 日本語 (Japanese) diff --git a/oracle/translations/oracle_ko.ts b/oracle/translations/oracle_ko.ts index e5f27aa0e..545bdf2e0 100644 --- a/oracle/translations/oracle_ko.ts +++ b/oracle/translations/oracle_ko.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ 파일을 %1에 저장 할 수 없습니다. - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English 한국어 (Korean) diff --git a/oracle/translations/oracle_nb.ts b/oracle/translations/oracle_nb.ts index 28adbe681..2f87d4e22 100644 --- a/oracle/translations/oracle_nb.ts +++ b/oracle/translations/oracle_nb.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ Filen kunne ikke lagres til %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Norsk Bokmål (Norwegian Bokmål) diff --git a/oracle/translations/oracle_nl.ts b/oracle/translations/oracle_nl.ts index 5f2f987d9..8787e8214 100644 --- a/oracle/translations/oracle_nl.ts +++ b/oracle/translations/oracle_nl.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ Als de kaartendatabases niet automatisch herladen, start dan de Cockatrice client opnieuw op. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ Het bestand kon niet worden opgeslagen in %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! Het bestand kon niet worden opgeslagen naar %1 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Nederlands (Dutch) diff --git a/oracle/translations/oracle_pl.ts b/oracle/translations/oracle_pl.ts index afcd92bb6..e576dd41b 100644 --- a/oracle/translations/oracle_pl.ts +++ b/oracle/translations/oracle_pl.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ Jeżeli bazy kart nie przeładują się automatycznie, zrestartuj klienta Cockatrice. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ Plik nie mógł zostać zapisany do %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! Plik nie mógł zostać zapisany do %1 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Polski (Polish) diff --git a/oracle/translations/oracle_pt.ts b/oracle/translations/oracle_pt.ts index 64d91e9c2..63d5d47a8 100644 --- a/oracle/translations/oracle_pt.ts +++ b/oracle/translations/oracle_pt.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ O ficheiro não pode ser gravado em %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Português (Portuguese) diff --git a/oracle/translations/oracle_pt_BR.ts b/oracle/translations/oracle_pt_BR.ts index 56f9da29e..59ea83ec1 100644 --- a/oracle/translations/oracle_pt_BR.ts +++ b/oracle/translations/oracle_pt_BR.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ Se a base de dados das cartas não for atualizada automaticamente, reinicie o cliente do Cockatrice. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ O arquivo não pode ser salvo para %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! O arquivo não pode ser salvo em %1 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Português do Brasil (Brazilian Portuguese) diff --git a/oracle/translations/oracle_ru.ts b/oracle/translations/oracle_ru.ts index 0327ea24f..2317821fe 100644 --- a/oracle/translations/oracle_ru.ts +++ b/oracle/translations/oracle_ru.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ Не удалось сохранить файл в %1 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Русский (Russian) diff --git a/oracle/translations/oracle_sr.ts b/oracle/translations/oracle_sr.ts index a055544b8..a0edc343c 100644 --- a/oracle/translations/oracle_sr.ts +++ b/oracle/translations/oracle_sr.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Srpski (Serbian) diff --git a/oracle/translations/oracle_tr.ts b/oracle/translations/oracle_tr.ts index a5b2f233a..04ae10437 100644 --- a/oracle/translations/oracle_tr.ts +++ b/oracle/translations/oracle_tr.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English Türkçe (Turkish) diff --git a/oracle/translations/oracle_yue.ts b/oracle/translations/oracle_yue.ts index ea691eb6a..963d33007 100644 --- a/oracle/translations/oracle_yue.ts +++ b/oracle/translations/oracle_yue.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ 如果卡牌資料庫尚未能自動更新,請把cockatrice用戶端重新開啟. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ 檔案未能儲藏於%1。 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! 檔案未能儲藏於%1 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English diff --git a/oracle/translations/oracle_zh-Hans.ts b/oracle/translations/oracle_zh-Hans.ts index d8f65610d..8381da1b5 100644 --- a/oracle/translations/oracle_zh-Hans.ts +++ b/oracle/translations/oracle_zh-Hans.ts @@ -1,27 +1,4 @@ - - BetaReleaseChannel - - - Beta - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - IntroPage @@ -305,15 +282,6 @@ 如果卡牌资料库尚未能自动更新,请把cockatrice用户端重新开启. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - SaveSetsPage @@ -388,21 +356,6 @@ 档案未能储藏于%1。 - - ShortcutsSettings - - - Your configuration file contained invalid shortcuts. -Please check your shortcut settings! - - - - - The following shortcuts have been set to default: - - - - SimpleDownloadFilePage @@ -438,34 +391,6 @@ Please check your shortcut settings! 档案未能储藏于%1 - - StableReleaseChannel - - - Default - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the tag update server. - - - - - Invalid reply received from the tag update server. - - - UnZip @@ -611,7 +536,6 @@ Please check your shortcut settings! i18n - English 简体中文 (Chinese Simplified) diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index 184de3727..c5cf99b68 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -110,6 +110,9 @@ void SettingsCache::setSeenTips(const QList & /* _seenTips */) void SettingsCache::setDeckPath(const QString & /* _deckPath */) { } +void SettingsCache::setFiltersPath(const QString & /*_filtersPath */) +{ +} void SettingsCache::setReplaysPath(const QString & /* _replaysPath */) { } @@ -238,16 +241,18 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) { } -void SettingsCache::setVisualDeckStoragePromptForConversion( - QT_STATE_CHANGED_T /* _visualDeckStoragePromptForConversion */) +void SettingsCache::setVisualDeckStoragePromptForConversion(bool /* _visualDeckStoragePromptForConversion */) { } -void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T /* _visualDeckStorageAlwaysConvert */) +void SettingsCache::setVisualDeckStorageAlwaysConvert(bool /* _visualDeckStorageAlwaysConvert */) { } void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T /* value */) { } +void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T /* value */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { }