diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 2db045819..10ffa3493 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -7,32 +7,33 @@
# Contributing to Cockatrice #
-First off, thanks for taking the time to contribute to our project! 🎉 ❤ ️✨
-The following is a set of guidelines for contributing to Cockatrice. These are
-mostly guidelines, not rules. Use your best judgment, and feel free to propose
-changes to this document in a pull request.
+First off, thanks for taking the time and considering to lend a helping hand to our project! 🎉 ❤ ️✨
+
+> [!NOTE]
+> The following is a set of guidelines for contributing to Cockatrice.
+> These are mostly guidelines, not rules. Use your best judgment, and feel free to
+> propose changes to this document in a pull request.
+>
+> [](
+> https://discord.gg/3Z9yzmA)
+> If you'd like to ask questions, get advice, or just want to say "Hi",
+> the Cockatrice Development Team uses [Discord](https://discord.gg/ZASRzKu)
+> for communications and you can reach out in the `#dev` channel.
# Recommended Setups #
-For those developers who like the Linux or MacOS environment, many of our
+For those developers on **Linux** or **macOS** environment, many of our
developers like working with a nifty program called [CLion](
-https://www.jetbrains.com/clion/). The program's a great asset and one of the
-best tools you'll find on these systems, but you're welcomed to use any IDE
-you most enjoy.
+https://www.jetbrains.com/clion/). The program is a great asset and one of the
+best tools you'll find on these systems.
-Developers who like Windows development tend to find [Visual Studio](
+Developers on **Windows** systems tend to find [Visual Studio](
https://www.visualstudio.com/) the best tool for the job.
-[](https://discord.gg/ZASRzKu)
-[](https://gitter.im/Cockatrice/Cockatrice)
-
-If you'd like to ask questions, get advice, or just want to say hi,
-the Cockatrice Development Team uses [Discord](https://discord.gg/ZASRzKu)
-for communications in the #dev channel. If you're not into Discord, we also
-have a [Gitter](https://gitter.im/Cockatrice/Cockatrice) channel available,
-albeit slightly less active.
+But you're welcomed to use any IDE you enjoy most of course!
# Code Style Guide #
@@ -54,7 +55,7 @@ The message will look like this:
*** Then commit and push those changes to this branch. ***
*** Check our CONTRIBUTING.md file for more details. ***
*** ***
-*** Thank you ❤️ ***
+*** Thank you ❤️ ***
*** ***
***********************************************************
```
@@ -64,7 +65,7 @@ information on our formatting guidelines.
### Compatibility ###
-Cockatrice is currently compiled on all platforms using C++11.
+Cockatrice is currently compiled on all platforms using C++20.
You'll notice C++03 code throughout the codebase. Please feel free
to help convert it over!
@@ -78,11 +79,12 @@ or other appropriate conversion.
### Formatting ###
The handy tool `clang-format` can format your code for you, it is available for
-almost any environment. A special `.clang-format` configuration file is
+almost any environment. A special [`.clang-format`](
+https://github.com/Cockatrice/Cockatrice/blob/master/.clang-format) configuration file is
included in the project and is used to format your code.
We've also included a bash script, `format.sh`, that will use clang-format to
-format all files in your pr in one go. Use `./format.sh --help` to show a full
+format all files in your PR in one go. Use `./format.sh --help` to show a full
help page.
To run clang-format on a single source file simply use the command
@@ -90,10 +92,10 @@ To run clang-format on a single source file simply use the command
clang-format with a specific version number appended,
`find /usr/bin -name clang-format*` should find it for you)
-See [the clang-format documentation](
+See the [clang-format documentation](
https://clang.llvm.org/docs/ClangFormat.html) for more information about the tool.
-#### Header files ####
+#### Header Files ####
Use header files with the extension `.h` and source files with the extension
`.cpp`.
@@ -168,10 +170,10 @@ braces around single line statements is preferred.
See the following example:
```c++
int main()
-{ // function or class: own line
- if (someCondition) { // control statement: same line
- doSomething(); // single line statement, braces preferred
- } else if (someOtherCondition1) { // else goes on the same line as a closing brace
+{ // function or class: own line
+ if (someCondition) { // control statement: same line
+ doSomething(); // single line statement, braces preferred
+ } else if (someOtherCondition1) { // else goes on the same line as a closing brace
for (int i = 0; i < 100; i++) {
doSomethingElse();
}
@@ -234,7 +236,7 @@ mutating objects.)
When pointers can't be avoided, try to use a smart pointer of some sort, such
as `QScopedPointer`, or, less preferably, `QSharedPointer`.
-### Database migrations ###
+### Database Migrations ###
The servatrice database's schema can be found at `servatrice/servatrice.sql`.
Everytime the schema gets modified, some other steps are due:
@@ -255,7 +257,7 @@ Ensure that the migration produces the expected effects; e.g. if you add a
new column, make sure the migration places it in the same order as
servatrice.sql.
-### Protocol buffer ###
+### Protocol Buffer ###
Cockatrice and Servatrice exchange data using binary messages. The syntax of
these messages is defined in the `proto` files in the `common/pb` folder. These
@@ -268,6 +270,7 @@ new clients incompatible to the old server and vice versa.
You can find more information on how we use Protobuf on [our wiki!](
https://github.com/Cockatrice/Cockatrice/wiki/Client-server-protocol)
+
# Reviewing Pull Requests #
After you have finished your changes to the project you should put them on a
@@ -286,6 +289,7 @@ all changes have been approved your pull request will be squashed into a single
commit and merged into the master branch by a team member. Your change will then
be included in the next release 👍
+
# Translations #
Basic workflow for translations:
@@ -294,16 +298,16 @@ Basic workflow for translations:
3. Maintainer verifies and merges the change;
4. Transifex picks up the new files from GitHub automatically;
5. Translators translate the new untranslated strings on Transifex;
- 6. Before a release, a maintainer fetches the updated translations from Transifex.
+ 6. Before a release, a maintainer fetches the newest translations from Transifex.
### Using Translations (for developers) ###
All user interface strings inside Cockatrice's source code must be written
in English (US).
Translations to other languages are managed using [Transifex](
-https://www.transifex.com/projects/p/cockatrice/).
+https://transifex.com/cockatrice/cockatrice/).
-Adding a new string to translate is as easy as adding the string in the
+Adding a new string for translation is as easy as adding the string in the
`tr("")` function, the string will be picked up as translatable automatically
and translated as needed.
For example, setting the text of a label in the way that the string
@@ -315,9 +319,9 @@ nameLabel.setText(tr("My name is:"));
To translate a string that would have plural forms you can add the amount to
the tr() call, also you can add an extra string as a hint for translators:
```c++
-QString message = tr("Everyone draws %n cards", "pop up message", amount);
+QString message = tr("Everyone draws %n cards", "english hint for translators", amount);
```
-See [QT's wiki on translations](
+See [Qt's wiki on translations](
https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals)
If you're about to propose a change that adds or modifies any translatable
@@ -325,7 +329,7 @@ string in the code, you don't need to take care of adding the new strings to
the translation files.
We have an automated process to update our language source files on a schedule
and provide the translators on Transifex with the new contents.
-Maintainers can also manually trigger this on demand.
+Maintainers can also manually trigger this workflow on demand via GitHub Actions.
### Maintaining Translations (for maintainers) ###
@@ -389,27 +393,27 @@ Now you are ready to commit your changes and open a PR.
Once the changes get merged, Transifex will pick up the modified files
-automatically (checked every few hours) and update their online editor where
+automatically (checked every few hours) and update the web editor where
translators will be able to translate the new strings right in the browser.
### Releasing Translations (for maintainers) ###
-Before rushing out a new release, a maintainer should fetch the most up to date
+Before publishing a new release, a maintainer should fetch the most up to date
translations from Transifex and commit them into the Cockatrice source code.
-This can be done manually from the Transifex web interface, but it's quite time
-consuming.
-
-As an alternative, you can install the Transifex CLI:
-
- http://docs.transifex.com/developer/client/
+We utilize the official GitHub integration to push all languages that are 100%
+translated from Transifex to our GitHub repo automatically.
+On top, it runs on a quarterly schedule to update changes for incomplete languages.
+A synchronisation/update can also be triggered manually from the Transifex web interface
+and a translation treshold can be set.
+As an alternative, you can install the [Transifex CLI](https://developers.transifex.com/docs/cli).
You'll then be able to use a git-like cli command to push and pull translations
from Transifex to the source code and vice versa.
### Adding Translations (for translators) ###
-As a translator you can help translate the new strings on [Transifex](
-https://www.transifex.com/projects/p/cockatrice/).
+As a translator, you can help to translate new strings on [Transifex](
+https://www.transifex.com/projects/p/cockatrice/) to your native language.
Please have a look at the specific [FAQ for translators](
https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ).
@@ -419,9 +423,9 @@ https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ).
### Publishing A New Release ###
We use [GitHub Releases](https://github.com/Cockatrice/Cockatrice/releases) to
-publish new stable versions and betas.
-Whenever a git tag is pushed to the repository github will create a draft
-release and upload binaries automatically.
+publish new stable versions and beta releases.
+Whenever a git tag is pushed to the repository, GitHub will create a draft
+release and upload binaries from our CI automatically.
To create a tag, simply do the following:
```bash
@@ -433,18 +437,16 @@ git push $UPSTREAM $TAG_NAME
```
You should define the variables as such:
-```
-`$UPSTREAM` - the remote for git@github.com:Cockatrice/Cockatrice.git
-`$TAG_NAME` should be formatted as:
- - `YYYY-MM-DD-Release-MAJ.MIN.PATCH` for **stable releases**
- - `YYYY-MM-DD-Development-MAJ.MIN.PATCH-beta.X` for **beta releases**
- With *MAJ.MIN.PATCH* being the NEXT release version!
-```
+ - `$UPSTREAM`: the remote for `git@github.com:Cockatrice/Cockatrice.git`
+ - `$TAG_NAME` should be formatted as:
+ - `YYYY-MM-DD-Release-MAJ.MIN.PATCH` for **stable releases**
+ - `YYYY-MM-DD-Development-MAJ.MIN.PATCH-beta.X` for **beta releases**
+ With MAJ.MIN.PATCH being the NEXT release version!
This will cause a tagged release to be established on the GitHub repository,
-with the binaries being added to the release whenever they are ready.
-The release is initially a draft, where the path notes can be edited and after
-all is good and ready it can be published on GitHub.
+with the binaries being added to the release whenever they are done building in CI.
+The release is initially a draft, where the release notes can be edited and after
+all is checked and ready, it can be published as GitHub release.
If you use a SemVer tag including "beta", the release that will be created at
GitHub will be marked as a "Pre-release" automatically.
The target of the `.../latest` URL will not be changed in that case, it always
@@ -457,7 +459,7 @@ revoke the tag by doing the following:
git push --delete upstream $TAG_NAME
git tag -d $TAG_NAME
```
-You can also do this on GitHub, you'll also want to delete the new release.
+You can also do this on GitHub, you'll also want to delete the false release.
In the first lines of [CMakeLists.txt](
https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt)
@@ -468,25 +470,24 @@ coming from the tag title, it's good practice to increment the ones at CMake
after every full release to stress that master is ahead of the last stable
release.
The preferred flow of operation is:
- * Just before a release, make sure the version number in CMakeLists.txt is set
- to the same release version you are about to tag.
- * This is also the time to change the pretty name in CMakeLists.txt called
- GIT_TAG_RELEASENAME and commit and push these changes.
- * Tag the release following the previously described syntax in order to get it
- correctly built and deployed by CI.
- * Wait for the configure step to create the release and update the patch
- notes.
- * Check on the github actions page for build progress which should be the top
- listed [here](
+ - Just before a release, make sure the version number in CMakeLists.txt is set
+ to the same release version you are about to tag.
+ - This is also the time to change the pretty name in CMakeLists.txt called
+ `GIT_TAG_RELEASENAME` and commit and push these changes.
+ - Tag the release following the previously described syntax in order to get it
+ correctly built and deployed by CI.
+ - Wait for the configuration step to create the release and update the patch
+ notes.
+ - Check on the GitHub Actions page for build progress which should be the top
+ listed [here](
https://github.com/Cockatrice/Cockatrice/actions?query=event%3Apush+-branch%3Amaster
).
- * When the build has been completed you can verify all uploaded files on the
- release are in order and hit the publish button.
- * After the release is complete, update the CMake version number again to the
- next targeted beta version, typically increasing `PROJECT_VERSION_PATCH` by
- one.
+ - When the build has been completed, you can verify if all uploaded files on the
+ draft release are included and hit the publish button.
+ - After the release is complete, update the CMake version number again to the
+ next targeted beta version, typically increasing `PROJECT_VERSION_PATCH` by
+ one.
When releasing a new stable version, all previous beta releases (and tags)
-should be deleted. This is needed for Cockatrice to update users of the "Beta"
-release channel correctly to the latest stable version as well.
+should be deleted as well.
This can be done the same way as revoking tags, mentioned above.
diff --git a/Dockerfile b/Dockerfile
index 31b84f026..330c51b4a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -26,6 +26,6 @@ RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=
WORKDIR /home/servatrice
-EXPOSE 4747 4748
+EXPOSE 4748
ENTRYPOINT [ "servatrice", "--log-to-console" ]
diff --git a/README.md b/README.md
index 86c669763..f7fffdedf 100644
--- a/README.md
+++ b/README.md
@@ -128,9 +128,9 @@ First, create an image from the Dockerfile
`cd /path/to/Cockatrice-Repo/`
`docker build -t servatrice .`
And then run it
-`docker run -i -p 4747:4747/tcp -t servatrice:latest`
+`docker run -i -p 4748:4748 -t servatrice:latest`
->Note: Running this command exposes the TCP port 4747 of the docker container
+>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).
@@ -145,7 +145,7 @@ docker-compose build # Build the Servatrice image using the same Dockerfile a
docker-compose up # Setup and run both the MySQL server and Servatrice.
```
->Note: Similar to the above Docker setup, this will expose TCP ports 4747 and 4748.
+>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.
diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index ca16d3d15..9b50c9469 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -4,206 +4,9 @@
project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
-set(cockatrice_SOURCES
- src/game/cards/abstract_card_drag_item.cpp
- src/game/cards/abstract_card_item.cpp
- src/client/game_logic/abstract_client.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/client/ui/widgets/general/display/banner_widget.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/filters/filter_card.cpp
- src/client/ui/widgets/cards/card_info_frame_widget.cpp
- src/client/ui/widgets/cards/card_info_picture_widget.cpp
- src/client/ui/widgets/cards/card_info_text_widget.cpp
- src/client/ui/widgets/cards/card_info_display_widget.cpp
- src/client/ui/widgets/cards/card_size_widget.cpp
- src/game/cards/card_item.cpp
- src/game/cards/card_list.cpp
- src/game/zones/card_zone.cpp
- src/server/chat_view/chat_view.cpp
- src/game/board/counter_general.cpp
- src/deck/custom_line_edit.cpp
- src/deck/deck_loader.cpp
- src/deck/deck_list_model.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_token.cpp
- src/dialogs/dlg_create_game.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_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/dialogs/dlg_load_deck.cpp
- src/game/deckview/deck_view.cpp
- src/game/deckview/deck_view_container.cpp
- src/game/filters/filter_string.cpp
- src/game/filters/filter_builder.cpp
- src/game/filters/filter_tree.cpp
- src/game/filters/filter_tree_model.cpp
- src/client/ui/layouts/flow_layout.cpp
- src/client/ui/widgets/general/layout_containers/flow_widget.cpp
- src/game/game_scene.cpp
- src/game/game_selector.cpp
- src/game/games_model.cpp
- src/game/game_view.cpp
- src/client/get_text_with_max.cpp
- src/game/hand_counter.cpp
- src/server/handle_public_servers.cpp
- src/game/zones/hand_zone.cpp
- src/client/game_logic/key_signals.cpp
- src/client/ui/line_edit_completer.cpp
- src/server/local_client.cpp
- src/server/local_server.cpp
- src/server/local_server_interface.cpp
- src/utility/logger.cpp
- src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
- src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.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/general/display/banner_widget.cpp
- src/client/ui/widgets/general/display/labeled_input.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/shadow_background_label.cpp
- src/main.cpp
- src/server/message_log_widget.cpp
- src/client/ui/layouts/overlap_layout.cpp
- src/client/ui/widgets/general/layout_containers/overlap_widget.cpp
- src/client/ui/widgets/general/layout_containers/overlap_control_widget.cpp
- src/server/pending_command.cpp
- src/game/phase.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/game/zones/pile_zone.cpp
- src/client/ui/pixel_map_generator.cpp
- src/game/player/player.cpp
- src/game/player/player_list_widget.cpp
- src/game/player/player_target.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/network/release_channel.cpp
- src/client/network/client_update_checker.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/client/network/replay_timeline_widget.cpp
- src/game/zones/select_zone.cpp
- src/utility/sequence_edit.cpp
- src/client/network/sets_model.cpp
- src/settings/card_database_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/cache_settings.cpp
- src/settings/shortcuts_settings.cpp
- src/settings/shortcut_treeview.cpp
- src/settings/card_override_settings.cpp
- src/settings/debug_settings.cpp
- src/client/sound_engine.cpp
- src/client/network/spoiler_background_updater.cpp
- src/game/zones/stack_zone.cpp
- src/client/tabs/tab.cpp
- src/client/tabs/tab_account.cpp
- src/client/tabs/tab_admin.cpp
- src/client/tabs/abstract_tab_deck_editor.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/api/edhrec/tab_edhrec.cpp
- src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp
- src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp
- src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp
- src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp
- src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp
- src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp
- src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp
- src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp
- src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp
- src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp
- src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp
- src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp
- src/game/zones/table_zone.cpp
- src/client/tapped_out_interface.cpp
- src/client/ui/theme_manager.cpp
- src/client/ui/tip_of_the_day.cpp
- src/client/translate_counter_name.cpp
- src/client/update_downloader.cpp
- src/server/user/user_context_menu.cpp
- src/server/user/user_info_connection.cpp
- src/server/user/user_info_box.cpp
- src/server/user/user_list_manager.cpp
- src/server/user/user_list_widget.cpp
- src/client/ui/window_main.cpp
- src/game/zones/view_zone_widget.cpp
- src/game/zones/view_zone.cpp
- src/client/menus/deck_editor/deck_editor_menu.cpp
- src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
- src/client/ui/widgets/cards/deck_preview_card_picture_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_tag_addition_widget.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_dialog.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_deck_tags_display_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_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/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
- ${VERSION_STRING_CPP}
-)
+file(GLOB_RECURSE cockatrice_CPP_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp)
+
+set(cockatrice_SOURCES ${cockatrice_CPP_FILES} ${VERSION_STRING_CPP})
add_subdirectory(sounds)
add_subdirectory(themes)
diff --git a/cockatrice/cockatrice_en@source.ts b/cockatrice/cockatrice_en@source.ts
index 82e0da237..be95b2f58 100644
--- a/cockatrice/cockatrice_en@source.ts
+++ b/cockatrice/cockatrice_en@source.ts
@@ -22,6 +22,86 @@
+
+ AbstractDlgDeckTextEdit
+
+
+ &Refresh
+
+
+
+
+ Parse Set Name and Number (if available)
+
+
+
+
+ AbstractTabDeckEditor
+
+
+ Open in new tab
+
+
+
+
+ Are you sure?
+
+
+
+
+ The decklist has been modified.
+Do you want to save the changes?
+
+
+
+
+
+
+
+
+
+
+ Error
+
+
+
+
+ Could not open deck at %1
+
+
+
+
+ Could not save remote deck
+
+
+
+
+
+ The deck could not be saved.
+Please check that the directory is writable and try again.
+
+
+
+
+ Save deck
+
+
+
+
+ The deck could not be saved.
+
+
+
+
+ There are no cards in your deck to be exported
+
+
+
+
+ No deck was selected to be exported.
+
+
+AdminNotesDialog
@@ -51,127 +131,123 @@
AppearanceSettingsPage
-
+ Error
-
+ Could not create themes directory at '%1'.
-
+ Theme settings
-
+ Current theme:
-
+ Open themes folder
-
+ Menu settings
-
+ Show keyboard shortcuts in right-click menus
-
- Draw missing color identities in visual deck storage without color label
-
-
-
-
- Missing color identity opacity
-
-
-
-
+ Card rendering
-
+ Display card names on cards having a picture
-
+ Auto-Rotate cards with sideways layout
-
+ Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart]
-
+ Bump sets that the deck contains cards from to the top in the printing selector
-
+ Scale cards on mouse over
-
+ Minimum overlap percentage of cards on the stack and in vertical hand
-
+ Maximum initial height for card view window:
-
+
+ rows
-
+
+ Maximum expanded height for card view window:
+
+
+
+ Hand layout
-
+ Display hand horizontally (wastes space)
-
+ Enable left justification
-
+ Table grid layout
-
+ Invert vertical coordinate
-
+ Minimum player count for multi-column layout:
-
+ Maximum font size for information displayed on cards:
@@ -291,22 +367,22 @@ This is only saved for moderators and cannot be seen by the banned person.
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.
@@ -457,22 +533,22 @@ This is only saved for moderators and cannot be seen by the banned person.
CardInfoPictureWidget
-
+ View related cards
-
+ Add card to deck
-
+ Mainboard
-
+ Sideboard
@@ -498,12 +574,12 @@ This is only saved for moderators and cannot be seen by the banned person.
CardItem
-
+ &Move to
-
+ &Power / toughness
@@ -639,169 +715,414 @@ This is only saved for moderators and cannot be seen by the banned person.
+
+ DeckEditorCardInfoDockWidget
+
+
+ Card Info
+
+
+
+
+ DeckEditorDatabaseDisplayWidget
+
+
+ Search by card name (or search expressions)
+
+
+
+
+ Add to Deck
+
+
+
+
+ Add to Sideboard
+
+
+
+
+ Select Printing
+
+
+
+
+ Show on EDHREC (Commander)
+
+
+
+
+ Show on EDHREC (Card)
+
+
+
+
+ Show Related cards
+
+
+
+
+ Add card to &maindeck
+
+
+
+
+ Add card to &sideboard
+
+
+
+
+ DeckEditorDeckDockWidget
+
+
+ Banner Card
+
+
+
+
+ Select Printing
+
+
+
+
+ Deck
+
+
+
+
+ Deck &name:
+
+
+
+
+ &Comments:
+
+
+
+
+ Hash:
+
+
+
+
+ &Increment number
+
+
+
+
+ &Decrement number
+
+
+
+
+ &Remove row
+
+
+
+
+ Swap card to/from sideboard
+
+
+
+
+ DeckEditorFilterDockWidget
+
+
+ Filters
+
+
+
+
+ &Clear all filters
+
+
+
+
+ Delete selected
+
+
+
+
+ DeckEditorMenu
+
+
+ &Deck Editor
+
+
+
+
+ &New deck
+
+
+
+
+ &Load deck...
+
+
+
+
+ Load recent deck...
+
+
+
+
+ Clear
+
+
+
+
+ &Save deck
+
+
+
+
+ Save deck &as...
+
+
+
+
+ Load deck from cl&ipboard...
+
+
+
+
+ Edit deck in clipboard
+
+
+
+
+
+ Annotated
+
+
+
+
+
+ Not Annotated
+
+
+
+
+ Save deck to clipboard
+
+
+
+
+ Annotated (No set info)
+
+
+
+
+ Not Annotated (No set info)
+
+
+
+
+ &Print deck...
+
+
+
+
+ &Send deck to online service
+
+
+
+
+ Create decklist (decklist.org)
+
+
+
+
+ Create decklist (decklist.xyz)
+
+
+
+
+ Analyze deck (deckstats.net)
+
+
+
+
+ Analyze deck (tappedout.net)
+
+
+
+
+ &Close
+
+
+
+
+ DeckEditorPrintingSelectorDockWidget
+
+
+ Printing Selector
+
+
+DeckEditorSettingsPage
-
-
+
+ Update Spoilers
-
-
+
+ Success
-
+ Download URLs have been reset.
-
+ Downloaded card pictures have been reset.
-
+ Error
-
+ One or more downloaded card pictures could not be cleared.
-
+ Add URL
-
+ URL:
-
-
+
+ Edit URL
-
+ Network Cache Size:
-
+ Redirect Cache TTL:
-
+ How long cached redirects for urls are valid for.
-
+ Picture Cache Size:
-
+ Add New URL
-
+ Remove URL
-
+ Day(s)
-
+ Updating...
-
+ Choose path
-
+ URL Download Priority
-
+ Spoilers
-
+ Download Spoilers Automatically
-
+ Spoiler Location:
-
+ Last Change
-
+ Spoilers download automatically on launch
-
+ Press the button to manually update without relaunching
-
+ Do not close settings until manual update is complete
-
+ Download card pictures on the fly
-
+ How to add a custom URL
-
+ Delete Downloaded Images
-
+ Reset Download URLs
-
+ On-disk cache for downloaded pictures
-
+ In-memory cache for pictures not currently on screen
@@ -834,15 +1155,28 @@ This is only saved for moderators and cannot be seen by the banned person.
+
+ DeckLoader
+
+
+ Common deck formats (%1)
+
+
+
+
+ All files (*.*)
+
+
+DeckPreviewColorIdentityFilterWidget
-
+ Mode: Exact Match
-
+ Mode: Includes
@@ -850,8 +1184,7 @@ This is only saved for moderators and cannot be seen by the banned person.
DeckPreviewDeckTagsDisplayWidget
-
-
+ Edit tags ...
@@ -914,6 +1247,101 @@ This is only saved for moderators and cannot be seen by the banned person.
+
+ DeckPreviewWidget
+
+
+ Banner Card
+
+
+
+
+ Open in deck editor
+
+
+
+
+ Edit Tags
+
+
+
+
+ Rename Deck
+
+
+
+
+ Save Deck to Clipboard
+
+
+
+
+ Annotated
+
+
+
+
+ Annotated (No set info)
+
+
+
+
+ Not Annotated
+
+
+
+
+ Not Annotated (No set info)
+
+
+
+
+ Rename File
+
+
+
+
+ Delete File
+
+
+
+
+ Set Banner Card
+
+
+
+
+
+ New name:
+
+
+
+
+
+ Error
+
+
+
+
+ Rename failed
+
+
+
+
+ Delete file
+
+
+
+
+ Are you sure you want to delete the selected file?
+
+
+
+
+ Delete failed
+
+
+DeckStatsInterface
@@ -931,48 +1359,48 @@ This is only saved for moderators and cannot be seen by the banned person.
DeckViewContainer
-
+ Load deck...
-
+ Load remote deck...
-
+ Unload deckUnload deck...
-
+ Ready to start
-
+ Force start
-
+ Sideboard unlocked
-
+ Sideboard locked
-
+ Error
-
+ The selected file could not be loaded.
@@ -1392,6 +1820,24 @@ To remove your current avatar, confirm without choosing a new image.
+
+ DlgEditDeckInClipboard
+
+
+ Edit deck in clipboard
+
+
+
+
+ Error
+
+
+
+
+ Invalid deck list.
+
+
+DlgEditPassword
@@ -1876,29 +2322,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia
DlgLoadDeckFromClipboard
-
- &Refresh
-
-
-
-
- Parse Set Name and Number (if available)
-
-
-
-
+ Load deck from clipboard
-
-
+ Error
-
-
+ Invalid deck list.
@@ -1906,7 +2340,7 @@ Make sure to enable the 'Token' set in the "Manage sets" dia
DlgLoadRemoteDeck
-
+ Load deck
@@ -1914,37 +2348,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia
DlgMoveTopCardsUntil
-
+ Card name (or search expressions):
-
+ Number of hits:
-
+ Auto play hits
-
+ Put top cards on stack until...
-
+ No cards matching the search expression exists in the card database. Proceed anyways?
-
+ Cockatrice
-
+ Invalid filter
@@ -2062,12 +2496,12 @@ Your email will be used to verify your account.
DlgSettings
-
+ Unknown Error loading card database
-
+ Your card database is invalid.
Cockatrice may not function correctly with an invalid database
@@ -2078,7 +2512,7 @@ Would you like to change your database location setting?
-
+ Your card database version is too old.
This can cause problems loading card information or images
@@ -2089,7 +2523,7 @@ Would you like to change your database location setting?
-
+ Your card database did not finish loading
Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached
@@ -2098,21 +2532,21 @@ Would you like to change your database location setting?
-
+ File Error loading your card database.
Would you like to change your database location setting?
-
+ Your card database was loaded but contains no cards.
Would you like to change your database location setting?
-
+ Unknown card database load status
Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues
@@ -2121,59 +2555,59 @@ Would you like to change your database location setting?
-
-
+
+ Error
-
+ The path to your deck directory is invalid. Would you like to go back and set the correct path?
-
+ The path to your card pictures directory is invalid. Would you like to go back and set the correct path?
-
+ Settings
-
+ General
-
+ Appearance
-
+ User Interface
-
+ Card Sources
-
+ Chat
-
+ Sound
-
+ Shortcuts
@@ -2225,9 +2659,9 @@ Would you like to change your database location setting?
-
-
-
+
+
+ Error
@@ -2239,7 +2673,8 @@ Please visit the download page to update manually.
- Downloading update...
+ Downloading update: %1
+ Downloading update...
@@ -2314,49 +2749,52 @@ Please visit the download page to update manually.
- Unfortunately there are no download packages available for your operating system.
-You may have to build from source yourself.
+ Unfortunately, the automatic updater failed to find a compatible download.
+You may have to manually download the new version.
+ Unfortunately there are no download packages available for your operating system.
+You may have to build from source yourself.
- Please check the download page manually and visit the wiki for instructions on compiling.
+ Please check the <a href="%1">releases page</a> on our Github and download the build for your system.
+ Please check the download page manually and visit the wiki for instructions on compiling.
-
-
-
+
+
+ Update Error
-
+ An error occurred while checking for updates:
-
+ An error occurred while downloading an update:
-
+ Installing...
-
+ Cockatrice is unable to open the installer.
-
+ Try to update manually by closing Cockatrice and running the installer.
-
+ Download location
@@ -2621,97 +3059,102 @@ You may have to build from source yourself.
GeneralSettingsPage
-
+ Reset all paths
-
+ All paths have been reset
-
-
-
-
-
-
+
+
+
+
+
+ Choose path
-
+ Personal settings
-
+ Language:
-
+ Paths (editing disabled in portable mode)
-
+ Paths
-
-
- Decks directory:
-
-
-
-
- Replays directory:
-
-
-
-
- Pictures directory:
-
-
-
-
- Card database:
-
-
-
-
- Custom database directory:
-
-
- Token database:
+ How to help with translations
- Update channel
+ Decks directory:
- Check for client updates on startup
+ Replays directory:
- Notify if a feature supported by the server is missing in my client
+ Pictures directory:
- Automatically run Oracle when running a new version of Cockatrice
+ Card database:
+ Custom database directory:
+
+
+
+
+ Token database:
+
+
+
+
+ Update channel
+
+
+
+
+ Check for client updates on startup
+
+
+
+
+ Notify if a feature supported by the server is missing in my client
+
+
+
+
+ Automatically run Oracle when running a new version of Cockatrice
+
+
+
+ Show tips on startup
@@ -2919,7 +3362,7 @@ Will now login.
-
+ Error
@@ -3389,7 +3832,7 @@ Read more about changing the set order or disabling specific sets and consequent
-
+ Information
@@ -3405,41 +3848,42 @@ Read more about changing the set order or disabling specific sets and consequent
- failed to start.
+ Failed to start. The file might be missing, or permissions might be incorrect.
- crashed.
+ The process crashed some time after starting successfully.
-
- timed out.
+
+ Timed out. The process took too long to respond. The last waitFor...() function timed out.
-
- write error.
+
+ An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel.
-
- read error.
+
+ An error occurred when attempting to read from the process. For example, the process may not be running.
-
- unknown error.
+
+ Unknown error occurred.
-
- The card database updater exited with an error: %1
+
+ The card database updater exited with an error:
+%1
-
+ This server supports additional features that your client doesn't have.
This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version.
@@ -3447,54 +3891,54 @@ To update your client, go to Help -> Check for Updates.
-
-
-
-
-
+
+
+
+
+ Load sets/cards
-
+ Selected file cannot be found.
-
+ You can only import XML databases at this time.
-
+ The new sets/cards have been added successfully.
Cockatrice will now reload the card database.
-
+ Sets/cards failed to import.
-
-
-
+
+
+ Reset Password
-
+ Your password has been reset successfully, you can now log in using the new credentials.
-
+ Failed to reset user account password, please contact the server operator to reset your password.
-
+ Activation request received, please check your email for an activation token.
@@ -4110,110 +4554,110 @@ Cockatrice will now reload the card database.
MessagesSettingsPage
-
+ Word1 Word2 Word3
-
+ Add New Message
-
+ Edit Message
-
+ Remove Message
-
+ Add message
-
-
+
+ Message:
-
+ Edit message
-
+ Chat settings
-
+ Custom alert words
-
+ Enable chat mentions
-
+ Enable mention completer
-
+ In-game message macros
-
+ How to use in-game message macros
-
+ Ignore chat room messages sent by unregistered users
-
+ Ignore private messages sent by unregistered users
-
-
+
+ Invert text color
-
+ Enable desktop notifications for private messages
-
+ Enable desktop notification for mentions
-
+ Enable room message history on join
-
-
+
+ (Color is hexadecimal)
-
+ Separate words with a space, alphanumeric characters only
@@ -4422,639 +4866,669 @@ Cockatrice will now reload the card database.
Player
-
+ Reveal top cards of library
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ Number of cards: (max. %1)
-
+ &View graveyard
-
+ &View exile
-
+ Player "%1"
-
-
-
-
+
+
+
+ &Graveyard
-
-
+
-
+
+ &Exile
-
+ &Move hand to...
-
+ &Top of library
-
+ &Bottom of library
-
+ &Move graveyard to...
-
-
-
+
+
+ &Hand
-
+ &Move exile to...
-
+ &View library
-
+ &View hand
-
+ View &top cards of library...
-
+ Reveal &library to...
-
+ &Always reveal top card
-
+ &View sideboard
-
+ &Draw card
-
+ D&raw cards...
-
+ &Undo last draw
-
+ Take &mulligan
-
- &Shuffle
-
-
-
-
+ Play top card &face down
-
+ Move top card to grave&yard
-
+ Move top card to e&xile
-
+ Move top cards to &graveyard...
-
+ Move top cards to &exile...
-
+ Put top card on &bottom
-
+ View bottom cards of library...
-
+ Lend library to...
-
+
+ Shuffle
+
+
+
+ Put top cards on stack &until...Take top cards &until...
-
- &Reveal hand to...
+
+ Shuffle top cards...
-
- Reveal r&andom card to...
-
-
-
-
- Reveal random card to...
-
-
-
-
- &Sideboard
-
-
-
-
- &Library
-
-
-
-
- &Counters
-
-
-
-
- &Untap all permanents
-
-
-
-
- R&oll die...
+
+ Shuffle bottom cards...
- &Create token...
+ &Reveal hand to...
- C&reate another token
+ Reveal r&andom card to...
- Cr&eate predefined token
+ Reveal random card to...
+
+
+
+
+ &Sideboard
+
+
+
+
+ &Library
+
+
+
+
+ &Counters
+
+
+
+
+ &Untap all permanents
+
+
+
+
+ R&oll die...
+
+
+
+
+ &Create token...
-
+ C&reate another token
+
+
+
+
+ Cr&eate predefined token
+
+
+
+
+ &All players
-
+ S&ay
-
+ &Select All
-
+ S&elect Row
-
+ S&elect Column
-
+ &Play
-
+ &Hide
-
+ Play &Face Down
-
+ &Tap / UntapTurn sideways or back again
-
+ Toggle &normal untapping
-
+ T&urn OverTurn face up/face down
-
+ &Peek at card face
-
+ &Clone
-
+ Attac&h to card...
-
+ Unattac&h
-
+ &Draw arrow...
-
+ &Increase power
-
+ &Decrease power
-
+ I&ncrease toughness
-
+ D&ecrease toughness
-
+ In&crease power and toughness
-
+ Dec&rease power and toughness
-
+ Increase power and decrease toughness
-
+ Decrease power and increase toughness
-
+ Set &power and toughness...
-
+ Reset p&ower and toughness
-
+ &Set annotation...
-
+ Red
-
+ Yellow
-
+ Green
-
+ &Add counter (%1)
-
+ &Remove counter (%1)
-
+ &Set counters (%1)...
-
+ &Top of library in random order
-
+ X cards from the top of library...
-
+ &Bottom of library in random order
-
+ View top cards of library
-
+ View bottom cards of library
-
+
+ Shuffle top cards of library
+
+
+
+
+ Shuffle bottom cards of library
+
+
+
+ Which position should this card be placed:
-
+ (max. %1)
-
+ Draw hand
-
+ 0 and lower are in comparison to current hand size
-
+ Draw cards
-
-
+
+ Number:
-
+ Move top cards to grave
-
+ Reveal &top cards to...
-
+ &Top of library...
-
+ &Bottom of library...
-
+ &Always look at top card
-
+ &Open deck in deck editor
-
+ &Play top card
-
+ &Draw bottom card
-
+ D&raw bottom cards...
-
+ &Play bottom card
-
+ Play bottom card &face down
-
+ Move bottom card to grave&yard
-
+ Move bottom card to e&xile
-
+ Move bottom cards to &graveyard...
-
+ Move bottom cards to &exile...
-
+ Put bottom card on &top
-
+ Selec&ted cards
-
+ Move top cards to exile
-
+ Move bottom cards to grave
-
+ Move bottom cards to exile
-
+ Draw bottom cards
-
-
+
+ C&reate another %1 token
-
+ Create tokens
-
+ Place card X cards from top of library
-
+ Change power/toughness
-
+ Change stats to:
-
+ Set annotation
-
+ Please enter the new annotation:
-
+ Set counters
-
+ Re&veal to...
-
+ View related cards
-
+ Token:
-
+ All tokens
+
+ PrintingSelector
+
+
+ Display Navigation Buttons
+
+
+PrintingSelectorCardOverlayWidget
@@ -5117,48 +5591,17 @@ Cockatrice will now reload the card database.
-
-
+
+ Descending
-
+ Ascending
-
- PrintingSelectorViewOptionsToolbarWidget
-
-
- Display Options
-
-
-
-
- PrintingSelectorViewOptionsWidget
-
-
- Display Sorting Options
-
-
-
-
- Display Search Bar
-
-
-
-
- Display Card Size Slider
-
-
-
-
- Display Navigation Buttons
-
-
-QMenuBar
@@ -5206,7 +5649,6 @@ Cockatrice will now reload the card database.
- All files (*.*)
@@ -5215,11 +5657,6 @@ Cockatrice will now reload the card database.
Cockatrice replays (*.cor)
-
-
- Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck)
-
- Maindeck
@@ -5482,53 +5919,53 @@ Cockatrice will now reload the card database.
ShortcutSettingsPage
-
-
+
+ Restore all default shortcuts
-
+ Do you really want to restore all default shortcuts?
-
+ Clear all default shortcuts
-
+ Do you really want to clear all shortcuts?
-
+ Section:
-
+ Action:
-
+ Shortcut:
-
+ How to set custom shortcuts
-
+ Clear all shortcuts
-
+ Search by shortcut name
@@ -5582,27 +6019,27 @@ Please check your shortcut settings!
SoundSettingsPage
-
+ Enable &sounds
-
+ Current sounds theme:
-
+ Test system sound engine
-
+ Sound settings
-
+ Master volume
@@ -5659,27 +6096,27 @@ 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.
@@ -5818,430 +6255,192 @@ Please check your shortcut settings!
TabDeckEditor
-
- Banner Card
-
-
-
-
- Search by card name (or search expressions)
- Search by card name
-
-
-
-
- Add to Deck
-
-
-
-
- Add to Sideboard
-
-
-
-
-
- Select Printing
-
-
-
-
- Show on EDHREC (Commander)
-
-
-
-
- Show on EDHREC (Card)
-
-
-
-
- Show Related cards
-
-
-
-
- &Clear all filters
-
-
-
-
- Delete selected
-
-
-
-
- Deck &name:
-
-
-
-
- &Comments:
-
-
-
-
- Hash:
-
-
-
-
- &New deck
-
-
-
-
- &Load deck...
-
-
-
-
- Load recent deck...
-
-
-
-
- Clear
-
-
-
-
- &Save deck
-
-
-
-
- Save deck &as...
-
-
-
-
- Load deck from cl&ipboard...
-
-
-
-
- Save deck to clipboard
-
-
-
-
- Annotated
-
-
-
-
- Annotated (No set name or number)
-
-
-
-
- Not Annotated
-
-
-
-
- Not Annotated (No set name or number)
-
-
-
-
- &Print deck...
-
-
-
-
- &Send deck to online service
-
-
-
-
- Create decklist (decklist.org)
-
-
-
-
- Analyze deck (deckstats.net)
-
-
-
-
- Analyze deck (tappedout.net)
-
-
-
-
- &Close
-
-
-
-
- Add card to &maindeck
-
-
-
-
- Add card to &sideboard
-
-
-
-
- &Remove row
-
-
-
-
- &Increment number
-
-
-
-
- &Decrement number
-
-
-
-
- Swap card to/from sideboard
-
-
-
-
- &Deck Editor
-
-
-
-
-
+ Card Info
-
-
+ Deck
-
-
+ Filters
-
- Printing Selector
-
-
-
-
+ &View
-
+ Printing
-
-
-
-
+
+
+
+ Visible
-
-
-
-
+
+
+
+ Floating
-
+ Reset layout
-
+ Deck: %1
-
-
- Could not open deck at %1
-
-
-
-
- Open in new tab
-
-
-
-
- Are you sure?
-
-
-
-
- The decklist has been modified.
-Do you want to save the changes?
-
-
-
-
-
-
-
-
-
-
- Error
-
-
-
-
- The deck could not be saved.
-
-
-
-
- Could not save remote deck
-
-
-
-
-
- The deck could not be saved.
-Please check that the directory is writable and try again.
-
-
-
-
- Save deck
-
-
-
-
- There are no cards in your deck to be exported
-
-
-
-
- No deck was selected to be saved.
-
- TabDeckStorage
-
+ Local file system
-
+ Server deck storage
-
-
+
+ Open in deck editor
-
- Upload deck
-
-
-
-
- Download deck
-
-
-
-
-
-
- New folder
+ Rename deck or folder
-
- Delete
+ Upload deck
+ Download deck
+
+
+
+
+
+
+
+ New folder
+
+
+
+
+
+ Delete
+
+
+
+ Open decks folder
-
-
-
+
+ Rename local folder
+
+
+
+
+ Rename local file
+
+
+
+
+ New name:
+
+
+
+
+
+
+ Error
-
-
+
+ Rename failed
+
+
+
+
+ Invalid deck file
-
+ Enter deck name
-
+ This decklist does not have a name.
Please enter a name:
-
+ Unnamed deck
-
+ Failed to upload deck to server
-
+ Delete local file
-
+ Are you sure you want to delete the selected files?
-
+ Delete remote decks
-
+ Are you sure you want to delete the selected decks?
-
-
+
+ Name of new folder:
-
+ Deck Storage
@@ -6253,6 +6452,16 @@ Please enter a name:
Visual Deck Storage
+
+
+ Error
+
+
+
+
+ Could not open deck at %1
+
+ TabEdhRec
@@ -6265,175 +6474,175 @@ Please enter a name:
TabGame
-
-
-
+
+
+ Replay
-
-
+
+ Game
-
-
+
+ Player List
-
-
+
+ Card Info
-
-
+
+ Messages
-
-
+
+ Replay Timeline
-
+ &Phases
-
+ &Game
-
+ Next &phase
-
+ Next phase with &action
-
+ Next &turn
-
+ Reverse turn order
-
+ &Remove all local arrows
-
+ Rotate View Cl&ockwise
-
+ Rotate View Co&unterclockwise
-
+ Game &information
-
+ Un&concede
-
+ &Concede
-
+ &Leave game
-
+ C&lose replay
-
+ &Focus Chat
-
+ &Say:
-
+ &View
-
-
-
-
+
+
+
+ Visible
-
-
-
-
+
+
+
+ Floating
-
+ Reset layout
-
+ Concede
-
+ Are you sure you want to concede this game?
-
+ Unconcede
-
+ You have already conceded. Do you want to return to this game?
-
+ Leave game
-
+ Are you sure you want to leave this game?
@@ -6443,37 +6652,37 @@ Please enter a name:
-
+ A player has joined game #%1
-
+ %1 has joined the game
-
+ kicked by game host or moderator
-
+ player left the game
-
+ player disconnected from server
-
+ reason unknown
-
+ You have been kicked out of the game.
@@ -6907,32 +7116,32 @@ The more information you put in, the more specific your results will be.
-
+ Are you sure?
-
+ There are still open games. Are you sure you want to quit?
-
+ Click to view
-
+ Your buddy %1 has signed on!
-
+ Unknown Event
-
+ The server has sent you a message that your client does not understand.
This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version.
@@ -6940,38 +7149,38 @@ To update your client, go to Help -> Check for Updates.
-
+ Idle Timeout
-
+ You are about to be logged out due to inactivity.
-
+ Promotion
-
+ You have been promoted. Please log out and back in for changes to take effect.
-
+ Warned
-
+ You have received a warning due to %1.
Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator.
-
+ You have received the following message from the server.
(custom messages like these could be untranslated)
@@ -7425,102 +7634,102 @@ Please refrain from engaging in this activity or further actions may be taken ag
UserInterfaceSettingsPage
-
+ General interface settings
-
+ &Double-click cards to play them (instead of single-click)
-
+ &Clicking plays all selected cards (instead of just the clicked card)
-
+ &Play all nonlands onto the stack (not the battlefield) by default
-
+ Close card view window when last card is removed
-
+ Annotate card text on tokens
-
+ Use tear-off menus, allowing right click menus to persist on screen
-
+ Notifications settings
-
+ Enable notifications in taskbar
-
+ Notify in the taskbar for game events while you are spectating
-
+ Notify in the taskbar when users in your buddy list connect
-
+ Animation settings
-
+ &Tap/untap animation
-
+ Deck editor/storage settings
-
+ Open deck in new tab by default
-
+ Prompt before converting .txt decks to .cod format
-
+ Always convert if not prompted
-
+ Use visual deck storage in game lobby
-
+ Replay settings
-
+ Buffer time for backwards skip via shortcut:
@@ -7528,22 +7737,22 @@ Please refrain from engaging in this activity or further actions may be taken ag
UserListWidget
-
+ Users connected to server: %1
-
+ Users in this room: %1
-
+ Buddies online: %1 / %2
-
+ Ignored users online: %1 / %2
@@ -7551,7 +7760,7 @@ Please refrain from engaging in this activity or further actions may be taken ag
VisualDeckStorageFolderDisplayWidget
-
+ Deck Storage
@@ -7559,7 +7768,7 @@ Please refrain from engaging in this activity or further actions may be taken ag
VisualDeckStorageSearchWidget
-
+ Search by filename
@@ -7590,10 +7799,45 @@ Please refrain from engaging in this activity or further actions may be taken ag
VisualDeckStorageWidget
-
+ Loading database ...
+
+
+ Show Folders
+
+
+
+
+ Show Tag Filter
+
+
+
+
+ Show Tags On Deck Previews
+
+
+
+
+ Show Banner Card Selection Option
+
+
+
+
+ Include Folder Names in Search
+
+
+
+
+ Draw unused Color Identities
+
+
+
+
+ Unused Color Identities Opacity
+
+ WarningDialog
@@ -7642,128 +7886,133 @@ Please refrain from engaging in this activity or further actions may be taken ag
WndSets
-
+ Move selected set to the top
-
+ Move selected set up
-
+ Move selected set down
-
+ Move selected set to the bottom
-
+ Search by set name, code, or type
-
+ Default order
-
+ Restore original art priority order
-
+ Enable all sets
-
+ Disable all sets
-
+ Enable selected set(s)
-
+ Disable selected set(s)
-
+ Deck Editor
-
+ Use CTRL+A to select all sets in the view.
-
+ Only cards in enabled sets will appear in the card list of the deck editor.
-
+ Image priority is decided in the following order:
-
+ first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom)%1 is a link to the wiki
-
+ Card Art
-
+ How to use custom card art
-
+ Hints
-
+ Note
-
+ Sorting by column allows you to find a set while not changing set priority.
-
+ To enable ordering again, click the column header until this message disappears.
-
+ Use the current sorting as the set priority instead
-
+ Sorts the set priority using the same column
-
+
+ Include online-only (Arena) cards [requires restart]
+
+
+
+ Manage sets
@@ -7847,12 +8096,12 @@ Please refrain from engaging in this activity or further actions may be taken ag
main
-
+ Connect on startup
-
+ Debug to file
@@ -7866,7 +8115,7 @@ Please refrain from engaging in this activity or further actions may be taken ag
-
+ Deck Editor
@@ -7947,7 +8196,7 @@ Please refrain from engaging in this activity or further actions may be taken ag
-
+ Replays
@@ -8003,722 +8252,763 @@ Please refrain from engaging in this activity or further actions may be taken ag
- Analyze Deck
+ Analyze Deck (deckstats.net)
+ Analyze Deck
-
+
+ Analyze Deck (tappedout.net)
+
+
+
+ Clear All Filters
-
+ Clear Selected Filter
-
+ Close
-
+ Remove Card
-
+ Manage Sets...
-
+ Edit Custom Tokens...
-
- Export Deck
+
+ Export Deck (decklist.org)
-
+
+ Export Deck (decklist.xyz)
+
+
+
+ Add Card
-
+ Load Deck...
-
+ Load Deck from Clipboard...
-
+
+ Edit Deck in Clipboard, Annotated
+
+
+
+
+ Edit Deck in Clipboard
+
+
+
+ New Deck
-
+ Open Custom Pictures Folder
-
+ Print Deck...
-
+ Delete Card
-
-
+
+ Reset Layout
-
+ Save Deck
-
+ Save Deck as...
-
+ Save Deck to Clipboard, Annotated
-
+
+ Save Deck to Clipboard, Annotated (No Set Info)
+
+
+
+ Save Deck to Clipboard
-
-
- 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
+ Save Deck to Clipboard (No Set Info)
- Remove Yellow Counter
+ Load Local Deck...
- Set Yellow Counters...
+ 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
-
+ Set White Counters...
-
+ Add Blue Counter
-
+ Remove Blue Counter
-
+ Set Blue Counters...
-
+ Add Black Counter
-
+ Remove Black Counter
-
+ Set Black Counters...
-
+ Add Colorless Counter
-
+ Remove Colorless Counter
-
+ Set Colorless Counters...
-
+ Add Other Counter
-
+ Remove Other Counter
-
+ Set Other Counters...
-
+ Add Power (+1/+0)
-
+ Remove Power (-1/-0)
-
+ Move Toughness to Power (+1/-1)
-
+ Add Toughness (+0/+1)
-
+ Remove Toughness (-0/-1)
-
+ Move Power to Toughness (-1/+1)
-
+ Add Power and Toughness (+1/+1)
-
+ Remove Power and Toughness (-1/-1)
-
+ Set Power and Toughness...
-
+ Reset Power and Toughness
-
+ Untap
-
+ Upkeep
-
+ Draw
-
+ First Main Phase
-
+ Start Combat
-
+ Attack
-
+ Block
-
+ Damage
-
+ End Combat
-
+ Second Main Phase
-
+ End
-
+ Next Phase
-
+ Next Phase Action
-
+ Next Turn
-
+ Hide Card in Reveal Window
-
+ Tap / Untap Card
-
+ Untap All
-
+ Toggle Untap
-
+ Turn Card Over
-
+ Peek Card
-
+ Play Card
-
+ Attach Card...
-
+ Unattach Card
-
+ Clone Card
-
+ Create Token...
-
+ Create All Related Tokens
-
+ Create Another Token
-
+ Set Annotation...
-
+ Select All Cards in Zone
-
+ Select All Cards in Row
-
+ Select All Cards in Column
-
-
+
+ Bottom of Library
-
-
-
-
+
+
+
+ Exile
-
-
-
-
+
+
+
+ Graveyard
-
-
+
+ Hand
-
-
+
+ Top of Library
-
-
-
+
+
+ Battlefield, Face Down
-
+ Battlefield
-
+ Library
-
+ Sideboard
-
+ Top Cards of Library
-
+ Bottom Cards of Library
-
+ Close Recent View
-
-
+
+ Stack
-
-
+
+ Graveyard (Multiple)
-
-
+
+ Exile (Multiple)
-
+ Stack Until Found
-
+ Draw Bottom Card
-
+ Draw Multiple Cards from Bottom...
-
+ Draw Arrow...
-
+ Remove Local Arrows
-
+ Leave Game
-
+ Concede
-
+ Roll Dice...
-
+ Shuffle Library
-
+
+ Shuffle Top Cards of Library
+
+
+
+
+ Shuffle Bottom Cards of Library
+
+
+
+ Mulligan
-
+ Draw a Card
-
+ Draw Multiple Cards...
-
+ Undo Draw
-
+ Always Reveal Top Card
-
+ Always Look At Top Card
-
+ Rotate View Clockwise
-
+ Rotate View Counterclockwise
-
+ Unfocus Text Box
-
+ Focus Chat
-
+ Clear Chat
-
+ Refresh
-
+ Skip Forward
-
+ Skip Backward
-
+ Skip Forward by a lot
-
+ Skip Backward by a lot
-
+ Play/Pause
-
+ Toggle Fast Forward
-
+ Visual Deck Storage
-
+ Deck Storage
-
+ Server
-
+ Account
-
+ Administration
-
+ Logs
diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini
index b141cbd00..60a123d5e 100644
--- a/cockatrice/resources/config/qtlogging.ini
+++ b/cockatrice/resources/config/qtlogging.ini
@@ -44,14 +44,13 @@
# cockatrice_xml.* = false
# cockatrice_xml.xml_3_parser = false
# cockatrice_xml.xml_4_parser = false
+# card_info = false
# card_list = false
-# stack_zone = false
-
flow_layout.debug = false
flow_widget.debug = false
flow_widget.size.debug = false
# pixel_map_generator = false
-# filter_string = false
\ No newline at end of file
+# filter_string = 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 73c76c807..2d8c613a5 100644
--- a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp
+++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp
@@ -55,6 +55,9 @@ DeckEditorMenu::DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *_deckEdit
aExportDeckDecklist = new QAction(QString(), this);
connect(aExportDeckDecklist, SIGNAL(triggered()), deckEditor, SLOT(actExportDeckDecklist()));
+ aExportDeckDecklistXyz = new QAction(QString(), this);
+ connect(aExportDeckDecklistXyz, SIGNAL(triggered()), deckEditor, SLOT(actExportDeckDecklistXyz()));
+
aAnalyzeDeckDeckstats = new QAction(QString(), this);
connect(aAnalyzeDeckDeckstats, SIGNAL(triggered()), deckEditor, SLOT(actAnalyzeDeckDeckstats()));
@@ -63,6 +66,8 @@ DeckEditorMenu::DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *_deckEdit
analyzeDeckMenu = new QMenu(this);
analyzeDeckMenu->addAction(aExportDeckDecklist);
+ analyzeDeckMenu->addAction(aExportDeckDecklistXyz);
+ analyzeDeckMenu->addSeparator();
analyzeDeckMenu->addAction(aAnalyzeDeckDeckstats);
analyzeDeckMenu->addAction(aAnalyzeDeckTappedout);
@@ -160,6 +165,7 @@ void DeckEditorMenu::retranslateUi()
analyzeDeckMenu->setTitle(tr("&Send deck to online service"));
aExportDeckDecklist->setText(tr("Create decklist (decklist.org)"));
+ aExportDeckDecklistXyz->setText(tr("Create decklist (decklist.xyz)"));
aAnalyzeDeckDeckstats->setText(tr("Analyze deck (deckstats.net)"));
aAnalyzeDeckTappedout->setText(tr("Analyze deck (tappedout.net)"));
@@ -172,13 +178,17 @@ void DeckEditorMenu::refreshShortcuts()
aNewDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aNewDeck"));
aLoadDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeck"));
aSaveDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeck"));
- aExportDeckDecklist->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklist"));
aSaveDeckAs->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckAs"));
aLoadDeckFromClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeckFromClipboard"));
aEditDeckInClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aEditDeckInClipboard"));
aEditDeckInClipboardRaw->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aEditDeckInClipboardRaw"));
aPrintDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aPrintDeck"));
+
+ aExportDeckDecklist->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklist"));
+ aExportDeckDecklistXyz->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklistXyz"));
aAnalyzeDeckDeckstats->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeck"));
+ aAnalyzeDeckTappedout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeckTappedout"));
+
aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose"));
aSaveDeckToClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboard"));
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 50ddc795a..a713b009f 100644
--- a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h
+++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h
@@ -17,7 +17,7 @@ public:
QAction *aNewDeck, *aLoadDeck, *aClearRecents, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard,
*aEditDeckInClipboard, *aEditDeckInClipboardRaw, *aSaveDeckToClipboard, *aSaveDeckToClipboardNoSetInfo,
*aSaveDeckToClipboardRaw, *aSaveDeckToClipboardRawNoSetInfo, *aPrintDeck, *aExportDeckDecklist,
- *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose;
+ *aExportDeckDecklistXyz, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose;
QMenu *loadRecentDeckMenu, *analyzeDeckMenu, *editDeckInClipboardMenu, *saveDeckToClipboardMenu;
void setSaveStatus(bool newStatus);
diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp
index 326e722c9..fe2148293 100644
--- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp
+++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp
@@ -355,8 +355,8 @@ bool AbstractTabDeckEditor::actSaveDeckAs()
dialog.setDirectory(SettingsCache::instance().getDeckPath());
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setDefaultSuffix("cod");
- dialog.setNameFilters(DeckLoader::fileNameFilters);
- dialog.selectFile(getDeckList()->getName().trimmed() + ".cod");
+ dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS);
+ dialog.selectFile(getDeckList()->getName().trimmed());
if (!dialog.exec())
return false;
@@ -455,17 +455,12 @@ void AbstractTabDeckEditor::actPrintDeck()
dlg->exec();
}
-// Action called when export deck to decklist menu item is pressed.
-void AbstractTabDeckEditor::actExportDeckDecklist()
+void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
{
- // Get the decklist class for the deck.
- DeckLoader *const deck = getDeckList();
- // create a string to load the decklist url into.
- QString decklistUrlString;
// check if deck is not null
- if (deck) {
+ if (DeckLoader *const deck = getDeckList()) {
// Get the decklist url string from the deck loader class.
- decklistUrlString = deck->exportDeckToDecklist();
+ QString decklistUrlString = deck->exportDeckToDecklist(website);
// Check to make sure the string isn't empty.
if (QString::compare(decklistUrlString, "", Qt::CaseInsensitive) == 0) {
// Show an error if the deck is empty, and return.
@@ -481,10 +476,26 @@ void AbstractTabDeckEditor::actExportDeckDecklist()
QDesktopServices::openUrl(decklistUrlString);
} else {
// if there's no deck loader object, return an error
- QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be saved."));
+ QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be exported."));
}
}
+/**
+ * Exports the deck to www.decklist.org (the old website)
+ */
+void AbstractTabDeckEditor::actExportDeckDecklist()
+{
+ exportToDecklistWebsite(DeckLoader::DecklistOrg);
+}
+
+/**
+ * Exports the deck to www.decklist.xyz (the new website)
+ */
+void AbstractTabDeckEditor::actExportDeckDecklistXyz()
+{
+ exportToDecklistWebsite(DeckLoader::DecklistXyz);
+}
+
void AbstractTabDeckEditor::actAnalyzeDeckDeckstats()
{
auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(),
diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h
index 47e6fc78c..0a5f97f56 100644
--- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h
+++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h
@@ -100,6 +100,7 @@ protected slots:
void actSaveDeckToClipboardRawNoSetInfo();
void actPrintDeck();
void actExportDeckDecklist();
+ void actExportDeckDecklistXyz();
void actAnalyzeDeckDeckstats();
void actAnalyzeDeckTappedout();
@@ -119,6 +120,7 @@ protected slots:
private:
virtual void setDeck(DeckLoader *_deck);
void editDeckInClipboard(bool annotated);
+ void exportToDecklistWebsite(DeckLoader::DecklistWebsite website);
protected:
/**
@@ -147,7 +149,7 @@ protected:
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating;
QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating;
- bool modified;
+ bool modified = false;
};
#endif // TAB_GENERIC_DECK_EDITOR_H
diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp
index e3e0bb3f7..4fb53d0d2 100644
--- a/cockatrice/src/client/tabs/tab_game.cpp
+++ b/cockatrice/src/client/tabs/tab_game.cpp
@@ -119,7 +119,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
refreshShortcuts();
messageLog->logReplayStarted(gameInfo.game_id());
- this->installEventFilter(this);
QTimer::singleShot(0, this, SLOT(loadLayout()));
}
@@ -164,7 +163,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
for (int i = gameInfo.game_types_size() - 1; i >= 0; i--)
gameTypes.append(roomGameTypes.find(gameInfo.game_types(i)).value());
- this->installEventFilter(this);
QTimer::singleShot(0, this, SLOT(loadLayout()));
}
@@ -1753,6 +1751,27 @@ void TabGame::createMessageDock(bool bReplay)
connect(messageLayoutDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool)));
}
+void TabGame::hideEvent(QHideEvent *event)
+{
+ LayoutsSettings &layouts = SettingsCache::instance().layouts();
+ if (replay) {
+ layouts.setReplayPlayAreaState(saveState());
+ layouts.setReplayPlayAreaGeometry(saveGeometry());
+ layouts.setReplayCardInfoSize(cardInfoDock->size());
+ layouts.setReplayMessageLayoutSize(messageLayoutDock->size());
+ layouts.setReplayPlayerListSize(playerListDock->size());
+ layouts.setReplayReplaySize(replayDock->size());
+ } else {
+ layouts.setGamePlayAreaState(saveState());
+ layouts.setGamePlayAreaGeometry(saveGeometry());
+ layouts.setGameCardInfoSize(cardInfoDock->size());
+ layouts.setGameMessageLayoutSize(messageLayoutDock->size());
+ layouts.setGamePlayerListSize(playerListDock->size());
+ }
+
+ Tab::hideEvent(event);
+}
+
// Method uses to sync docks state with menu items state
bool TabGame::eventFilter(QObject *o, QEvent *e)
{
@@ -1772,23 +1791,6 @@ bool TabGame::eventFilter(QObject *o, QEvent *e)
}
}
- if (o == this && e->type() == QEvent::Hide) {
- LayoutsSettings &layouts = SettingsCache::instance().layouts();
- if (replay) {
- layouts.setReplayPlayAreaState(saveState());
- layouts.setReplayPlayAreaGeometry(saveGeometry());
- layouts.setReplayCardInfoSize(cardInfoDock->size());
- layouts.setReplayMessageLayoutSize(messageLayoutDock->size());
- layouts.setReplayPlayerListSize(playerListDock->size());
- layouts.setReplayReplaySize(replayDock->size());
- } else {
- layouts.setGamePlayAreaState(saveState());
- layouts.setGamePlayAreaGeometry(saveGeometry());
- layouts.setGameCardInfoSize(cardInfoDock->size());
- layouts.setGameMessageLayoutSize(messageLayoutDock->size());
- layouts.setGamePlayerListSize(playerListDock->size());
- }
- }
return false;
}
diff --git a/cockatrice/src/client/tabs/tab_game.h b/cockatrice/src/client/tabs/tab_game.h
index 984ed4b0d..096998728 100644
--- a/cockatrice/src/client/tabs/tab_game.h
+++ b/cockatrice/src/client/tabs/tab_game.h
@@ -208,6 +208,7 @@ private slots:
void actResetLayout();
void freeDocksSize();
+ void hideEvent(QHideEvent *event) override;
bool eventFilter(QObject *o, QEvent *e) override;
void dockVisibleTriggered();
void dockFloatingTriggered();
diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp
index e2fb234d7..632eae06d 100644
--- a/cockatrice/src/client/tabs/tab_supervisor.cpp
+++ b/cockatrice/src/client/tabs/tab_supervisor.cpp
@@ -232,22 +232,45 @@ void TabSupervisor::refreshShortcuts()
aTabLog->setShortcuts(shortcuts.getShortcut("Tabs/aTabLog"));
}
-bool TabSupervisor::closeRequest()
+void TabSupervisor::closeEvent(QCloseEvent *event)
{
+ // This will accept the event, which we may then override.
+ QTabWidget::closeEvent(event);
+
if (getGameCount()) {
if (QMessageBox::question(this, tr("Are you sure?"),
tr("There are still open games. Are you sure you want to quit?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
- return false;
+ event->ignore();
+ return;
}
}
for (AbstractTabDeckEditor *tab : deckEditorTabs) {
- if (!tab->confirmClose())
- return false;
+ if (!tab->confirmClose()) {
+ event->ignore();
+ }
}
- return true;
+ // Close the game tabs in order to make sure they store their layout.
+ QSet gameTabsToRemove;
+ for (auto it = gameTabs.begin(), end = gameTabs.end(); it != end; ++it) {
+ if (it.value()->close()) {
+ // Hotfix: the tab owns the `QMenu`s so they need to be cleared,
+ // otherwise we end up with use-after-free bugs.
+ if (it.value() == currentWidget()) {
+ emit setMenu();
+ }
+
+ gameTabsToRemove.insert(it.key());
+ } else {
+ event->ignore();
+ }
+ }
+
+ for (auto tabId : gameTabsToRemove) {
+ gameTabs.remove(tabId);
+ }
}
AbstractClient *TabSupervisor::getClient() const
diff --git a/cockatrice/src/client/tabs/tab_supervisor.h b/cockatrice/src/client/tabs/tab_supervisor.h
index 068128793..c8ae109d8 100644
--- a/cockatrice/src/client/tabs/tab_supervisor.h
+++ b/cockatrice/src/client/tabs/tab_supervisor.h
@@ -138,7 +138,7 @@ public:
return deckEditorTabs;
}
bool getAdminLocked() const;
- bool closeRequest();
+ void closeEvent(QCloseEvent *event) override;
bool switchToGameTabIfAlreadyExists(const int gameId);
static void actShowPopup(const QString &message);
signals:
@@ -192,4 +192,4 @@ private slots:
void processNotifyUserEvent(const Event_NotifyUser &event);
};
-#endif
\ No newline at end of file
+#endif
diff --git a/cockatrice/src/client/ui/pixel_map_generator.cpp b/cockatrice/src/client/ui/pixel_map_generator.cpp
index c695e1093..25bd67264 100644
--- a/cockatrice/src/client/ui/pixel_map_generator.cpp
+++ b/cockatrice/src/client/ui/pixel_map_generator.cpp
@@ -180,6 +180,12 @@ QMap CountryPixmapGenerator::pmCache;
* @param idName id that the tag has to match
* @param attrValue the value to update the attribute to
*/
+static void setAttrRecur(QDomElement &elem,
+ const QString &tagName,
+ const QString &attrName,
+ const QString &idName,
+ const QString &attrValue);
+
void setAttrRecur(QDomElement &elem,
const QString &tagName,
const QString &attrName,
diff --git a/cockatrice/src/client/ui/theme_manager.cpp b/cockatrice/src/client/ui/theme_manager.cpp
index 4decf3f40..3dff1b7ac 100644
--- a/cockatrice/src/client/ui/theme_manager.cpp
+++ b/cockatrice/src/client/ui/theme_manager.cpp
@@ -120,10 +120,10 @@ void ThemeManager::themeChangedSlot()
if (dirPath.isEmpty()) {
// set default values
QDir::setSearchPaths("theme", DEFAULT_RESOURCE_PATHS);
- handBgBrush = HANDZONE_BG_DEFAULT;
- tableBgBrush = TABLEZONE_BG_DEFAULT;
- playerBgBrush = PLAYERZONE_BG_DEFAULT;
- stackBgBrush = STACKZONE_BG_DEFAULT;
+ brushes[Role::Hand] = HANDZONE_BG_DEFAULT;
+ brushes[Role::Table] = TABLEZONE_BG_DEFAULT;
+ brushes[Role::Player] = PLAYERZONE_BG_DEFAULT;
+ brushes[Role::Stack] = STACKZONE_BG_DEFAULT;
} else {
// resources
QStringList resources;
@@ -132,73 +132,58 @@ void ThemeManager::themeChangedSlot()
// zones bg
dir.cd("zones");
- handBgBrush = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT);
- tableBgBrush = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT);
- playerBgBrush = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
- stackBgBrush = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
+ brushes[Role::Hand] = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT);
+ brushes[Role::Table] = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT);
+ brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
+ brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
+ }
+ for (auto &brushCache : brushesCache) {
+ brushCache.clear();
}
- tableBgBrushesCache.clear();
- stackBgBrushesCache.clear();
- playerBgBrushesCache.clear();
- handBgBrushesCache.clear();
QPixmapCache::clear();
emit themeChanged();
}
-QBrush ThemeManager::getExtraTableBgBrush(QString extraNumber, QBrush &fallbackBrush)
+static QString roleBgName(ThemeManager::Role role)
{
- QBrush returnBrush;
+ switch (role) {
+ case ThemeManager::Hand:
+ return HANDZONE_BG_NAME;
- if (!tableBgBrushesCache.contains(extraNumber.toInt())) {
- returnBrush = loadExtraBrush(TABLEZONE_BG_NAME + extraNumber, fallbackBrush);
- tableBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
- } else {
- returnBrush = tableBgBrushesCache.value(extraNumber.toInt());
+ case ThemeManager::Player:
+ return PLAYERZONE_BG_NAME;
+
+ case ThemeManager::Stack:
+ return STACKZONE_BG_NAME;
+
+ case ThemeManager::Table:
+ return TABLEZONE_BG_NAME;
+
+ default:
+ Q_ASSERT(false);
}
-
- return returnBrush;
}
-QBrush ThemeManager::getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush)
+QBrush &ThemeManager::getBgBrush(Role role)
{
- QBrush returnBrush;
-
- if (!stackBgBrushesCache.contains(extraNumber.toInt())) {
- returnBrush = loadExtraBrush(STACKZONE_BG_NAME + extraNumber, fallbackBrush);
- stackBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
- } else {
- returnBrush = stackBgBrushesCache.value(extraNumber.toInt());
- }
-
- return returnBrush;
+ return brushes[role];
}
-QBrush ThemeManager::getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush)
+QBrush ThemeManager::getExtraBgBrush(Role role, int zoneId)
{
- QBrush returnBrush;
-
- if (!playerBgBrushesCache.contains(extraNumber.toInt())) {
- returnBrush = loadExtraBrush(PLAYERZONE_BG_NAME + extraNumber, fallbackBrush);
- playerBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
- } else {
- returnBrush = playerBgBrushesCache.value(extraNumber.toInt());
+ if (zoneId <= 0) {
+ return getBgBrush(role);
}
- return returnBrush;
-}
+ QBrushMap &brushCache = brushesCache[role];
-QBrush ThemeManager::getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush)
-{
- QBrush returnBrush;
-
- if (!handBgBrushesCache.contains(extraNumber.toInt())) {
- returnBrush = loadExtraBrush(HANDZONE_BG_NAME + extraNumber, fallbackBrush);
- handBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
- } else {
- returnBrush = handBgBrushesCache.value(extraNumber.toInt());
+ if (!brushCache.contains(zoneId)) {
+ QBrush brush = loadExtraBrush(roleBgName(role) + QString::number(zoneId), getBgBrush(role));
+ brushCache.insert(zoneId, brush);
+ return brush;
}
- return returnBrush;
+ return brushCache.value(zoneId);
}
diff --git a/cockatrice/src/client/ui/theme_manager.h b/cockatrice/src/client/ui/theme_manager.h
index 2385f5c23..20854b961 100644
--- a/cockatrice/src/client/ui/theme_manager.h
+++ b/cockatrice/src/client/ui/theme_manager.h
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
inline Q_LOGGING_CATEGORY(ThemeManagerLog, "theme_manager");
@@ -22,13 +23,23 @@ class ThemeManager : public QObject
public:
ThemeManager(QObject *parent = nullptr);
+ enum Role
+ {
+ MinRole = 0,
+ Hand = MinRole,
+ Stack,
+ Table,
+ Player,
+ MaxRole = Player,
+ };
+
private:
- QBrush handBgBrush, stackBgBrush, tableBgBrush, playerBgBrush;
+ std::array brushes;
QStringMap availableThemes;
/*
Internal cache for multiple backgrounds
*/
- QBrushMap tableBgBrushesCache, stackBgBrushesCache, playerBgBrushesCache, handBgBrushesCache;
+ std::array brushesCache;
protected:
void ensureThemeDirectoryExists();
@@ -36,27 +47,10 @@ protected:
QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush);
public:
- QBrush &getHandBgBrush()
- {
- return handBgBrush;
- }
- QBrush &getStackBgBrush()
- {
- return stackBgBrush;
- }
- QBrush &getTableBgBrush()
- {
- return tableBgBrush;
- }
- QBrush &getPlayerBgBrush()
- {
- return playerBgBrush;
- }
QStringMap &getAvailableThemes();
- QBrush getExtraTableBgBrush(QString extraNumber, QBrush &fallbackBrush);
- QBrush getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush);
- QBrush getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush);
- QBrush getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush);
+
+ QBrush &getBgBrush(Role zone);
+ QBrush getExtraBgBrush(Role zone, int zoneId = 0);
protected slots:
void themeChangedSlot();
signals:
diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp
index c39d6656d..59388bfc3 100644
--- a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp
@@ -1,5 +1,6 @@
#include "color_identity_widget.h"
+#include "../../../../../settings/cache_settings.h"
#include "mana_symbol_widget.h"
#include
@@ -17,18 +18,21 @@ ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, CardInfoPtr _card) : Q
layout->setAlignment(Qt::AlignCenter); // Ensure icons are centered
setLayout(layout);
+ // Define the full WUBRG set (White, Blue, Black, Red, Green)
+ QString fullColorIdentity = "WUBRG";
+
if (card) {
- QString manaCost = card->getColors(); // Get mana cost string
+ manaCost = card->getColors(); // Get mana cost string
QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string
- for (const QString &symbol : symbols) {
- ManaSymbolWidget *manaSymbol = new ManaSymbolWidget(this, symbol, true);
- layout->addWidget(manaSymbol);
- }
+ populateManaSymbolWidgets();
}
+ connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this,
+ &ColorIdentityWidget::toggleUnusedVisibility);
}
-ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString manaCost) : QWidget(parent), card(nullptr)
+ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString _manaCost)
+ : QWidget(parent), card(nullptr), manaCost(_manaCost)
{
layout = new QHBoxLayout(this);
layout->setSpacing(5); // Small spacing between icons
@@ -36,14 +40,43 @@ ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString manaCost) : QW
layout->setAlignment(Qt::AlignCenter); // Ensure icons are centered
setLayout(layout);
+ populateManaSymbolWidgets();
+
+ connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this,
+ &ColorIdentityWidget::toggleUnusedVisibility);
+}
+
+void ColorIdentityWidget::populateManaSymbolWidgets()
+{
+ // Define the full WUBRG set (White, Blue, Black, Red, Green)
+ QString fullColorIdentity = "WUBRG";
QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string
- for (const QString &symbol : symbols) {
- ManaSymbolWidget *manaSymbol = new ManaSymbolWidget(this, symbol);
- layout->addWidget(manaSymbol);
+ if (SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities()) {
+ for (const QString symbol : fullColorIdentity) {
+ auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol));
+ layout->addWidget(manaSymbol);
+ }
+ } else {
+ for (const QString &symbol : symbols) {
+ auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol));
+ layout->addWidget(manaSymbol);
+ }
}
}
+void ColorIdentityWidget::toggleUnusedVisibility()
+{
+ if (layout != nullptr) {
+ QLayoutItem *item;
+ while ((item = layout->takeAt(0)) != nullptr) {
+ item->widget()->deleteLater(); // Delete the widget
+ delete item; // Delete the layout item
+ }
+ }
+ populateManaSymbolWidgets();
+}
+
void ColorIdentityWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
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 1e81e7c26..8518aa1a5 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
@@ -12,13 +12,17 @@ class ColorIdentityWidget : public QWidget
public:
explicit ColorIdentityWidget(QWidget *parent, CardInfoPtr card);
explicit ColorIdentityWidget(QWidget *parent, QString manaCost);
+ void populateManaSymbolWidgets();
QStringList parseColorIdentity(const QString &manaString);
+
public slots:
void resizeEvent(QResizeEvent *event) override;
+ void toggleUnusedVisibility();
private:
CardInfoPtr card;
+ QString manaCost;
QHBoxLayout *layout;
};
diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp
index a66c314a5..dd4d1c1f6 100644
--- a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp
@@ -1,5 +1,7 @@
#include "mana_symbol_widget.h"
+#include "../../../../../settings/cache_settings.h"
+
#include
ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isActive, bool _mayBeToggled)
@@ -13,6 +15,9 @@ ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isAct
opacityEffect = new QGraphicsOpacityEffect(this);
setGraphicsEffect(opacityEffect);
updateOpacity();
+
+ connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageUnusedColorIdentitiesOpacityChanged, this,
+ &ManaSymbolWidget::updateOpacity);
}
void ManaSymbolWidget::setColorActive(bool active)
@@ -26,7 +31,14 @@ void ManaSymbolWidget::setColorActive(bool active)
void ManaSymbolWidget::updateOpacity()
{
- qreal opacity = isActive ? 1.0 : 0.5;
+ qreal opacity;
+ if (mayBeToggled) {
+ // UI elements that users can click on shouldn't be transparent.
+ opacity = isActive ? 1.0 : 0.5;
+ } else {
+ // It's just for display, they can do whatever they want.
+ opacity = isActive ? 1.0 : SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity() / 100.0;
+ }
opacityEffect->setOpacity(opacity);
}
diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
index 31089e7c4..775390182 100644
--- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
@@ -1,5 +1,6 @@
#include "card_info_picture_enlarged_widget.h"
+#include "../../../../settings/cache_settings.h"
#include "../../picture_loader/picture_loader.h"
#include
@@ -17,6 +18,12 @@ CardInfoPictureEnlargedWidget::CardInfoPictureEnlargedWidget(QWidget *parent)
{
setWindowFlags(Qt::ToolTip); // Keeps this widget on top of everything
setAttribute(Qt::WA_TranslucentBackground);
+
+ connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
+ Q_UNUSED(_roundCardCorners);
+
+ update();
+ });
}
/**
@@ -79,7 +86,8 @@ void CardInfoPictureEnlargedWidget::paintEvent(QPaintEvent *event)
QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2};
// Define the radius for rounded corners
- qreal radius = 0.05 * scaledSize.width(); // Adjust the radius as needed for rounded corners
+ // Adjust the radius as needed for rounded corners
+ qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * scaledSize.width() : 0.;
QStylePainter painter(this);
// Fill the background with transparent color to ensure rounded corners are rendered properly
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 592766939..546568a73 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
@@ -3,7 +3,6 @@
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/cards/card_item.h"
#include "../../../../settings/cache_settings.h"
-#include "../../../tabs/tab_deck_editor.h"
#include "../../../tabs/tab_supervisor.h"
#include "../../picture_loader/picture_loader.h"
#include "../../window_main.h"
@@ -44,6 +43,12 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool hoverTo
hoverTimer = new QTimer(this);
hoverTimer->setSingleShot(true);
connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap);
+
+ connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
+ Q_UNUSED(_roundCardCorners);
+
+ update();
+ });
}
/**
@@ -186,7 +191,8 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event)
QRect targetRect{targetX, targetY, targetW, targetH};
// Compute rounded corner radius
- qreal radius = 0.05 * static_cast(targetRect.width()); // Ensure consistent rounding
+ // Ensure consistent rounding
+ qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * static_cast(targetRect.width()) : 0.;
// Draw the pixmap with rounded corners
QStylePainter painter(this);
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 1d6ac86bb..a89f73aed 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
@@ -105,6 +105,8 @@ void DeckEditorDeckDockWidget::createDeckDock()
auto *upperLayout = new QGridLayout;
upperLayout->setObjectName("upperLayout");
+ upperLayout->setContentsMargins(11, 11, 11, 0);
+
upperLayout->addWidget(nameLabel, 0, 0);
upperLayout->addWidget(nameEdit, 0, 1);
@@ -314,12 +316,17 @@ DeckLoader *DeckEditorDeckDockWidget::getDeckList()
return deckModel->getDeckList();
}
+/**
+ * Resets the tab to the state for a blank new tab.
+ */
void DeckEditorDeckDockWidget::cleanDeck()
{
deckModel->cleanList();
nameEdit->setText(QString());
commentsEdit->setText(QString());
hashLabel->setText(QString());
+ updateBannerCardComboBox();
+ deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
}
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index)
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 2f03ad656..c60b1733b 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
@@ -96,11 +96,11 @@ void DeckEditorFilterDockWidget::filterViewCustomContextMenu(const QPoint &point
action = menu.addAction(QString("delete"));
action->setData(point);
- connect(&menu, SIGNAL(triggered(QAction *)), this, SLOT(filterRemove(QAction *)));
+ connect(&menu, &QMenu::triggered, this, &DeckEditorFilterDockWidget::filterRemove);
menu.exec(filterView->mapToGlobal(point));
}
-void DeckEditorFilterDockWidget::filterRemove(QAction *action)
+void DeckEditorFilterDockWidget::filterRemove(const QAction *action)
{
QPoint point;
QModelIndex idx;
diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h
index 3113b2e24..9cd8d8d6f 100644
--- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h
+++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h
@@ -28,10 +28,9 @@ private:
KeySignals filterViewKeySignals;
QWidget *filterBox;
- void filterRemove(QAction *action);
-
private slots:
void filterViewCustomContextMenu(const QPoint &point);
+ void filterRemove(const QAction *action);
void actClearFilterAll();
void actClearFilterOne();
void refreshShortcuts();
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 60b4d5b26..97825ff21 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
@@ -14,22 +14,21 @@
#include
DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList)
- : QWidget(_parent), deckList(_deckList)
+ : QWidget(_parent), deckList(nullptr)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
// Create layout
auto *layout = new QHBoxLayout(this);
- layout->setContentsMargins(5, 5, 5, 5);
- layout->setSpacing(5);
+ layout->setContentsMargins(0, 0, 0, 0);
setFixedHeight(100);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
- if (deckList) {
- connectDeckList(deckList);
+ if (_deckList) {
+ connectDeckList(_deckList);
}
layout->addWidget(flowWidget);
@@ -37,10 +36,20 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par
void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList)
{
- flowWidget->clearLayout();
+ if (deckList) {
+ disconnect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags);
+ }
+
deckList = _deckList;
connect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags);
+ refreshTags();
+}
+
+void DeckPreviewDeckTagsDisplayWidget::refreshTags()
+{
+ flowWidget->clearLayout();
+
for (const QString &tag : deckList->getTags()) {
flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag));
}
@@ -51,16 +60,6 @@ void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList)
flowWidget->addWidget(tagAdditionWidget);
}
-void DeckPreviewDeckTagsDisplayWidget::refreshTags()
-{
- flowWidget->clearLayout();
- QStringList tags = deckList->getTags();
- for (const QString &tag : tags) {
- flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag));
- }
- flowWidget->addWidget(new DeckPreviewTagAdditionWidget(this, tr("Edit tags ...")));
-}
-
/**
* Gets the filepath of all files (no directories) in target directory and all subdirectories
*/
@@ -161,4 +160,4 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
}
}
}
-}
\ No newline at end of file
+}
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 3564a05a9..35f434bdf 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
@@ -298,11 +298,11 @@ QMenu *DeckPreviewWidget::createRightClickMenu()
connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(true, true); });
- connect(saveToClipboardMenu->addAction(tr("Annotated (No set name or number)")), &QAction::triggered, this,
+ connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(true, false); });
connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(false, true); });
- connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set name or number)")), &QAction::triggered, this,
+ connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(false, false); });
menu->addSeparator();
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
index b198d06bd..319244f85 100644
--- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
+++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
@@ -58,6 +58,12 @@ void VisualDeckStorageFolderDisplayWidget::refreshUi()
header->setText(bannerText);
}
+/**
+ * Gets all files in the directory that have an accepted decklist file extension
+ *
+ * @param filePath The directory to search through
+ * @param recursive Whether to search through subdirectories
+ */
static QStringList getAllFiles(const QString &filePath, bool recursive)
{
QStringList allFiles;
@@ -65,7 +71,7 @@ static QStringList getAllFiles(const QString &filePath, bool recursive)
// QDirIterator with QDir::Files ensures only files are listed (no directories)
auto flags =
recursive ? QDirIterator::Subdirectories | QDirIterator::FollowSymlinks : QDirIterator::NoIteratorFlags;
- QDirIterator it(filePath, QDir::Files, flags);
+ QDirIterator it(filePath, DeckLoader::ACCEPTED_FILE_EXTENSIONS, QDir::Files, flags);
while (it.hasNext()) {
allFiles << it.next(); // Add each file path to the list
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 8ac228410..a1c75644c 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
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(parent), folderWidget(nullptr)
@@ -40,6 +41,7 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
refreshButton->setFixedSize(32, 32);
connect(refreshButton, &QPushButton::clicked, this, &VisualDeckStorageWidget::refreshIfPossible);
+ // quick settings menu
showFoldersCheckBox = new QCheckBox(this);
showFoldersCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowFolders());
connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &VisualDeckStorageWidget::updateShowFolders);
@@ -77,6 +79,29 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageSearchFolderNames);
+ // color identity opacity selector
+ auto unusedColorIdentityOpacityWidget = new QWidget(this);
+
+ unusedColorIdentitiesOpacityLabel = new QLabel(unusedColorIdentityOpacityWidget);
+ unusedColorIdentitiesOpacitySpinBox = new QSpinBox(unusedColorIdentityOpacityWidget);
+
+ unusedColorIdentitiesOpacitySpinBox->setMinimum(0);
+ unusedColorIdentitiesOpacitySpinBox->setMaximum(100);
+ unusedColorIdentitiesOpacitySpinBox->setValue(
+ SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity());
+ connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged),
+ &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
+
+ unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox);
+
+ unusedColorIdentitiesOpacitySpinBox->setValue(
+ SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity());
+
+ auto unusedColorIdentityOpacityLayout = new QHBoxLayout(unusedColorIdentityOpacityWidget);
+ unusedColorIdentityOpacityLayout->setContentsMargins(11, 0, 11, 0);
+ unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacityLabel);
+ unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacitySpinBox);
+
// card size slider
cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckStorageCardSize());
@@ -84,9 +109,10 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
quickSettingsWidget->addSettingsWidget(showFoldersCheckBox);
quickSettingsWidget->addSettingsWidget(tagFilterVisibilityCheckBox);
quickSettingsWidget->addSettingsWidget(tagsOnWidgetsVisibilityCheckBox);
- quickSettingsWidget->addSettingsWidget(drawUnusedColorIdentitiesCheckBox);
quickSettingsWidget->addSettingsWidget(bannerCardComboBoxVisibilityCheckBox);
quickSettingsWidget->addSettingsWidget(searchFolderNamesCheckBox);
+ quickSettingsWidget->addSettingsWidget(drawUnusedColorIdentitiesCheckBox);
+ quickSettingsWidget->addSettingsWidget(unusedColorIdentityOpacityWidget);
quickSettingsWidget->addSettingsWidget(cardSizeWidget);
searchAndSortLayout->addWidget(deckPreviewColorIdentityFilterWidget);
@@ -159,9 +185,11 @@ void VisualDeckStorageWidget::retranslateUi()
showFoldersCheckBox->setText(tr("Show Folders"));
tagFilterVisibilityCheckBox->setText(tr("Show Tag Filter"));
tagsOnWidgetsVisibilityCheckBox->setText(tr("Show Tags On Deck Previews"));
- drawUnusedColorIdentitiesCheckBox->setText(tr("Draw not contained Color Identities"));
bannerCardComboBoxVisibilityCheckBox->setText(tr("Show Banner Card Selection Option"));
searchFolderNamesCheckBox->setText(tr("Include Folder Names in Search"));
+ drawUnusedColorIdentitiesCheckBox->setText(tr("Draw unused Color Identities"));
+ unusedColorIdentitiesOpacityLabel->setText(tr("Unused Color Identities Opacity"));
+ unusedColorIdentitiesOpacitySpinBox->setSuffix("%");
}
/**
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 adfff06cb..acab16710 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
@@ -15,6 +15,7 @@
#include
#include
+class QSpinBox;
class VisualDeckStorageSearchWidget;
class VisualDeckStorageSortWidget;
class VisualDeckStorageTagFilterWidget;
@@ -64,6 +65,8 @@ private:
QCheckBox *tagFilterVisibilityCheckBox;
QCheckBox *tagsOnWidgetsVisibilityCheckBox;
QCheckBox *searchFolderNamesCheckBox;
+ QLabel *unusedColorIdentitiesOpacityLabel;
+ QSpinBox *unusedColorIdentitiesOpacitySpinBox;
QScrollArea *scrollArea;
VisualDeckStorageFolderDisplayWidget *folderWidget;
diff --git a/cockatrice/src/client/ui/window_main.cpp b/cockatrice/src/client/ui/window_main.cpp
index 9e5129bf1..cf0ee6c27 100644
--- a/cockatrice/src/client/ui/window_main.cpp
+++ b/cockatrice/src/client/ui/window_main.cpp
@@ -1018,7 +1018,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
return;
bClosingDown = true;
- if (!tabSupervisor->closeRequest()) {
+ if (!tabSupervisor->close()) {
event->ignore();
bClosingDown = false;
return;
diff --git a/cockatrice/src/deck/deck_loader.cpp b/cockatrice/src/deck/deck_loader.cpp
index 697905d0e..8130752b7 100644
--- a/cockatrice/src/deck/deck_loader.cpp
+++ b/cockatrice/src/deck/deck_loader.cpp
@@ -16,9 +16,10 @@
#include
#include
-const QStringList DeckLoader::fileNameFilters = QStringList()
- << QObject::tr("Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck)")
- << QObject::tr("All files (*.*)");
+const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.dek", "*.txt", "*.mwDeck"};
+
+const QStringList DeckLoader::FILE_NAME_FILTERS = {
+ tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")};
DeckLoader::DeckLoader() : DeckList(), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
{
@@ -225,6 +226,19 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat f
return result;
}
+static QString getDomainForWebsite(DeckLoader::DecklistWebsite website)
+{
+ switch (website) {
+ case DeckLoader::DecklistOrg:
+ return "www.decklist.org";
+ case DeckLoader::DecklistXyz:
+ return "www.decklist.xyz";
+ default:
+ qCWarning(DeckLoaderLog) << "Invalid decklist website enum:" << website;
+ return "";
+ }
+}
+
// This struct is here to support the forEachCard function call, defined in decklist. It
// requires a function to be called for each card, and passes an inner node and a card for
// each card in the decklist.
@@ -274,11 +288,14 @@ struct FormatDeckListForExport
}
};
-// Export deck to decklist function, called to format the deck in a way to be sent to a server
-QString DeckLoader::exportDeckToDecklist()
+/**
+ * Export deck to decklist function, called to format the deck in a way to be sent to a server
+ * @param website The website we're sending the deck to
+ */
+QString DeckLoader::exportDeckToDecklist(DecklistWebsite website)
{
// Add the base url
- QString deckString = "https://www.decklist.org/?";
+ QString deckString = "https://" + getDomainForWebsite(website) + "/?";
// Create two strings to pass to function
QString mainBoardCards, sideBoardCards;
// Set up the struct to call.
diff --git a/cockatrice/src/deck/deck_loader.h b/cockatrice/src/deck/deck_loader.h
index 035830e0a..53dd1f711 100644
--- a/cockatrice/src/deck/deck_loader.h
+++ b/cockatrice/src/deck/deck_loader.h
@@ -20,7 +20,22 @@ public:
PlainTextFormat,
CockatriceFormat
};
- static const QStringList fileNameFilters;
+
+ /**
+ * Supported file extensions for decklist files
+ */
+ static const QStringList ACCEPTED_FILE_EXTENSIONS;
+
+ /**
+ * For use with `QFileDialog::setNameFilters`
+ */
+ static const QStringList FILE_NAME_FILTERS;
+
+ enum DecklistWebsite
+ {
+ DecklistOrg,
+ DecklistXyz
+ };
private:
QString lastFileName;
@@ -62,7 +77,7 @@ public:
bool loadFromRemote(const QString &nativeString, int remoteDeckId);
bool saveToFile(const QString &fileName, FileFormat fmt);
bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt);
- QString exportDeckToDecklist();
+ QString exportDeckToDecklist(DecklistWebsite website);
void resolveSetNameAndNumberToProviderID();
diff --git a/cockatrice/src/dialogs/dlg_load_deck.cpp b/cockatrice/src/dialogs/dlg_load_deck.cpp
index 0fabdaaa3..91252a768 100644
--- a/cockatrice/src/dialogs/dlg_load_deck.cpp
+++ b/cockatrice/src/dialogs/dlg_load_deck.cpp
@@ -11,7 +11,7 @@ DlgLoadDeck::DlgLoadDeck(QWidget *parent) : QFileDialog(parent, tr("Load Deck"))
}
setDirectory(startingDir);
- setNameFilters(DeckLoader::fileNameFilters);
+ setNameFilters(DeckLoader::FILE_NAME_FILTERS);
connect(this, &DlgLoadDeck::accepted, this, &DlgLoadDeck::actAccepted);
}
diff --git a/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp
index 6cecc10bf..26f92f4e9 100644
--- a/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp
+++ b/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp
@@ -80,7 +80,7 @@ bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const
QTextStream stream(&buffer);
- if (deckLoader->loadFromStream_Plain(stream)) {
+ if (deckLoader->loadFromStream_Plain(stream, true)) {
if (loadSetNameAndNumberCheckBox->isChecked()) {
deckLoader->resolveSetNameAndNumberToProviderID();
} else {
diff --git a/cockatrice/src/dialogs/dlg_manage_sets.cpp b/cockatrice/src/dialogs/dlg_manage_sets.cpp
index ab82b9b65..626abd43f 100644
--- a/cockatrice/src/dialogs/dlg_manage_sets.cpp
+++ b/cockatrice/src/dialogs/dlg_manage_sets.cpp
@@ -8,6 +8,7 @@
#include "../settings/cache_settings.h"
#include
+#include
#include
#include
#include
@@ -162,6 +163,11 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
sortWarning->setLayout(sortWarningLayout);
sortWarning->setVisible(false);
+ includeOnlineOnlyCards = SettingsCache::instance().getIncludeOnlineOnlyCards();
+ QCheckBox *onlineOnly = new QCheckBox(tr("Include online-only (Arena) cards [requires restart]"));
+ onlineOnly->setChecked(includeOnlineOnlyCards);
+ connect(onlineOnly, &QAbstractButton::toggled, this, &WndSets::includeOnlineOnlyCardsChanged);
+
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(actSave()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(actRestore()));
@@ -175,8 +181,9 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
mainLayout->addWidget(enableSomeButton, 2, 1);
mainLayout->addWidget(disableSomeButton, 2, 2);
mainLayout->addWidget(sortWarning, 3, 1, 1, 2);
- mainLayout->addWidget(hintsGroupBox, 4, 1, 1, 2);
- mainLayout->addWidget(buttonBox, 5, 1, 1, 2);
+ mainLayout->addWidget(onlineOnly, 4, 1, 1, 2);
+ mainLayout->addWidget(hintsGroupBox, 5, 1, 1, 2);
+ mainLayout->addWidget(buttonBox, 6, 1, 1, 2);
mainLayout->setColumnStretch(1, 1);
mainLayout->setColumnStretch(2, 1);
@@ -239,9 +246,15 @@ void WndSets::rebuildMainLayout(int actionToTake)
}
}
+void WndSets::includeOnlineOnlyCardsChanged(bool _includeOnlineOnlyCards)
+{
+ includeOnlineOnlyCards = _includeOnlineOnlyCards;
+}
+
void WndSets::actSave()
{
model->save(CardDatabaseManager::getInstance());
+ SettingsCache::instance().setIncludeOnlineOnlyCards(includeOnlineOnlyCards);
PictureLoader::clearPixmapCache();
close();
}
diff --git a/cockatrice/src/dialogs/dlg_manage_sets.h b/cockatrice/src/dialogs/dlg_manage_sets.h
index e974f67f9..13dea7fef 100644
--- a/cockatrice/src/dialogs/dlg_manage_sets.h
+++ b/cockatrice/src/dialogs/dlg_manage_sets.h
@@ -44,6 +44,7 @@ private:
void saveHeaderState();
void rebuildMainLayout(int actionToTake);
bool setOrderIsSorted;
+ bool includeOnlineOnlyCards;
enum
{
NO_SETS_SELECTED,
@@ -73,6 +74,7 @@ private slots:
void actDisableResetButton(const QString &filterText);
void actSort(int index);
void actIgnoreWarning();
+ void includeOnlineOnlyCardsChanged(bool _includeOnlineOnlyCardsChanged);
};
#endif
diff --git a/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp b/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp
index 4f0207155..edf593571 100644
--- a/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp
+++ b/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp
@@ -3,7 +3,6 @@
#include "../game/cards/card_database.h"
#include "../game/cards/card_database_manager.h"
#include "../game/filters/filter_string.h"
-#include "trice_limits.h"
#include
#include
@@ -14,15 +13,17 @@
#include
#include
-DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QString _expr, uint _numberOfHits, bool autoPlay)
+DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, uint _numberOfHits, bool autoPlay)
: QDialog(parent)
{
exprLabel = new QLabel(tr("Card name (or search expressions):"));
- exprEdit = new QLineEdit(this);
- exprEdit->setFocus();
- exprEdit->setText(_expr);
- exprLabel->setBuddy(exprEdit);
+ exprComboBox = new QComboBox(this);
+ exprComboBox->setFocus();
+ exprComboBox->setEditable(true);
+ exprComboBox->setInsertPolicy(QComboBox::InsertAtTop);
+ exprComboBox->insertItems(0, exprs);
+ exprLabel->setBuddy(exprComboBox);
numberOfHitsLabel = new QLabel(tr("Number of hits:"));
numberOfHitsEdit = new QSpinBox(this);
@@ -43,7 +44,7 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QString _expr, uint
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(exprLabel);
- mainLayout->addWidget(exprEdit);
+ mainLayout->addWidget(exprComboBox);
mainLayout->addItem(grid);
mainLayout->addWidget(autoPlayCheckBox);
mainLayout->addWidget(buttonBox);
@@ -92,7 +93,7 @@ bool DlgMoveTopCardsUntil::validateMatchExists(const FilterString &filterString)
void DlgMoveTopCardsUntil::validateAndAccept()
{
- auto movingCardsUntilFilter = FilterString(exprEdit->text());
+ auto movingCardsUntilFilter = FilterString(exprComboBox->currentText());
if (!movingCardsUntilFilter.valid()) {
QMessageBox::warning(this, tr("Invalid filter"), movingCardsUntilFilter.error(), QMessageBox::Ok);
return;
@@ -102,12 +103,29 @@ void DlgMoveTopCardsUntil::validateAndAccept()
return;
}
+ // move currently selected text to top of history list
+ if (exprComboBox->currentIndex() != 0) {
+ QString currentExpr = exprComboBox->currentText();
+ exprComboBox->removeItem(exprComboBox->currentIndex());
+ exprComboBox->insertItem(0, currentExpr);
+ exprComboBox->setCurrentIndex(0);
+ }
+
accept();
}
QString DlgMoveTopCardsUntil::getExpr() const
{
- return exprEdit->text();
+ return exprComboBox->currentText();
+}
+
+QStringList DlgMoveTopCardsUntil::getExprs() const
+{
+ QStringList exprs;
+ for (int i = 0; i < exprComboBox->count(); ++i) {
+ exprs.append(exprComboBox->itemText(i));
+ }
+ return exprs;
}
uint DlgMoveTopCardsUntil::getNumberOfHits() const
diff --git a/cockatrice/src/dialogs/dlg_move_top_cards_until.h b/cockatrice/src/dialogs/dlg_move_top_cards_until.h
index c4cc2e0cb..d41d029d6 100644
--- a/cockatrice/src/dialogs/dlg_move_top_cards_until.h
+++ b/cockatrice/src/dialogs/dlg_move_top_cards_until.h
@@ -2,10 +2,10 @@
#define DLG_MOVE_TOP_CARDS_UNTIL_H
#include
+#include
#include
#include
#include
-#include
#include
class FilterString;
@@ -15,7 +15,7 @@ class DlgMoveTopCardsUntil : public QDialog
Q_OBJECT
QLabel *exprLabel, *numberOfHitsLabel;
- QLineEdit *exprEdit;
+ QComboBox *exprComboBox;
QSpinBox *numberOfHitsEdit;
QDialogButtonBox *buttonBox;
QCheckBox *autoPlayCheckBox;
@@ -25,10 +25,11 @@ class DlgMoveTopCardsUntil : public QDialog
public:
explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr,
- QString expr = QString(),
+ QStringList exprs = QStringList(),
uint numberOfHits = 1,
bool autoPlay = false);
QString getExpr() const;
+ QStringList getExprs() const;
uint getNumberOfHits() const;
bool isAutoPlay() const;
};
diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp
index a6ad383d6..d380f8bf3 100644
--- a/cockatrice/src/dialogs/dlg_settings.cpp
+++ b/cockatrice/src/dialogs/dlg_settings.cpp
@@ -41,6 +41,7 @@
#include
#include
#include
+#include
#define WIKI_CUSTOM_PIC_URL "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs"
#define WIKI_CUSTOM_SHORTCUTS "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Keyboard-Shortcuts"
@@ -358,25 +359,8 @@ AppearanceSettingsPage::AppearanceSettingsPage()
showShortcutsCheckBox.setChecked(settings.getShowShortcuts());
connect(&showShortcutsCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &AppearanceSettingsPage::showShortcutsChanged);
- visualDeckStorageDrawUnusedColorIdentitiesCheckBox.setChecked(
- settings.getVisualDeckStorageDrawUnusedColorIdentities());
- connect(&visualDeckStorageDrawUnusedColorIdentitiesCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings,
- &SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities);
-
- visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setMinimum(0);
- visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setMaximum(100);
- visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setValue(
- settings.getVisualDeckStorageUnusedColorIdentitiesOpacity());
- connect(&visualDeckStorageUnusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged),
- &settings, &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
-
- visualDeckStorageUnusedColorIdentitiesOpacityLabel.setBuddy(&visualDeckStorageUnusedColorIdentitiesOpacitySpinBox);
-
auto *menuGrid = new QGridLayout;
menuGrid->addWidget(&showShortcutsCheckBox, 0, 0);
- menuGrid->addWidget(&visualDeckStorageDrawUnusedColorIdentitiesCheckBox, 1, 0);
- menuGrid->addWidget(&visualDeckStorageUnusedColorIdentitiesOpacityLabel, 2, 0);
- menuGrid->addWidget(&visualDeckStorageUnusedColorIdentitiesOpacitySpinBox, 2, 1);
menuGroupBox = new QGroupBox;
menuGroupBox->setLayout(menuGrid);
@@ -400,6 +384,9 @@ AppearanceSettingsPage::AppearanceSettingsPage()
cardScalingCheckBox.setChecked(settings.getScaleCards());
connect(&cardScalingCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setCardScaling);
+ roundCardCornersCheckBox.setChecked(settings.getRoundCardCorners());
+ connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings, &SettingsCache::setRoundCardCorners);
+
verticalCardOverlapPercentBox.setValue(settings.getStackCardOverlapPercent());
verticalCardOverlapPercentBox.setRange(0, 80);
connect(&verticalCardOverlapPercentBox, SIGNAL(valueChanged(int)), &settings,
@@ -419,14 +406,15 @@ AppearanceSettingsPage::AppearanceSettingsPage()
cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2);
cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2);
cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2);
- cardsGrid->addWidget(&overrideAllCardArtWithPersonalPreferenceCheckBox, 3, 0, 1, 2);
- cardsGrid->addWidget(&bumpSetsWithCardsInDeckToTopCheckBox, 4, 0, 1, 2);
- cardsGrid->addWidget(&verticalCardOverlapPercentLabel, 5, 0, 1, 1);
- cardsGrid->addWidget(&verticalCardOverlapPercentBox, 5, 1, 1, 1);
- cardsGrid->addWidget(&cardViewInitialRowsMaxLabel, 6, 0);
- cardsGrid->addWidget(&cardViewInitialRowsMaxBox, 6, 1);
- cardsGrid->addWidget(&cardViewExpandedRowsMaxLabel, 7, 0);
- cardsGrid->addWidget(&cardViewExpandedRowsMaxBox, 7, 1);
+ cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2);
+ cardsGrid->addWidget(&overrideAllCardArtWithPersonalPreferenceCheckBox, 4, 0, 1, 2);
+ cardsGrid->addWidget(&bumpSetsWithCardsInDeckToTopCheckBox, 5, 0, 1, 2);
+ cardsGrid->addWidget(&verticalCardOverlapPercentLabel, 6, 0, 1, 1);
+ cardsGrid->addWidget(&verticalCardOverlapPercentBox, 6, 1, 1, 1);
+ cardsGrid->addWidget(&cardViewInitialRowsMaxLabel, 7, 0);
+ cardsGrid->addWidget(&cardViewInitialRowsMaxBox, 7, 1);
+ cardsGrid->addWidget(&cardViewExpandedRowsMaxLabel, 8, 0);
+ cardsGrid->addWidget(&cardViewExpandedRowsMaxBox, 8, 1);
cardsGroupBox = new QGroupBox;
cardsGroupBox->setLayout(cardsGrid);
@@ -547,10 +535,6 @@ void AppearanceSettingsPage::retranslateUi()
menuGroupBox->setTitle(tr("Menu settings"));
showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus"));
- visualDeckStorageDrawUnusedColorIdentitiesCheckBox.setText(
- tr("Draw missing color identities in visual deck storage without color label"));
- visualDeckStorageUnusedColorIdentitiesOpacityLabel.setText(tr("Missing color identity opacity"));
- visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setSuffix("%");
cardsGroupBox->setTitle(tr("Card rendering"));
displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture"));
@@ -561,6 +545,7 @@ void AppearanceSettingsPage::retranslateUi()
bumpSetsWithCardsInDeckToTopCheckBox.setText(
tr("Bump sets that the deck contains cards from to the top in the printing selector"));
cardScalingCheckBox.setText(tr("Scale cards on mouse over"));
+ roundCardCornersCheckBox.setText(tr("Use rounded card corners"));
verticalCardOverlapPercentLabel.setText(
tr("Minimum overlap percentage of cards on the stack and in vertical hand"));
cardViewInitialRowsMaxLabel.setText(tr("Maximum initial height for card view window:"));
diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h
index a601e5023..8f325309b 100644
--- a/cockatrice/src/dialogs/dlg_settings.h
+++ b/cockatrice/src/dialogs/dlg_settings.h
@@ -102,14 +102,12 @@ private:
QLabel minPlayersForMultiColumnLayoutLabel;
QLabel maxFontSizeForCardsLabel;
QCheckBox showShortcutsCheckBox;
- QCheckBox visualDeckStorageDrawUnusedColorIdentitiesCheckBox;
- QLabel visualDeckStorageUnusedColorIdentitiesOpacityLabel;
- QSpinBox visualDeckStorageUnusedColorIdentitiesOpacitySpinBox;
QCheckBox displayCardNamesCheckBox;
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
QCheckBox bumpSetsWithCardsInDeckToTopCheckBox;
QCheckBox cardScalingCheckBox;
+ QCheckBox roundCardCornersCheckBox;
QLabel verticalCardOverlapPercentLabel;
QSpinBox verticalCardOverlapPercentBox;
QLabel cardViewInitialRowsMaxLabel;
diff --git a/cockatrice/src/dialogs/dlg_update.cpp b/cockatrice/src/dialogs/dlg_update.cpp
index 3e3ec2039..ae60fc0b7 100644
--- a/cockatrice/src/dialogs/dlg_update.cpp
+++ b/cockatrice/src/dialogs/dlg_update.cpp
@@ -165,10 +165,12 @@ void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Releas
QString(": %1 ").arg(release->getName()) + "" + tr("Released") +
QString(": %1 (").arg(publishDate, release->getDescriptionUrl()) + tr("Changelog") +
")
" +
- tr("Unfortunately there are no download packages available for your operating system. \nYou may have "
- "to build from source yourself.") +
+ tr("Unfortunately, the automatic updater failed to find a compatible download. \nYou may have to "
+ "manually download the new version.") +
"
" +
- tr("Please check the download page manually and visit the wiki for instructions on compiling."));
+ tr("Please check the releases page on our Github and download the build for your "
+ "system.")
+ .arg(release->getDescriptionUrl()));
}
}
diff --git a/cockatrice/src/game/board/arrow_target.cpp b/cockatrice/src/game/board/arrow_target.cpp
index e844757dd..2dbd913fa 100644
--- a/cockatrice/src/game/board/arrow_target.cpp
+++ b/cockatrice/src/game/board/arrow_target.cpp
@@ -6,6 +6,7 @@
ArrowTarget::ArrowTarget(Player *_owner, QGraphicsItem *parent)
: AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false)
{
+ setFlag(ItemSendsScenePositionChanges);
}
ArrowTarget::~ArrowTarget()
@@ -25,3 +26,16 @@ void ArrowTarget::setBeingPointedAt(bool _beingPointedAt)
beingPointedAt = _beingPointedAt;
update();
}
+
+QVariant ArrowTarget::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
+{
+ if (change == ItemScenePositionHasChanged && scene()) {
+ for (auto *arrow : arrowsFrom)
+ arrow->updatePath();
+
+ for (auto *arrow : arrowsTo)
+ arrow->updatePath();
+ }
+
+ return QGraphicsItem::itemChange(change, value);
+}
diff --git a/cockatrice/src/game/board/arrow_target.h b/cockatrice/src/game/board/arrow_target.h
index 98a80091f..a4ef6895d 100644
--- a/cockatrice/src/game/board/arrow_target.h
+++ b/cockatrice/src/game/board/arrow_target.h
@@ -57,5 +57,8 @@ public:
{
arrowsTo.removeOne(arrow);
}
+
+protected:
+ QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
};
#endif
diff --git a/cockatrice/src/game/cards/abstract_card_drag_item.cpp b/cockatrice/src/game/cards/abstract_card_drag_item.cpp
index e9fe30c50..0c7acd917 100644
--- a/cockatrice/src/game/cards/abstract_card_drag_item.cpp
+++ b/cockatrice/src/game/cards/abstract_card_drag_item.cpp
@@ -1,6 +1,6 @@
#include "abstract_card_drag_item.h"
-#include "card_database.h"
+#include "../../settings/cache_settings.h"
#include
#include
@@ -32,6 +32,13 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
.translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF));
setCacheMode(DeviceCoordinateCache);
+
+ connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
+ Q_UNUSED(_roundCardCorners);
+
+ prepareGeometryChange();
+ update();
+ });
}
AbstractCardDragItem::~AbstractCardDragItem()
@@ -43,7 +50,8 @@ AbstractCardDragItem::~AbstractCardDragItem()
QPainterPath AbstractCardDragItem::shape() const
{
QPainterPath shape;
- shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH);
+ qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
+ shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
diff --git a/cockatrice/src/game/cards/abstract_card_item.cpp b/cockatrice/src/game/cards/abstract_card_item.cpp
index 15dcc7e71..dc00aae66 100644
--- a/cockatrice/src/game/cards/abstract_card_item.cpp
+++ b/cockatrice/src/game/cards/abstract_card_item.cpp
@@ -26,6 +26,13 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent,
connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); });
refreshCardInfo();
+
+ connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
+ Q_UNUSED(_roundCardCorners);
+
+ prepareGeometryChange();
+ update();
+ });
}
AbstractCardItem::~AbstractCardItem()
@@ -41,7 +48,8 @@ QRectF AbstractCardItem::boundingRect() const
QPainterPath AbstractCardItem::shape() const
{
QPainterPath shape;
- shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH);
+ qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
+ shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
@@ -325,5 +333,5 @@ QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change,
update();
return value;
} else
- return QGraphicsItem::itemChange(change, value);
+ return ArrowTarget::itemChange(change, value);
}
diff --git a/cockatrice/src/game/cards/card_database.cpp b/cockatrice/src/game/cards/card_database.cpp
index c7e2f06f2..0fe43e20a 100644
--- a/cockatrice/src/game/cards/card_database.cpp
+++ b/cockatrice/src/game/cards/card_database.cpp
@@ -1,10 +1,8 @@
#include "card_database.h"
-#include "../../client/network/spoiler_background_updater.h"
#include "../../client/ui/picture_loader/picture_loader.h"
#include "../../settings/cache_settings.h"
#include "../../utility/card_set_comparator.h"
-#include "../game_specific_terms.h"
#include "./card_database_parser/cockatrice_xml_3.h"
#include "./card_database_parser/cockatrice_xml_4.h"
@@ -20,351 +18,6 @@
const char *CardDatabase::TOKENS_SETNAME = "TK";
-CardSet::CardSet(const QString &_shortName,
- const QString &_longName,
- const QString &_setType,
- const QDate &_releaseDate,
- const CardSet::Priority _priority)
- : shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType), priority(_priority)
-{
- loadSetOptions();
-}
-
-CardSetPtr CardSet::newInstance(const QString &_shortName,
- const QString &_longName,
- const QString &_setType,
- const QDate &_releaseDate,
- const Priority _priority)
-{
- CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate, _priority));
- // ptr->setSmartPointer(ptr);
- return ptr;
-}
-
-QString CardSet::getCorrectedShortName() const
-{
- // For Windows machines.
- QSet invalidFileNames;
- invalidFileNames << "CON"
- << "PRN"
- << "AUX"
- << "NUL"
- << "COM1"
- << "COM2"
- << "COM3"
- << "COM4"
- << "COM5"
- << "COM6"
- << "COM7"
- << "COM8"
- << "COM9"
- << "LPT1"
- << "LPT2"
- << "LPT3"
- << "LPT4"
- << "LPT5"
- << "LPT6"
- << "LPT7"
- << "LPT8"
- << "LPT9";
-
- return invalidFileNames.contains(shortName) ? shortName + "_" : shortName;
-}
-
-void CardSet::loadSetOptions()
-{
- sortKey = SettingsCache::instance().cardDatabase().getSortKey(shortName);
- enabled = SettingsCache::instance().cardDatabase().isEnabled(shortName);
- isknown = SettingsCache::instance().cardDatabase().isKnown(shortName);
-}
-
-void CardSet::setSortKey(unsigned int _sortKey)
-{
- sortKey = _sortKey;
- SettingsCache::instance().cardDatabase().setSortKey(shortName, _sortKey);
-}
-
-void CardSet::setEnabled(bool _enabled)
-{
- enabled = _enabled;
- SettingsCache::instance().cardDatabase().setEnabled(shortName, _enabled);
-}
-
-void CardSet::setIsKnown(bool _isknown)
-{
- isknown = _isknown;
- SettingsCache::instance().cardDatabase().setIsKnown(shortName, _isknown);
-}
-
-class SetList::KeyCompareFunctor
-{
-public:
- inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
- {
- if (a.isNull() || b.isNull()) {
- qCDebug(CardDatabaseLog) << "SetList::KeyCompareFunctor a or b is null";
- return false;
- }
-
- return a->getSortKey() < b->getSortKey();
- }
-};
-
-void SetList::sortByKey()
-{
- std::sort(begin(), end(), KeyCompareFunctor());
-}
-
-int SetList::getEnabledSetsNum()
-{
- int num = 0;
- for (int i = 0; i < size(); ++i) {
- CardSetPtr set = at(i);
- if (set && set->getEnabled()) {
- ++num;
- }
- }
- return num;
-}
-
-int SetList::getUnknownSetsNum()
-{
- int num = 0;
- for (int i = 0; i < size(); ++i) {
- CardSetPtr set = at(i);
- if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
- ++num;
- }
- }
- return num;
-}
-
-QStringList SetList::getUnknownSetsNames()
-{
- QStringList sets = QStringList();
- for (int i = 0; i < size(); ++i) {
- CardSetPtr set = at(i);
- if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
- sets << set->getShortName();
- }
- }
- return sets;
-}
-
-void SetList::enableAllUnknown()
-{
- for (int i = 0; i < size(); ++i) {
- CardSetPtr set = at(i);
- if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
- set->setIsKnown(true);
- set->setEnabled(true);
- } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
- set->setEnabled(true);
- }
- }
-}
-
-void SetList::enableAll()
-{
- for (int i = 0; i < size(); ++i) {
- CardSetPtr set = at(i);
-
- if (set == nullptr) {
- qCDebug(CardDatabaseLog) << "enabledAll has null";
- continue;
- }
-
- if (!set->getIsKnownIgnored()) {
- set->setIsKnown(true);
- }
-
- set->setEnabled(true);
- }
-}
-
-void SetList::markAllAsKnown()
-{
- for (int i = 0; i < size(); ++i) {
- CardSetPtr set = at(i);
- if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
- set->setIsKnown(true);
- set->setEnabled(false);
- } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
- set->setEnabled(true);
- }
- }
-}
-
-void SetList::guessSortKeys()
-{
- defaultSort();
- for (int i = 0; i < size(); ++i) {
- CardSetPtr set = at(i);
- if (set.isNull()) {
- qCDebug(CardDatabaseLog) << "guessSortKeys set is null";
- continue;
- }
- set->setSortKey(i);
- }
-}
-
-void SetList::defaultSort()
-{
- std::sort(begin(), end(), [](const CardSetPtr &a, const CardSetPtr &b) {
- // Sort by priority, then by release date, then by short name
- if (a->getPriority() != b->getPriority()) {
- return a->getPriority() < b->getPriority(); // lowest first
- } else if (a->getReleaseDate() != b->getReleaseDate()) {
- return a->getReleaseDate() > b->getReleaseDate(); // most recent first
- } else {
- return a->getShortName() < b->getShortName(); // alphabetically
- }
- });
-}
-
-CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set)
-{
-}
-
-CardInfo::CardInfo(const QString &_name,
- const QString &_text,
- bool _isToken,
- QVariantHash _properties,
- const QList &_relatedCards,
- const QList &_reverseRelatedCards,
- CardInfoPerSetMap _sets,
- bool _cipt,
- bool _landscapeOrientation,
- int _tableRow,
- bool _upsideDownArt)
- : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
- reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt),
- landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt)
-{
- pixmapCacheKey = QLatin1String("card_") + name;
- simpleName = CardInfo::simplifyName(name);
-
- refreshCachedSetNames();
-}
-
-CardInfo::~CardInfo()
-{
- PictureLoader::clearPixmapCache(smartThis);
-}
-
-CardInfoPtr CardInfo::newInstance(const QString &_name)
-{
- return newInstance(_name, QString(), false, QVariantHash(), QList(), QList(),
- CardInfoPerSetMap(), false, false, 0, false);
-}
-
-CardInfoPtr CardInfo::newInstance(const QString &_name,
- const QString &_text,
- bool _isToken,
- QVariantHash _properties,
- const QList &_relatedCards,
- const QList &_reverseRelatedCards,
- CardInfoPerSetMap _sets,
- bool _cipt,
- bool _landscapeOrientation,
- int _tableRow,
- bool _upsideDownArt)
-{
- CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards,
- _sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt));
- ptr->setSmartPointer(ptr);
-
- for (const auto &cardInfoPerSetList : _sets) {
- for (const CardInfoPerSet &set : cardInfoPerSetList) {
- set.getPtr()->append(ptr);
- break;
- }
- }
-
- return ptr;
-}
-
-QString CardInfo::getCorrectedName() const
-{
- // remove all the characters reserved in windows file paths,
- // other oses only disallow a subset of these so it covers all
- static const QRegularExpression rmrx(R"(( // |[*<>:"\\?\x00-\x08\x10-\x1f]))");
- static const QRegularExpression spacerx(R"([/\x09-\x0f])");
- static const QString space(' ');
- QString result = name;
- // Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental?
- return result.remove(rmrx).replace(spacerx, space);
-}
-
-void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info)
-{
- _set->append(smartThis);
- sets[_set->getShortName()].append(_info);
-
- refreshCachedSetNames();
-}
-
-void CardInfo::combineLegalities(const QVariantHash &props)
-{
- QHashIterator it(props);
- while (it.hasNext()) {
- it.next();
- if (it.key().startsWith("format-")) {
- smartThis->setProperty(it.key(), it.value().toString());
- }
- }
-}
-
-void CardInfo::refreshCachedSetNames()
-{
- QStringList setList;
- // update the cached list of set names
- for (const auto &cardInfoPerSetList : sets) {
- for (const auto &set : cardInfoPerSetList) {
- if (set.getPtr()->getEnabled()) {
- setList << set.getPtr()->getShortName();
- }
- break;
- }
- }
- setsNames = setList.join(", ");
-}
-
-QString CardInfo::simplifyName(const QString &name)
-{
- static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)");
- static const QRegularExpression nonAlnum("[^a-z0-9]");
-
- QString simpleName = name.toLower();
-
- // remove spaces and right halves of split cards
- simpleName.remove(spaceOrSplit);
-
- // So Aetherling would work, but not Ætherling since 'Æ' would get replaced
- // with nothing.
- simpleName.replace("æ", "ae");
-
- // Replace Jötun Grunt with Jotun Grunt.
- simpleName = simpleName.normalized(QString::NormalizationForm_KD);
-
- // remove all non alphanumeric characters from the name
- simpleName.remove(nonAlnum);
- return simpleName;
-}
-
-const QChar CardInfo::getColorChar() const
-{
- QString colors = getColors();
- switch (colors.size()) {
- case 0:
- return QChar();
- case 1:
- return colors.at(0);
- default:
- return QChar('m');
- }
-}
-
CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded)
{
qRegisterMetaType("CardInfoPtr");
@@ -884,64 +537,3 @@ bool CardDatabase::saveCustomTokensToFile()
availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName);
return true;
}
-
-CardRelation::CardRelation(const QString &_name,
- AttachType _attachType,
- bool _isCreateAllExclusion,
- bool _isVariableCount,
- int _defaultCount,
- bool _isPersistent)
- : name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion),
- isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent)
-{
-}
-
-void CardInfo::resetReverseRelatedCards2Me()
-{
- for (CardRelation *cardRelation : this->getReverseRelatedCards2Me()) {
- cardRelation->deleteLater();
- }
- reverseRelatedCardsToMe = QList();
-}
-
-// Back-compatibility methods. Remove ASAP
-const QString CardInfo::getCardType() const
-{
- return getProperty(Mtg::CardType);
-}
-void CardInfo::setCardType(const QString &value)
-{
- setProperty(Mtg::CardType, value);
-}
-const QString CardInfo::getCmc() const
-{
- return getProperty(Mtg::ConvertedManaCost);
-}
-const QString CardInfo::getColors() const
-{
- return getProperty(Mtg::Colors);
-}
-void CardInfo::setColors(const QString &value)
-{
- setProperty(Mtg::Colors, value);
-}
-const QString CardInfo::getLoyalty() const
-{
- return getProperty(Mtg::Loyalty);
-}
-const QString CardInfo::getMainCardType() const
-{
- return getProperty(Mtg::MainCardType);
-}
-const QString CardInfo::getManaCost() const
-{
- return getProperty(Mtg::ManaCost);
-}
-const QString CardInfo::getPowTough() const
-{
- return getProperty(Mtg::PowTough);
-}
-void CardInfo::setPowTough(const QString &value)
-{
- setProperty(Mtg::PowTough, value);
-}
diff --git a/cockatrice/src/game/cards/card_database.h b/cockatrice/src/game/cards/card_database.h
index 12b4a06fe..236278b30 100644
--- a/cockatrice/src/game/cards/card_database.h
+++ b/cockatrice/src/game/cards/card_database.h
@@ -1,16 +1,14 @@
#ifndef CARDDATABASE_H
#define CARDDATABASE_H
+#include "card_info.h"
+
#include
#include
#include
#include
#include
-#include
-#include
-#include
#include
-#include
#include
#include
@@ -18,406 +16,8 @@ inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database");
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading");
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure");
-class CardDatabase;
-class CardInfo;
-class CardInfoPerSet;
-class CardSet;
-class CardRelation;
class ICardDatabaseParser;
-typedef QMap QStringMap;
-typedef QSharedPointer CardInfoPtr;
-typedef QSharedPointer CardSetPtr;
-typedef QMap> CardInfoPerSetMap;
-
-Q_DECLARE_METATYPE(CardInfoPtr)
-
-class CardSet : public QList
-{
-public:
- enum Priority
- {
- PriorityFallback = 0,
- PriorityPrimary = 10,
- PrioritySecondary = 20,
- PriorityReprint = 30,
- PriorityOther = 40,
- PriorityLowest = 100,
- };
-
-private:
- QString shortName, longName;
- unsigned int sortKey;
- QDate releaseDate;
- QString setType;
- Priority priority;
- bool enabled, isknown;
-
-public:
- explicit CardSet(const QString &_shortName = QString(),
- const QString &_longName = QString(),
- const QString &_setType = QString(),
- const QDate &_releaseDate = QDate(),
- const Priority _priority = PriorityFallback);
- static CardSetPtr newInstance(const QString &_shortName = QString(),
- const QString &_longName = QString(),
- const QString &_setType = QString(),
- const QDate &_releaseDate = QDate(),
- const Priority _priority = PriorityFallback);
- QString getCorrectedShortName() const;
- QString getShortName() const
- {
- return shortName;
- }
- QString getLongName() const
- {
- return longName;
- }
- QString getSetType() const
- {
- return setType;
- }
- QDate getReleaseDate() const
- {
- return releaseDate;
- }
- Priority getPriority() const
- {
- return priority;
- }
- void setLongName(const QString &_longName)
- {
- longName = _longName;
- }
- void setSetType(const QString &_setType)
- {
- setType = _setType;
- }
- void setReleaseDate(const QDate &_releaseDate)
- {
- releaseDate = _releaseDate;
- }
- void setPriority(const Priority _priority)
- {
- priority = _priority;
- }
-
- void loadSetOptions();
- int getSortKey() const
- {
- return sortKey;
- }
- void setSortKey(unsigned int _sortKey);
- bool getEnabled() const
- {
- return enabled;
- }
- void setEnabled(bool _enabled);
- bool getIsKnown() const
- {
- return isknown;
- }
- void setIsKnown(bool _isknown);
-
- // Determine incomplete sets.
- bool getIsKnownIgnored() const
- {
- return longName.length() + setType.length() + releaseDate.toString().length() == 0;
- }
-};
-
-class SetList : public QList
-{
-private:
- class KeyCompareFunctor;
-
-public:
- void sortByKey();
- void guessSortKeys();
- void enableAllUnknown();
- void enableAll();
- void markAllAsKnown();
- int getEnabledSetsNum();
- int getUnknownSetsNum();
- QStringList getUnknownSetsNames();
- void defaultSort();
-};
-
-class CardInfoPerSet
-{
-public:
- explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer(nullptr));
- ~CardInfoPerSet() = default;
-
- bool operator==(const CardInfoPerSet &other) const
- {
- return this->set == other.set && this->properties == other.properties;
- }
-
-private:
- CardSetPtr set;
- // per-set card properties;
- QVariantHash properties;
-
-public:
- const CardSetPtr getPtr() const
- {
- return set;
- }
- const QStringList getProperties() const
- {
- return properties.keys();
- }
- const QString getProperty(const QString &propertyName) const
- {
- return properties.value(propertyName).toString();
- }
- void setProperty(const QString &_name, const QString &_value)
- {
- properties.insert(_name, _value);
- }
-};
-
-class CardInfo : public QObject
-{
- Q_OBJECT
-private:
- CardInfoPtr smartThis;
- // The card name
- QString name;
- // The name without punctuation or capitalization, for better card name recognition.
- QString simpleName;
- // The key used to identify this card in the cache
- QString pixmapCacheKey;
- // card text
- QString text;
- // whether this is not a "real" card but a token
- bool isToken;
- // basic card properties; common for all the sets
- QVariantHash properties;
- // the cards i'm related to
- QList relatedCards;
- // the card i'm reverse-related to
- QList reverseRelatedCards;
- // the cards thare are reverse-related to me
- QList reverseRelatedCardsToMe;
- // card sets
- CardInfoPerSetMap sets;
- // cached set names
- QString setsNames;
- // positioning properties; used by UI
- bool cipt;
- bool landscapeOrientation;
- int tableRow;
- bool upsideDownArt;
-
-public:
- explicit CardInfo(const QString &_name,
- const QString &_text,
- bool _isToken,
- QVariantHash _properties,
- const QList &_relatedCards,
- const QList &_reverseRelatedCards,
- CardInfoPerSetMap _sets,
- bool _cipt,
- bool _landscapeOrientation,
- int _tableRow,
- bool _upsideDownArt);
- CardInfo(const CardInfo &other)
- : QObject(other.parent()), name(other.name), simpleName(other.simpleName), pixmapCacheKey(other.pixmapCacheKey),
- text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
- reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
- sets(other.sets), setsNames(other.setsNames), cipt(other.cipt),
- landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt)
- {
- }
- ~CardInfo() override;
-
- static CardInfoPtr newInstance(const QString &_name);
-
- static CardInfoPtr newInstance(const QString &_name,
- const QString &_text,
- bool _isToken,
- QVariantHash _properties,
- const QList &_relatedCards,
- const QList &_reverseRelatedCards,
- CardInfoPerSetMap _sets,
- bool _cipt,
- bool _landscapeOrientation,
- int _tableRow,
- bool _upsideDownArt);
-
- CardInfoPtr clone() const
- {
- // Use the copy constructor to create a new instance
- CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this));
- newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance
- return newCardInfo;
- }
-
- void setSmartPointer(CardInfoPtr _ptr)
- {
- smartThis = std::move(_ptr);
- }
-
- // basic properties
- inline const QString &getName() const
- {
- return name;
- }
- const QString &getSimpleName() const
- {
- return simpleName;
- }
- void setPixmapCacheKey(QString _pixmapCacheKey)
- {
- pixmapCacheKey = _pixmapCacheKey;
- }
- const QString &getPixmapCacheKey() const
- {
- return pixmapCacheKey;
- }
-
- const QString &getText() const
- {
- return text;
- }
- void setText(const QString &_text)
- {
- text = _text;
- emit cardInfoChanged(smartThis);
- }
-
- bool getIsToken() const
- {
- return isToken;
- }
- const QStringList getProperties() const
- {
- return properties.keys();
- }
- const QString getProperty(const QString &propertyName) const
- {
- return properties.value(propertyName).toString();
- }
- void setProperty(const QString &_name, const QString &_value)
- {
- properties.insert(_name, _value);
- emit cardInfoChanged(smartThis);
- }
- bool hasProperty(const QString &propertyName) const
- {
- return properties.contains(propertyName);
- }
- const CardInfoPerSetMap &getSets() const
- {
- return sets;
- }
- const QString &getSetsNames() const
- {
- return setsNames;
- }
- const QString getSetProperty(const QString &setName, const QString &propertyName) const
- {
- if (!sets.contains(setName))
- return "";
-
- for (const auto &set : sets[setName]) {
- if (QLatin1String("card_") + this->getName() + QString("_") + QString(set.getProperty("uuid")) ==
- this->getPixmapCacheKey()) {
- return set.getProperty(propertyName);
- }
- }
-
- return sets[setName][0].getProperty(propertyName);
- }
-
- // related cards
- const QList &getRelatedCards() const
- {
- return relatedCards;
- }
- const QList &getReverseRelatedCards() const
- {
- return reverseRelatedCards;
- }
- const QList &getReverseRelatedCards2Me() const
- {
- return reverseRelatedCardsToMe;
- }
- const QList getAllRelatedCards() const
- {
- QList result;
- result.append(getRelatedCards());
- result.append(getReverseRelatedCards2Me());
- return result;
- }
- void resetReverseRelatedCards2Me();
- void addReverseRelatedCards2Me(CardRelation *cardRelation)
- {
- reverseRelatedCardsToMe.append(cardRelation);
- }
-
- // positioning
- bool getCipt() const
- {
- return cipt;
- }
- bool getLandscapeOrientation() const
- {
- return landscapeOrientation;
- }
- int getTableRow() const
- {
- return tableRow;
- }
- void setTableRow(int _tableRow)
- {
- tableRow = _tableRow;
- }
- bool getUpsideDownArt() const
- {
- return upsideDownArt;
- }
- const QChar getColorChar() const;
-
- // Back-compatibility methods. Remove ASAP
- const QString getCardType() const;
- void setCardType(const QString &value);
- const QString getCmc() const;
- const QString getColors() const;
- void setColors(const QString &value);
- const QString getLoyalty() const;
- const QString getMainCardType() const;
- const QString getManaCost() const;
- const QString getPowTough() const;
- void setPowTough(const QString &value);
-
- // methods using per-set properties
- QString getCustomPicURL(const QString &set) const
- {
- return getSetProperty(set, "picurl");
- }
- QString getCorrectedName() const;
- void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet());
- void combineLegalities(const QVariantHash &props);
- void emitPixmapUpdated()
- {
- emit pixmapUpdated();
- }
- void refreshCachedSetNames();
-
- /**
- * Simplify a name to have no punctuation and lowercase all letters, for
- * less strict name-matching.
- */
- static QString simplifyName(const QString &name);
-
-signals:
- void pixmapUpdated();
- void cardInfoChanged(CardInfoPtr card);
-};
-
enum LoadStatus
{
Ok,
@@ -523,79 +123,4 @@ signals:
void cardRemoved(CardInfoPtr card);
};
-class CardRelation : public QObject
-{
- Q_OBJECT
-public:
- enum AttachType
- {
- DoesNotAttach = 0,
- AttachTo = 1,
- TransformInto = 2,
- };
-
-private:
- QString name;
- AttachType attachType;
- bool isCreateAllExclusion;
- bool isVariableCount;
- int defaultCount;
- bool isPersistent;
-
-public:
- explicit CardRelation(const QString &_name = QString(),
- AttachType _attachType = DoesNotAttach,
- bool _isCreateAllExclusion = false,
- bool _isVariableCount = false,
- int _defaultCount = 1,
- bool _isPersistent = false);
-
- inline const QString &getName() const
- {
- return name;
- }
- AttachType getAttachType() const
- {
- return attachType;
- }
- bool getDoesAttach() const
- {
- return attachType != DoesNotAttach;
- }
- bool getDoesTransform() const
- {
- return attachType == TransformInto;
- }
- QString getAttachTypeAsString() const
- {
- switch (attachType) {
- case AttachTo:
- return "attach";
- case TransformInto:
- return "transform";
- default:
- return "";
- }
- }
- bool getCanCreateAnother() const
- {
- return !getDoesAttach();
- }
- bool getIsCreateAllExclusion() const
- {
- return isCreateAllExclusion;
- }
- bool getIsVariable() const
- {
- return isVariableCount;
- }
- int getDefaultCount() const
- {
- return defaultCount;
- }
- bool getIsPersistent() const
- {
- return isPersistent;
- }
-};
#endif
diff --git a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp
index e7f657b96..827b70fc3 100644
--- a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp
+++ b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp
@@ -1,5 +1,7 @@
#include "cockatrice_xml_4.h"
+#include "../../../settings/cache_settings.h"
+
#include
#include
#include
@@ -124,6 +126,7 @@ QVariantHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &x
void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
{
+ bool includeOnlineOnlyCards = SettingsCache::instance().getIncludeOnlineOnlyCards();
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
@@ -183,7 +186,17 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
attrName = "picurl";
setInfo.setProperty(attrName, attr.value().toString());
}
- _sets[setName].append(setInfo);
+
+ // This is very much a hack and not the right place to
+ // put this check, as it requires a reload of Cockatrice
+ // to be apply.
+ //
+ // However, this is also true of the `set->getEnabled()`
+ // check above (which is currently bugged as well), so
+ // we'll fix both at the same time.
+ if (includeOnlineOnlyCards || setInfo.getProperty("isOnlineOnly") != "true") {
+ _sets[setName].append(setInfo);
+ }
}
// related cards
} else if (xmlName == "related" || xmlName == "reverse-related") {
diff --git a/cockatrice/src/game/cards/card_info.cpp b/cockatrice/src/game/cards/card_info.cpp
new file mode 100644
index 000000000..c4e00638b
--- /dev/null
+++ b/cockatrice/src/game/cards/card_info.cpp
@@ -0,0 +1,418 @@
+#include "card_info.h"
+
+#include "../../client/ui/picture_loader/picture_loader.h"
+#include "../../settings/cache_settings.h"
+#include "../game_specific_terms.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+CardSet::CardSet(const QString &_shortName,
+ const QString &_longName,
+ const QString &_setType,
+ const QDate &_releaseDate,
+ const CardSet::Priority _priority)
+ : shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType), priority(_priority)
+{
+ loadSetOptions();
+}
+
+CardSetPtr CardSet::newInstance(const QString &_shortName,
+ const QString &_longName,
+ const QString &_setType,
+ const QDate &_releaseDate,
+ const Priority _priority)
+{
+ CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate, _priority));
+ // ptr->setSmartPointer(ptr);
+ return ptr;
+}
+
+QString CardSet::getCorrectedShortName() const
+{
+ // For Windows machines.
+ QSet invalidFileNames;
+ invalidFileNames << "CON"
+ << "PRN"
+ << "AUX"
+ << "NUL"
+ << "COM1"
+ << "COM2"
+ << "COM3"
+ << "COM4"
+ << "COM5"
+ << "COM6"
+ << "COM7"
+ << "COM8"
+ << "COM9"
+ << "LPT1"
+ << "LPT2"
+ << "LPT3"
+ << "LPT4"
+ << "LPT5"
+ << "LPT6"
+ << "LPT7"
+ << "LPT8"
+ << "LPT9";
+
+ return invalidFileNames.contains(shortName) ? shortName + "_" : shortName;
+}
+
+void CardSet::loadSetOptions()
+{
+ sortKey = SettingsCache::instance().cardDatabase().getSortKey(shortName);
+ enabled = SettingsCache::instance().cardDatabase().isEnabled(shortName);
+ isknown = SettingsCache::instance().cardDatabase().isKnown(shortName);
+}
+
+void CardSet::setSortKey(unsigned int _sortKey)
+{
+ sortKey = _sortKey;
+ SettingsCache::instance().cardDatabase().setSortKey(shortName, _sortKey);
+}
+
+void CardSet::setEnabled(bool _enabled)
+{
+ enabled = _enabled;
+ SettingsCache::instance().cardDatabase().setEnabled(shortName, _enabled);
+}
+
+void CardSet::setIsKnown(bool _isknown)
+{
+ isknown = _isknown;
+ SettingsCache::instance().cardDatabase().setIsKnown(shortName, _isknown);
+}
+
+class SetList::KeyCompareFunctor
+{
+public:
+ inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
+ {
+ if (a.isNull() || b.isNull()) {
+ qCDebug(CardInfoLog) << "SetList::KeyCompareFunctor a or b is null";
+ return false;
+ }
+
+ return a->getSortKey() < b->getSortKey();
+ }
+};
+
+void SetList::sortByKey()
+{
+ std::sort(begin(), end(), KeyCompareFunctor());
+}
+
+int SetList::getEnabledSetsNum()
+{
+ int num = 0;
+ for (int i = 0; i < size(); ++i) {
+ CardSetPtr set = at(i);
+ if (set && set->getEnabled()) {
+ ++num;
+ }
+ }
+ return num;
+}
+
+int SetList::getUnknownSetsNum()
+{
+ int num = 0;
+ for (int i = 0; i < size(); ++i) {
+ CardSetPtr set = at(i);
+ if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
+ ++num;
+ }
+ }
+ return num;
+}
+
+QStringList SetList::getUnknownSetsNames()
+{
+ QStringList sets = QStringList();
+ for (int i = 0; i < size(); ++i) {
+ CardSetPtr set = at(i);
+ if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
+ sets << set->getShortName();
+ }
+ }
+ return sets;
+}
+
+void SetList::enableAllUnknown()
+{
+ for (int i = 0; i < size(); ++i) {
+ CardSetPtr set = at(i);
+ if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
+ set->setIsKnown(true);
+ set->setEnabled(true);
+ } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
+ set->setEnabled(true);
+ }
+ }
+}
+
+void SetList::enableAll()
+{
+ for (int i = 0; i < size(); ++i) {
+ CardSetPtr set = at(i);
+
+ if (set == nullptr) {
+ qCDebug(CardInfoLog) << "enabledAll has null";
+ continue;
+ }
+
+ if (!set->getIsKnownIgnored()) {
+ set->setIsKnown(true);
+ }
+
+ set->setEnabled(true);
+ }
+}
+
+void SetList::markAllAsKnown()
+{
+ for (int i = 0; i < size(); ++i) {
+ CardSetPtr set = at(i);
+ if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
+ set->setIsKnown(true);
+ set->setEnabled(false);
+ } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
+ set->setEnabled(true);
+ }
+ }
+}
+
+void SetList::guessSortKeys()
+{
+ defaultSort();
+ for (int i = 0; i < size(); ++i) {
+ CardSetPtr set = at(i);
+ if (set.isNull()) {
+ qCDebug(CardInfoLog) << "guessSortKeys set is null";
+ continue;
+ }
+ set->setSortKey(i);
+ }
+}
+
+void SetList::defaultSort()
+{
+ std::sort(begin(), end(), [](const CardSetPtr &a, const CardSetPtr &b) {
+ // Sort by priority, then by release date, then by short name
+ if (a->getPriority() != b->getPriority()) {
+ return a->getPriority() < b->getPriority(); // lowest first
+ } else if (a->getReleaseDate() != b->getReleaseDate()) {
+ return a->getReleaseDate() > b->getReleaseDate(); // most recent first
+ } else {
+ return a->getShortName() < b->getShortName(); // alphabetically
+ }
+ });
+}
+
+CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set)
+{
+}
+
+CardInfo::CardInfo(const QString &_name,
+ const QString &_text,
+ bool _isToken,
+ QVariantHash _properties,
+ const QList &_relatedCards,
+ const QList &_reverseRelatedCards,
+ CardInfoPerSetMap _sets,
+ bool _cipt,
+ bool _landscapeOrientation,
+ int _tableRow,
+ bool _upsideDownArt)
+ : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
+ reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt),
+ landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt)
+{
+ pixmapCacheKey = QLatin1String("card_") + name;
+ simpleName = CardInfo::simplifyName(name);
+
+ refreshCachedSetNames();
+}
+
+CardInfo::~CardInfo()
+{
+ PictureLoader::clearPixmapCache(smartThis);
+}
+
+CardInfoPtr CardInfo::newInstance(const QString &_name)
+{
+ return newInstance(_name, QString(), false, QVariantHash(), QList(), QList(),
+ CardInfoPerSetMap(), false, false, 0, false);
+}
+
+CardInfoPtr CardInfo::newInstance(const QString &_name,
+ const QString &_text,
+ bool _isToken,
+ QVariantHash _properties,
+ const QList &_relatedCards,
+ const QList &_reverseRelatedCards,
+ CardInfoPerSetMap _sets,
+ bool _cipt,
+ bool _landscapeOrientation,
+ int _tableRow,
+ bool _upsideDownArt)
+{
+ CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards,
+ _sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt));
+ ptr->setSmartPointer(ptr);
+
+ for (const auto &cardInfoPerSetList : _sets) {
+ for (const CardInfoPerSet &set : cardInfoPerSetList) {
+ set.getPtr()->append(ptr);
+ break;
+ }
+ }
+
+ return ptr;
+}
+
+QString CardInfo::getCorrectedName() const
+{
+ // remove all the characters reserved in windows file paths,
+ // other oses only disallow a subset of these so it covers all
+ static const QRegularExpression rmrx(R"(( // |[*<>:"\\?\x00-\x08\x10-\x1f]))");
+ static const QRegularExpression spacerx(R"([/\x09-\x0f])");
+ static const QString space(' ');
+ QString result = name;
+ // Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental?
+ return result.remove(rmrx).replace(spacerx, space);
+}
+
+void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info)
+{
+ _set->append(smartThis);
+ sets[_set->getShortName()].append(_info);
+
+ refreshCachedSetNames();
+}
+
+void CardInfo::combineLegalities(const QVariantHash &props)
+{
+ QHashIterator it(props);
+ while (it.hasNext()) {
+ it.next();
+ if (it.key().startsWith("format-")) {
+ smartThis->setProperty(it.key(), it.value().toString());
+ }
+ }
+}
+
+void CardInfo::refreshCachedSetNames()
+{
+ QStringList setList;
+ // update the cached list of set names
+ for (const auto &cardInfoPerSetList : sets) {
+ for (const auto &set : cardInfoPerSetList) {
+ if (set.getPtr()->getEnabled()) {
+ setList << set.getPtr()->getShortName();
+ }
+ break;
+ }
+ }
+ setsNames = setList.join(", ");
+}
+
+QString CardInfo::simplifyName(const QString &name)
+{
+ static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)");
+ static const QRegularExpression nonAlnum("[^a-z0-9]");
+
+ QString simpleName = name.toLower();
+
+ // remove spaces and right halves of split cards
+ simpleName.remove(spaceOrSplit);
+
+ // So Aetherling would work, but not Ætherling since 'Æ' would get replaced
+ // with nothing.
+ simpleName.replace("æ", "ae");
+
+ // Replace Jötun Grunt with Jotun Grunt.
+ simpleName = simpleName.normalized(QString::NormalizationForm_KD);
+
+ // remove all non alphanumeric characters from the name
+ simpleName.remove(nonAlnum);
+ return simpleName;
+}
+
+const QChar CardInfo::getColorChar() const
+{
+ QString colors = getColors();
+ switch (colors.size()) {
+ case 0:
+ return QChar();
+ case 1:
+ return colors.at(0);
+ default:
+ return QChar('m');
+ }
+}
+
+CardRelation::CardRelation(const QString &_name,
+ AttachType _attachType,
+ bool _isCreateAllExclusion,
+ bool _isVariableCount,
+ int _defaultCount,
+ bool _isPersistent)
+ : name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion),
+ isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent)
+{
+}
+
+void CardInfo::resetReverseRelatedCards2Me()
+{
+ for (CardRelation *cardRelation : this->getReverseRelatedCards2Me()) {
+ cardRelation->deleteLater();
+ }
+ reverseRelatedCardsToMe = QList();
+}
+
+// Back-compatibility methods. Remove ASAP
+const QString CardInfo::getCardType() const
+{
+ return getProperty(Mtg::CardType);
+}
+void CardInfo::setCardType(const QString &value)
+{
+ setProperty(Mtg::CardType, value);
+}
+const QString CardInfo::getCmc() const
+{
+ return getProperty(Mtg::ConvertedManaCost);
+}
+const QString CardInfo::getColors() const
+{
+ return getProperty(Mtg::Colors);
+}
+void CardInfo::setColors(const QString &value)
+{
+ setProperty(Mtg::Colors, value);
+}
+const QString CardInfo::getLoyalty() const
+{
+ return getProperty(Mtg::Loyalty);
+}
+const QString CardInfo::getMainCardType() const
+{
+ return getProperty(Mtg::MainCardType);
+}
+const QString CardInfo::getManaCost() const
+{
+ return getProperty(Mtg::ManaCost);
+}
+const QString CardInfo::getPowTough() const
+{
+ return getProperty(Mtg::PowTough);
+}
+void CardInfo::setPowTough(const QString &value)
+{
+ setProperty(Mtg::PowTough, value);
+}
diff --git a/cockatrice/src/game/cards/card_info.h b/cockatrice/src/game/cards/card_info.h
new file mode 100644
index 000000000..9d06bc5f6
--- /dev/null
+++ b/cockatrice/src/game/cards/card_info.h
@@ -0,0 +1,491 @@
+#ifndef CARD_INFO_H
+#define CARD_INFO_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+inline Q_LOGGING_CATEGORY(CardInfoLog, "card_info");
+
+class CardInfo;
+class CardInfoPerSet;
+class CardSet;
+class CardRelation;
+class ICardDatabaseParser;
+
+typedef QMap QStringMap;
+typedef QSharedPointer CardInfoPtr;
+typedef QSharedPointer CardSetPtr;
+typedef QMap> CardInfoPerSetMap;
+
+Q_DECLARE_METATYPE(CardInfoPtr)
+
+class CardSet : public QList
+{
+public:
+ enum Priority
+ {
+ PriorityFallback = 0,
+ PriorityPrimary = 10,
+ PrioritySecondary = 20,
+ PriorityReprint = 30,
+ PriorityOther = 40,
+ PriorityLowest = 100,
+ };
+
+private:
+ QString shortName, longName;
+ unsigned int sortKey;
+ QDate releaseDate;
+ QString setType;
+ Priority priority;
+ bool enabled, isknown;
+
+public:
+ explicit CardSet(const QString &_shortName = QString(),
+ const QString &_longName = QString(),
+ const QString &_setType = QString(),
+ const QDate &_releaseDate = QDate(),
+ const Priority _priority = PriorityFallback);
+ static CardSetPtr newInstance(const QString &_shortName = QString(),
+ const QString &_longName = QString(),
+ const QString &_setType = QString(),
+ const QDate &_releaseDate = QDate(),
+ const Priority _priority = PriorityFallback);
+ QString getCorrectedShortName() const;
+ QString getShortName() const
+ {
+ return shortName;
+ }
+ QString getLongName() const
+ {
+ return longName;
+ }
+ QString getSetType() const
+ {
+ return setType;
+ }
+ QDate getReleaseDate() const
+ {
+ return releaseDate;
+ }
+ Priority getPriority() const
+ {
+ return priority;
+ }
+ void setLongName(const QString &_longName)
+ {
+ longName = _longName;
+ }
+ void setSetType(const QString &_setType)
+ {
+ setType = _setType;
+ }
+ void setReleaseDate(const QDate &_releaseDate)
+ {
+ releaseDate = _releaseDate;
+ }
+ void setPriority(const Priority _priority)
+ {
+ priority = _priority;
+ }
+
+ void loadSetOptions();
+ int getSortKey() const
+ {
+ return sortKey;
+ }
+ void setSortKey(unsigned int _sortKey);
+ bool getEnabled() const
+ {
+ return enabled;
+ }
+ void setEnabled(bool _enabled);
+ bool getIsKnown() const
+ {
+ return isknown;
+ }
+ void setIsKnown(bool _isknown);
+
+ // Determine incomplete sets.
+ bool getIsKnownIgnored() const
+ {
+ return longName.length() + setType.length() + releaseDate.toString().length() == 0;
+ }
+};
+
+class SetList : public QList
+{
+private:
+ class KeyCompareFunctor;
+
+public:
+ void sortByKey();
+ void guessSortKeys();
+ void enableAllUnknown();
+ void enableAll();
+ void markAllAsKnown();
+ int getEnabledSetsNum();
+ int getUnknownSetsNum();
+ QStringList getUnknownSetsNames();
+ void defaultSort();
+};
+
+class CardInfoPerSet
+{
+public:
+ explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer(nullptr));
+ ~CardInfoPerSet() = default;
+
+ bool operator==(const CardInfoPerSet &other) const
+ {
+ return this->set == other.set && this->properties == other.properties;
+ }
+
+private:
+ CardSetPtr set;
+ // per-set card properties;
+ QVariantHash properties;
+
+public:
+ const CardSetPtr getPtr() const
+ {
+ return set;
+ }
+ const QStringList getProperties() const
+ {
+ return properties.keys();
+ }
+ const QString getProperty(const QString &propertyName) const
+ {
+ return properties.value(propertyName).toString();
+ }
+ void setProperty(const QString &_name, const QString &_value)
+ {
+ properties.insert(_name, _value);
+ }
+};
+
+class CardInfo : public QObject
+{
+ Q_OBJECT
+private:
+ CardInfoPtr smartThis;
+ // The card name
+ QString name;
+ // The name without punctuation or capitalization, for better card name recognition.
+ QString simpleName;
+ // The key used to identify this card in the cache
+ QString pixmapCacheKey;
+ // card text
+ QString text;
+ // whether this is not a "real" card but a token
+ bool isToken;
+ // basic card properties; common for all the sets
+ QVariantHash properties;
+ // the cards i'm related to
+ QList relatedCards;
+ // the card i'm reverse-related to
+ QList reverseRelatedCards;
+ // the cards thare are reverse-related to me
+ QList reverseRelatedCardsToMe;
+ // card sets
+ CardInfoPerSetMap sets;
+ // cached set names
+ QString setsNames;
+ // positioning properties; used by UI
+ bool cipt;
+ bool landscapeOrientation;
+ int tableRow;
+ bool upsideDownArt;
+
+public:
+ explicit CardInfo(const QString &_name,
+ const QString &_text,
+ bool _isToken,
+ QVariantHash _properties,
+ const QList &_relatedCards,
+ const QList &_reverseRelatedCards,
+ CardInfoPerSetMap _sets,
+ bool _cipt,
+ bool _landscapeOrientation,
+ int _tableRow,
+ bool _upsideDownArt);
+ CardInfo(const CardInfo &other)
+ : QObject(other.parent()), name(other.name), simpleName(other.simpleName), pixmapCacheKey(other.pixmapCacheKey),
+ text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
+ reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
+ sets(other.sets), setsNames(other.setsNames), cipt(other.cipt),
+ landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt)
+ {
+ }
+ ~CardInfo() override;
+
+ static CardInfoPtr newInstance(const QString &_name);
+
+ static CardInfoPtr newInstance(const QString &_name,
+ const QString &_text,
+ bool _isToken,
+ QVariantHash _properties,
+ const QList &_relatedCards,
+ const QList &_reverseRelatedCards,
+ CardInfoPerSetMap _sets,
+ bool _cipt,
+ bool _landscapeOrientation,
+ int _tableRow,
+ bool _upsideDownArt);
+
+ CardInfoPtr clone() const
+ {
+ // Use the copy constructor to create a new instance
+ CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this));
+ newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance
+ return newCardInfo;
+ }
+
+ void setSmartPointer(CardInfoPtr _ptr)
+ {
+ smartThis = std::move(_ptr);
+ }
+
+ // basic properties
+ inline const QString &getName() const
+ {
+ return name;
+ }
+ const QString &getSimpleName() const
+ {
+ return simpleName;
+ }
+ void setPixmapCacheKey(QString _pixmapCacheKey)
+ {
+ pixmapCacheKey = _pixmapCacheKey;
+ }
+ const QString &getPixmapCacheKey() const
+ {
+ return pixmapCacheKey;
+ }
+
+ const QString &getText() const
+ {
+ return text;
+ }
+ void setText(const QString &_text)
+ {
+ text = _text;
+ emit cardInfoChanged(smartThis);
+ }
+
+ bool getIsToken() const
+ {
+ return isToken;
+ }
+ const QStringList getProperties() const
+ {
+ return properties.keys();
+ }
+ const QString getProperty(const QString &propertyName) const
+ {
+ return properties.value(propertyName).toString();
+ }
+ void setProperty(const QString &_name, const QString &_value)
+ {
+ properties.insert(_name, _value);
+ emit cardInfoChanged(smartThis);
+ }
+ bool hasProperty(const QString &propertyName) const
+ {
+ return properties.contains(propertyName);
+ }
+ const CardInfoPerSetMap &getSets() const
+ {
+ return sets;
+ }
+ const QString &getSetsNames() const
+ {
+ return setsNames;
+ }
+ const QString getSetProperty(const QString &setName, const QString &propertyName) const
+ {
+ if (!sets.contains(setName))
+ return "";
+
+ for (const auto &set : sets[setName]) {
+ if (QLatin1String("card_") + this->getName() + QString("_") + QString(set.getProperty("uuid")) ==
+ this->getPixmapCacheKey()) {
+ return set.getProperty(propertyName);
+ }
+ }
+
+ return sets[setName][0].getProperty(propertyName);
+ }
+
+ // related cards
+ const QList &getRelatedCards() const
+ {
+ return relatedCards;
+ }
+ const QList &getReverseRelatedCards() const
+ {
+ return reverseRelatedCards;
+ }
+ const QList &getReverseRelatedCards2Me() const
+ {
+ return reverseRelatedCardsToMe;
+ }
+ const QList getAllRelatedCards() const
+ {
+ QList result;
+ result.append(getRelatedCards());
+ result.append(getReverseRelatedCards2Me());
+ return result;
+ }
+ void resetReverseRelatedCards2Me();
+ void addReverseRelatedCards2Me(CardRelation *cardRelation)
+ {
+ reverseRelatedCardsToMe.append(cardRelation);
+ }
+
+ // positioning
+ bool getCipt() const
+ {
+ return cipt;
+ }
+ bool getLandscapeOrientation() const
+ {
+ return landscapeOrientation;
+ }
+ int getTableRow() const
+ {
+ return tableRow;
+ }
+ void setTableRow(int _tableRow)
+ {
+ tableRow = _tableRow;
+ }
+ bool getUpsideDownArt() const
+ {
+ return upsideDownArt;
+ }
+ const QChar getColorChar() const;
+
+ // Back-compatibility methods. Remove ASAP
+ const QString getCardType() const;
+ void setCardType(const QString &value);
+ const QString getCmc() const;
+ const QString getColors() const;
+ void setColors(const QString &value);
+ const QString getLoyalty() const;
+ const QString getMainCardType() const;
+ const QString getManaCost() const;
+ const QString getPowTough() const;
+ void setPowTough(const QString &value);
+
+ // methods using per-set properties
+ QString getCustomPicURL(const QString &set) const
+ {
+ return getSetProperty(set, "picurl");
+ }
+ QString getCorrectedName() const;
+ void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet());
+ void combineLegalities(const QVariantHash &props);
+ void emitPixmapUpdated()
+ {
+ emit pixmapUpdated();
+ }
+ void refreshCachedSetNames();
+
+ /**
+ * Simplify a name to have no punctuation and lowercase all letters, for
+ * less strict name-matching.
+ */
+ static QString simplifyName(const QString &name);
+
+signals:
+ void pixmapUpdated();
+ void cardInfoChanged(CardInfoPtr card);
+};
+
+class CardRelation : public QObject
+{
+ Q_OBJECT
+public:
+ enum AttachType
+ {
+ DoesNotAttach = 0,
+ AttachTo = 1,
+ TransformInto = 2,
+ };
+
+private:
+ QString name;
+ AttachType attachType;
+ bool isCreateAllExclusion;
+ bool isVariableCount;
+ int defaultCount;
+ bool isPersistent;
+
+public:
+ explicit CardRelation(const QString &_name = QString(),
+ AttachType _attachType = DoesNotAttach,
+ bool _isCreateAllExclusion = false,
+ bool _isVariableCount = false,
+ int _defaultCount = 1,
+ bool _isPersistent = false);
+
+ inline const QString &getName() const
+ {
+ return name;
+ }
+ AttachType getAttachType() const
+ {
+ return attachType;
+ }
+ bool getDoesAttach() const
+ {
+ return attachType != DoesNotAttach;
+ }
+ bool getDoesTransform() const
+ {
+ return attachType == TransformInto;
+ }
+ QString getAttachTypeAsString() const
+ {
+ switch (attachType) {
+ case AttachTo:
+ return "attach";
+ case TransformInto:
+ return "transform";
+ default:
+ return "";
+ }
+ }
+ bool getCanCreateAnother() const
+ {
+ return !getDoesAttach();
+ }
+ bool getIsCreateAllExclusion() const
+ {
+ return isCreateAllExclusion;
+ }
+ bool getIsVariable() const
+ {
+ return isVariableCount;
+ }
+ int getDefaultCount() const
+ {
+ return defaultCount;
+ }
+ bool getIsPersistent() const
+ {
+ return isPersistent;
+ }
+};
+#endif
diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp
index 19cff1df1..c088a4ed1 100644
--- a/cockatrice/src/game/cards/card_item.cpp
+++ b/cockatrice/src/game/cards/card_item.cpp
@@ -22,10 +22,9 @@ CardItem::CardItem(Player *_owner,
const QString &_name,
const QString &_providerId,
int _cardid,
- bool _revealedCard,
CardZone *_zone)
- : AbstractCardItem(parent, _name, _providerId, _owner, _cardid), zone(_zone), revealedCard(_revealedCard),
- attacking(false), destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
+ : AbstractCardItem(parent, _name, _providerId, _owner, _cardid), zone(_zone), attacking(false),
+ destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
{
owner->addCard(this);
@@ -483,11 +482,9 @@ QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value)
owner->setCardMenu(cardMenu);
owner->getGame()->setActiveCard(this);
} else if (owner->getCardMenu() == cardMenu) {
- if (scene() && scene()->selectedItems().isEmpty()) {
- owner->setCardMenu(nullptr);
- }
+ owner->setCardMenu(nullptr);
owner->getGame()->setActiveCard(nullptr);
}
}
- return QGraphicsItem::itemChange(change, value);
+ return AbstractCardItem::itemChange(change, value);
}
diff --git a/cockatrice/src/game/cards/card_item.h b/cockatrice/src/game/cards/card_item.h
index 2c77af20e..5b4b325ba 100644
--- a/cockatrice/src/game/cards/card_item.h
+++ b/cockatrice/src/game/cards/card_item.h
@@ -22,7 +22,6 @@ class CardItem : public AbstractCardItem
Q_OBJECT
private:
CardZone *zone;
- bool revealedCard;
bool attacking;
QMap counters;
QString annotation;
@@ -55,7 +54,6 @@ public:
const QString &_name = QString(),
const QString &_providerId = QString(),
int _cardid = -1,
- bool revealedCard = false,
CardZone *_zone = nullptr);
~CardItem() override;
void retranslateUi();
@@ -85,10 +83,6 @@ public:
{
owner = _owner;
}
- bool getRevealedCard() const
- {
- return revealedCard;
- }
bool getAttacking() const
{
return attacking;
diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp
index 920703d63..41c6ed45a 100644
--- a/cockatrice/src/game/deckview/deck_view.cpp
+++ b/cockatrice/src/game/deckview/deck_view.cpp
@@ -74,6 +74,12 @@ DeckViewCard::DeckViewCard(QGraphicsItem *parent,
: AbstractCardItem(parent, _name, _providerId, 0, -1), originZone(_originZone), dragItem(0)
{
setAcceptHoverEvents(true);
+
+ connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
+ Q_UNUSED(_roundCardCorners);
+
+ update();
+ });
}
DeckViewCard::~DeckViewCard()
@@ -91,7 +97,7 @@ void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
pen.setJoinStyle(Qt::MiterJoin);
pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red);
painter->setPen(pen);
- qreal cardRadius = 0.05 * (CARD_WIDTH - 3);
+ qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CARD_WIDTH - 3) : 0.0;
painter->drawRoundedRect(QRectF(1.5, 1.5, CARD_WIDTH - 3., CARD_HEIGHT - 3.), cardRadius, cardRadius);
painter->restore();
}
@@ -178,7 +184,7 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI
{
qreal totalTextWidth = getCardTypeTextWidth();
- painter->fillRect(boundingRect(), themeManager->getTableBgBrush());
+ painter->fillRect(boundingRect(), themeManager->getBgBrush(ThemeManager::Table));
painter->setPen(QColor(255, 255, 255, 100));
painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
@@ -525,4 +531,4 @@ void DeckView::clearDeck()
void DeckView::resetSideboardPlan()
{
deckViewScene->resetSideboardPlan();
-}
\ No newline at end of file
+}
diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp
index bcd18d23a..8244f35c0 100644
--- a/cockatrice/src/game/player/player.cpp
+++ b/cockatrice/src/game/player/player.cpp
@@ -93,12 +93,7 @@ void PlayerArea::updateBg()
void PlayerArea::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
- QBrush brush = themeManager->getPlayerBgBrush();
-
- if (playerZoneId > 0) {
- // If the extra image is not found, load the default one
- brush = themeManager->getExtraPlayerBgBrush(QString::number(playerZoneId), brush);
- }
+ QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Player, playerZoneId);
painter->fillRect(boundingRect(), brush);
}
@@ -1448,19 +1443,20 @@ void Player::actMoveTopCardsUntil()
{
stopMoveTopCardsUntil();
- DlgMoveTopCardsUntil dlg(game, movingCardsUntilExpr, movingCardsUntilNumberOfHits, movingCardsUntilAutoPlay);
+ DlgMoveTopCardsUntil dlg(game, movingCardsUntilExprs, movingCardsUntilNumberOfHits, movingCardsUntilAutoPlay);
if (!dlg.exec()) {
return;
}
- movingCardsUntilExpr = dlg.getExpr();
+ auto expr = dlg.getExpr();
+ movingCardsUntilExprs = dlg.getExprs();
movingCardsUntilNumberOfHits = dlg.getNumberOfHits();
movingCardsUntilAutoPlay = dlg.isAutoPlay();
if (zones.value("deck")->getCards().empty()) {
stopMoveTopCardsUntil();
} else {
- movingCardsUntilFilter = FilterString(movingCardsUntilExpr);
+ movingCardsUntilFilter = FilterString(expr);
movingCardsUntilCounter = movingCardsUntilNumberOfHits;
movingCardsUntil = true;
actMoveTopCardToPlay();
@@ -2069,7 +2065,6 @@ void Player::createCard(const CardItem *sourceCard,
cmd.set_target_zone("table"); // We currently only support creating tokens on the table
cmd.set_target_card_id(sourceCard->getId());
cmd.set_target_mode(Command_CreateToken::ATTACH_TO);
- cmd.set_card_provider_id(sourceCard->getProviderId().toStdString());
break;
case CardRelation::TransformInto:
diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h
index 66f3089ed..26df3ae50 100644
--- a/cockatrice/src/game/player/player.h
+++ b/cockatrice/src/game/player/player.h
@@ -279,7 +279,7 @@ private:
bool movingCardsUntil;
QTimer *moveTopCardTimer;
- QString movingCardsUntilExpr = {};
+ QStringList movingCardsUntilExprs = {};
int movingCardsUntilNumberOfHits = 1;
bool movingCardsUntilAutoPlay = false;
FilterString movingCardsUntilFilter;
diff --git a/cockatrice/src/game/zones/hand_zone.cpp b/cockatrice/src/game/zones/hand_zone.cpp
index 896e9b60f..31f0515d4 100644
--- a/cockatrice/src/game/zones/hand_zone.cpp
+++ b/cockatrice/src/game/zones/hand_zone.cpp
@@ -78,12 +78,7 @@ QRectF HandZone::boundingRect() const
void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
- QBrush brush = themeManager->getHandBgBrush();
-
- if (player->getZoneId() > 0) {
- // If the extra image is not found, load the default one
- brush = themeManager->getExtraHandBgBrush(QString::number(player->getZoneId()), brush);
- }
+ QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Hand, player->getZoneId());
painter->fillRect(boundingRect(), brush);
}
diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp
index 08ee3d28e..ba23560c9 100644
--- a/cockatrice/src/game/zones/pile_zone.cpp
+++ b/cockatrice/src/game/zones/pile_zone.cpp
@@ -21,6 +21,13 @@ PileZone::PileZone(Player *_p, const QString &_name, bool _isShufflable, bool _c
.translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2)
.rotate(90)
.translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2));
+
+ connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
+ Q_UNUSED(_roundCardCorners);
+
+ prepareGeometryChange();
+ update();
+ });
}
QRectF PileZone::boundingRect() const
@@ -31,7 +38,8 @@ QRectF PileZone::boundingRect() const
QPainterPath PileZone::shape() const
{
QPainterPath shape;
- shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH);
+ qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
+ shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp
index 189a0a551..b597a9e81 100644
--- a/cockatrice/src/game/zones/stack_zone.cpp
+++ b/cockatrice/src/game/zones/stack_zone.cpp
@@ -49,12 +49,7 @@ QRectF StackZone::boundingRect() const
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
- QBrush brush = themeManager->getStackBgBrush();
-
- if (player->getZoneId() > 0) {
- // If the extra image is not found, load the default one
- brush = themeManager->getExtraStackBgBrush(QString::number(player->getZoneId()), brush);
- }
+ QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, player->getZoneId());
painter->fillRect(boundingRect(), brush);
}
@@ -71,26 +66,25 @@ void StackZone::handleDropEvent(const QList &dragItems, CardZone
cmd.set_target_zone(getName().toStdString());
int index = 0;
- if (cards.isEmpty()) {
- index = 0;
- } else {
+
+ if (!cards.isEmpty()) {
const auto cardCount = static_cast(cards.size());
const auto &card = cards.at(0);
- if (card == nullptr) {
- qCWarning(StackZoneLog) << "Attempted to move card from" << startZone->getName() << ", but was null";
- return;
- }
-
index = qRound(divideCardSpaceInZone(dropPoint.y(), cardCount, boundingRect().height(),
card->boundingRect().height(), true));
- }
- if (startZone == this) {
- const auto &dragItem = dragItems.at(0);
- const auto &card = cards.at(index);
- if (card != nullptr && dragItem != nullptr && card->getId() == dragItem->getId()) {
- return;
+ // divideCardSpaceInZone is not guaranteed to return a valid index
+ // currently, so clamp it to avoid crashes.
+ index = qBound(0, index, cardCount - 1);
+
+ if (startZone == this) {
+ const auto &dragItem = dragItems.at(0);
+ const auto &card = cards.at(index);
+
+ if (card->getId() == dragItem->getId()) {
+ return;
+ }
}
}
@@ -109,8 +103,6 @@ void StackZone::handleDropEvent(const QList &dragItems, CardZone
void StackZone::reorganizeCards()
{
if (!cards.isEmpty()) {
- QSet arrowsToUpdate;
-
const auto cardCount = static_cast(cards.size());
qreal totalWidth = boundingRect().width();
qreal cardWidth = cards.at(0)->boundingRect().width();
@@ -125,16 +117,6 @@ void StackZone::reorganizeCards()
divideCardSpaceInZone(i, cardCount, boundingRect().height(), cards.at(0)->boundingRect().height());
card->setPos(x, y);
card->setRealZValue(i);
-
- for (ArrowItem *item : card->getArrowsFrom()) {
- arrowsToUpdate.insert(item);
- }
- for (ArrowItem *item : card->getArrowsTo()) {
- arrowsToUpdate.insert(item);
- }
- }
- for (ArrowItem *item : arrowsToUpdate) {
- item->updatePath();
}
}
update();
diff --git a/cockatrice/src/game/zones/stack_zone.h b/cockatrice/src/game/zones/stack_zone.h
index 4ef06ea9a..38dee0aca 100644
--- a/cockatrice/src/game/zones/stack_zone.h
+++ b/cockatrice/src/game/zones/stack_zone.h
@@ -3,8 +3,6 @@
#include "select_zone.h"
-inline Q_LOGGING_CATEGORY(StackZoneLog, "stack_zone");
-
class StackZone : public SelectZone
{
Q_OBJECT
diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp
index 686cb3588..71327b9ec 100644
--- a/cockatrice/src/game/zones/table_zone.cpp
+++ b/cockatrice/src/game/zones/table_zone.cpp
@@ -54,12 +54,7 @@ bool TableZone::isInverted() const
void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
- QBrush brush = themeManager->getTableBgBrush();
-
- if (player->getZoneId() > 0) {
- // If the extra image is not found, load the default one
- brush = themeManager->getExtraTableBgBrush(QString::number(player->getZoneId()), brush);
- }
+ QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, player->getZoneId());
painter->fillRect(boundingRect(), brush);
if (active) {
@@ -157,8 +152,6 @@ void TableZone::handleDropEventByGrid(const QList &dragItems,
void TableZone::reorganizeCards()
{
- QSet arrowsToUpdate;
-
// Calculate card stack widths so mapping functions work properly
computeCardStackWidths();
@@ -189,23 +182,7 @@ void TableZone::reorganizeCards()
qreal childY = y + 5;
attachedCard->setPos(childX, childY);
attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100);
- for (ArrowItem *item : attachedCard->getArrowsFrom()) {
- arrowsToUpdate.insert(item);
- }
- for (ArrowItem *item : attachedCard->getArrowsTo()) {
- arrowsToUpdate.insert(item);
- }
}
-
- for (ArrowItem *item : cards[i]->getArrowsFrom()) {
- arrowsToUpdate.insert(item);
- }
- for (ArrowItem *item : cards[i]->getArrowsTo()) {
- arrowsToUpdate.insert(item);
- }
- }
- for (ArrowItem *item : arrowsToUpdate) {
- item->updatePath();
}
resizeToContents();
diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp
index d86e71f90..9e9ba6e81 100644
--- a/cockatrice/src/game/zones/view_zone.cpp
+++ b/cockatrice/src/game/zones/view_zone.cpp
@@ -70,7 +70,7 @@ void ZoneViewZone::initializeCards(const QList &cardLis
if (!cardList.isEmpty()) {
for (int i = 0; i < cardList.size(); ++i)
addCard(new CardItem(player, this, QString::fromStdString(cardList[i]->name()),
- QString::fromStdString(cardList[i]->provider_id()), cardList[i]->id(), revealZone),
+ QString::fromStdString(cardList[i]->provider_id()), cardList[i]->id()),
false, i);
reorganizeCards();
} else if (!origZone->contentsKnown()) {
@@ -88,8 +88,7 @@ void ZoneViewZone::initializeCards(const QList &cardLis
int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size());
for (int i = 0; i < number; i++) {
CardItem *card = c.at(i);
- addCard(new CardItem(player, this, card->getName(), card->getProviderId(), card->getId(), revealZone),
- false, i);
+ addCard(new CardItem(player, this, card->getName(), card->getProviderId(), card->getId()), false, i);
}
reorganizeCards();
}
@@ -103,7 +102,7 @@ void ZoneViewZone::zoneDumpReceived(const Response &r)
const ServerInfo_Card &cardInfo = resp.zone_info().card_list(i);
auto cardName = QString::fromStdString(cardInfo.name());
auto cardProviderId = QString::fromStdString(cardInfo.provider_id());
- auto *card = new CardItem(player, this, cardName, cardProviderId, cardInfo.id(), revealZone, this);
+ auto *card = new CardItem(player, this, cardName, cardProviderId, cardInfo.id(), this);
cards.insert(i, card);
}
diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp
index 4ba165284..a4a7a7cd7 100644
--- a/cockatrice/src/settings/cache_settings.cpp
+++ b/cockatrice/src/settings/cache_settings.cpp
@@ -254,11 +254,13 @@ SettingsCache::SettingsCache()
showShortcuts = settings->value("menu/showshortcuts", true).toBool();
displayCardNames = settings->value("cards/displaycardnames", true).toBool();
+ roundCardCorners = settings->value("cards/roundcardcorners", true).toBool();
overrideAllCardArtWithPersonalPreference =
settings->value("cards/overrideallcardartwithpersonalpreference", false).toBool();
bumpSetsWithCardsInDeckToTop = settings->value("cards/bumpsetswithcardsindecktotop", true).toBool();
printingSelectorSortOrder = settings->value("cards/printingselectorsortorder", 1).toInt();
printingSelectorCardSize = settings->value("cards/printingselectorcardsize", 100).toInt();
+ includeOnlineOnlyCards = settings->value("cards/includeonlineonlycards", false).toBool();
printingSelectorNavigationButtonsVisible =
settings->value("cards/printingselectornavigationbuttonsvisible", true).toBool();
visualDeckStorageCardSize = settings->value("interface/visualdeckstoragecardsize", 100).toInt();
@@ -661,6 +663,13 @@ void SettingsCache::setPrintingSelectorCardSize(int _printingSelectorCardSize)
emit printingSelectorCardSizeChanged();
}
+void SettingsCache::setIncludeOnlineOnlyCards(bool _includeOnlineOnlyCards)
+{
+ includeOnlineOnlyCards = _includeOnlineOnlyCards;
+ settings->setValue("cards/includeonlineonlycards", includeOnlineOnlyCards);
+ emit includeOnlineOnlyCardsChanged(includeOnlineOnlyCards);
+}
+
void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible)
{
printingSelectorNavigationButtonsVisible = _navigationButtonsVisible;
@@ -728,6 +737,7 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visual
visualDeckStorageUnusedColorIdentitiesOpacity = _visualDeckStorageUnusedColorIdentitiesOpacity;
settings->setValue("interface/visualdeckstorageunusedcoloridentitiesopacity",
visualDeckStorageUnusedColorIdentitiesOpacity);
+ emit visualDeckStorageUnusedColorIdentitiesOpacityChanged(visualDeckStorageUnusedColorIdentitiesOpacity);
}
void SettingsCache::setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion)
@@ -1292,6 +1302,16 @@ void SettingsCache::setMaxFontSize(int _max)
settings->setValue("game/maxfontsize", maxFontSize);
}
+void SettingsCache::setRoundCardCorners(bool _roundCardCorners)
+{
+ if (_roundCardCorners == roundCardCorners)
+ return;
+
+ roundCardCorners = _roundCardCorners;
+ settings->setValue("cards/roundcardcorners", _roundCardCorners);
+ emit roundCardCornersChanged(roundCardCorners);
+}
+
void SettingsCache::loadPaths()
{
QString dataPath = getDataPath();
diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h
index 28f77f1b0..2556a507d 100644
--- a/cockatrice/src/settings/cache_settings.h
+++ b/cockatrice/src/settings/cache_settings.h
@@ -47,6 +47,7 @@ class QSettings;
class SettingsCache : public QObject
{
Q_OBJECT
+
signals:
void langChanged();
void picsPathChanged();
@@ -58,12 +59,14 @@ signals:
void bumpSetsWithCardsInDeckToTopChanged();
void printingSelectorSortOrderChanged();
void printingSelectorCardSizeChanged();
+ void includeOnlineOnlyCardsChanged(bool _includeOnlineOnlyCards);
void printingSelectorNavigationButtonsVisibleChanged();
void visualDeckStorageShowTagFilterChanged(bool _visible);
void visualDeckStorageShowBannerCardComboBoxChanged(bool _visible);
void visualDeckStorageShowTagsOnDeckPreviewsChanged(bool _visible);
void visualDeckStorageCardSizeChanged();
void visualDeckStorageDrawUnusedColorIdentitiesChanged(bool _visible);
+ void visualDeckStorageUnusedColorIdentitiesOpacityChanged(bool value);
void visualDeckStorageInGameChanged(bool enabled);
void horizontalHandChanged();
void handJustificationChanged();
@@ -81,6 +84,7 @@ signals:
void downloadSpoilerTimeIndexChanged();
void downloadSpoilerStatusChanged();
void useTearOffMenusChanged(bool state);
+ void roundCardCornersChanged(bool roundCardCorners);
private:
QSettings *settings;
@@ -127,6 +131,7 @@ private:
bool bumpSetsWithCardsInDeckToTop;
int printingSelectorSortOrder;
int printingSelectorCardSize;
+ bool includeOnlineOnlyCards;
bool printingSelectorNavigationButtonsVisible;
int visualDeckStorageSortingOrder;
bool visualDeckStorageShowFolders;
@@ -201,6 +206,7 @@ private:
QList releaseChannels;
bool isPortableBuild;
bool localTime;
+ bool roundCardCorners;
public:
SettingsCache();
@@ -401,6 +407,10 @@ public:
{
return printingSelectorCardSize;
}
+ bool getIncludeOnlineOnlyCards() const
+ {
+ return includeOnlineOnlyCards;
+ }
bool getPrintingSelectorNavigationButtonsVisible() const
{
return printingSelectorNavigationButtonsVisible;
@@ -731,6 +741,10 @@ public:
{
return mbDownloadSpoilers;
}
+ bool getRoundCardCorners() const
+ {
+ return roundCardCorners;
+ }
static SettingsCache &instance();
void resetPaths();
@@ -778,6 +792,7 @@ public slots:
void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop);
void setPrintingSelectorSortOrder(int _printingSelectorSortOrder);
void setPrintingSelectorCardSize(int _printingSelectorCardSize);
+ void setIncludeOnlineOnlyCards(bool _includeOnlineOnlyCards);
void setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible);
void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder);
void setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value);
@@ -839,6 +854,7 @@ public slots:
void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion);
void setUpdateReleaseChannelIndex(int value);
void setMaxFontSize(int _max);
+ void setRoundCardCorners(bool _roundCardCorners);
};
#endif
diff --git a/cockatrice/src/settings/shortcuts_settings.h b/cockatrice/src/settings/shortcuts_settings.h
index 0dcbcbad0..595932623 100644
--- a/cockatrice/src/settings/shortcuts_settings.h
+++ b/cockatrice/src/settings/shortcuts_settings.h
@@ -173,9 +173,13 @@ private:
{"MainWindow/aWatchReplay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Watch Replay..."),
parseSequenceString(""),
ShortcutGroup::Main_Window)},
- {"TabDeckEditor/aAnalyzeDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck"),
+ {"TabDeckEditor/aAnalyzeDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck (deckstats.net)"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
+ {"TabDeckEditor/aAnalyzeDeckTappedout",
+ ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck (tappedout.net)"),
+ parseSequenceString(""),
+ ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aClearFilterAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Clear All Filters"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
@@ -193,9 +197,14 @@ private:
{"TabDeckEditor/aEditTokens", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Edit Custom Tokens..."),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
- {"TabDeckEditor/aExportDeckDecklist", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Export Deck"),
- parseSequenceString(""),
- ShortcutGroup::Deck_Editor)},
+ {"TabDeckEditor/aExportDeckDecklist",
+ ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Export Deck (decklist.org)"),
+ parseSequenceString(""),
+ ShortcutGroup::Deck_Editor)},
+ {"TabDeckEditor/aExportDeckDecklistXyz",
+ ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Export Deck (decklist.xyz)"),
+ parseSequenceString(""),
+ ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aIncrement", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card"),
parseSequenceString("+"),
ShortcutGroup::Deck_Editor)},
diff --git a/common/decklist.cpp b/common/decklist.cpp
index cf1240d8a..789f57910 100644
--- a/common/decklist.cpp
+++ b/common/decklist.cpp
@@ -548,7 +548,14 @@ bool DeckList::saveToFile_Native(QIODevice *device)
return true;
}
-bool DeckList::loadFromStream_Plain(QTextStream &in)
+/**
+ * Clears the decklist and loads in a new deck from text
+ *
+ * @param in The text to load
+ * @param preserveMetadata If true, don't clear the existing metadata
+ * @return False if the input was empty, true otherwise.
+ */
+bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata)
{
const QRegularExpression reCardLine(R"(^\s*[\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption);
const QRegularExpression reEmpty("^\\s*$");
@@ -577,7 +584,7 @@ bool DeckList::loadFromStream_Plain(QTextStream &in)
{QRegularExpression("æ"), QString("ae")},
{QRegularExpression(" ?[|/]+ ?"), QString(" // ")}};
- cleanList();
+ cleanList(preserveMetadata);
auto inputs = in.readAll().trimmed().split('\n');
auto max_line = inputs.size();
@@ -752,7 +759,7 @@ InnerDecklistNode *DeckList::getZoneObjFromName(const QString &zoneName)
bool DeckList::loadFromFile_Plain(QIODevice *device)
{
QTextStream in(device);
- return loadFromStream_Plain(in);
+ return loadFromStream_Plain(in, false);
}
struct WriteToStream
@@ -801,12 +808,19 @@ QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappe
return result;
}
-void DeckList::cleanList()
+/**
+ * Clears all cards and other data from the decklist
+ *
+ * @param preserveMetadata If true, only clear the cards
+ */
+void DeckList::cleanList(bool preserveMetadata)
{
root->clearTree();
- setName();
- setComments();
- setTags();
+ if (!preserveMetadata) {
+ setName();
+ setComments();
+ setTags();
+ }
refreshDeckHash();
}
diff --git a/common/decklist.h b/common/decklist.h
index 8acbd4945..de839ad23 100644
--- a/common/decklist.h
+++ b/common/decklist.h
@@ -351,13 +351,13 @@ public:
QString writeToString_Native();
bool loadFromFile_Native(QIODevice *device);
bool saveToFile_Native(QIODevice *device);
- bool loadFromStream_Plain(QTextStream &stream);
+ bool loadFromStream_Plain(QTextStream &stream, bool preserveMetadata);
bool loadFromFile_Plain(QIODevice *device);
bool saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards);
bool saveToFile_Plain(QIODevice *device, bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false);
QString writeToString_Plain(bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false);
- void cleanList();
+ void cleanList(bool preserveMetadata = false);
bool isEmpty() const
{
return root->isEmpty() && name.isEmpty() && comments.isEmpty() && sideboardPlans.isEmpty();
diff --git a/dbconverter/CMakeLists.txt b/dbconverter/CMakeLists.txt
index 78fe36350..ca661376e 100644
--- a/dbconverter/CMakeLists.txt
+++ b/dbconverter/CMakeLists.txt
@@ -9,6 +9,7 @@ set(dbconverter_SOURCES
../cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp
../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp
../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp
+ ../cockatrice/src/game/cards/card_info.cpp
../cockatrice/src/settings/settings_manager.cpp
${VERSION_STRING_CPP}
)
diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp
index fa79bf45c..a158c8989 100644
--- a/dbconverter/src/mocks.cpp
+++ b/dbconverter/src/mocks.cpp
@@ -202,6 +202,9 @@ void SettingsCache::setPrintingSelectorSortOrder(int /* _printingSelectorSortOrd
void SettingsCache::setPrintingSelectorCardSize(int /* _printingSelectorCardSize */)
{
}
+void SettingsCache::setIncludeOnlineOnlyCards(bool /* _includeOnlineOnlyCards */)
+{
+}
void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */)
{
}
@@ -387,6 +390,9 @@ void SettingsCache::setUpdateReleaseChannelIndex(int /* value */)
void SettingsCache::setMaxFontSize(int /* _max */)
{
}
+void SettingsCache::setRoundCardCorners(bool /* _roundCardCorners */)
+{
+}
void PictureLoader::clearPixmapCache(CardInfoPtr /* card */)
{
diff --git a/doc/carddatabase_v4/cards.xsd b/doc/carddatabase_v4/cards.xsd
index 00ed72555..6c5eb9bda 100644
--- a/doc/carddatabase_v4/cards.xsd
+++ b/doc/carddatabase_v4/cards.xsd
@@ -28,6 +28,7 @@
+
diff --git a/docker-compose.yml b/docker-compose.yml
index 7cef0ffe0..3d9f9f38f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -20,7 +20,6 @@ services:
depends_on:
- mysql
ports:
- - "4747:4747"
- "4748:4748"
entrypoint: "/bin/bash -c 'sleep 10; servatrice --config /tmp/servatrice.ini --log-to-console'"
restart: always
diff --git a/docker-compose.yml.windows b/docker-compose.yml.windows
index 8634e0241..6663c90fd 100644
--- a/docker-compose.yml.windows
+++ b/docker-compose.yml.windows
@@ -20,7 +20,6 @@ services:
depends_on:
- mysql
ports:
- - "4747:4747"
- "4748:4748"
entrypoint: "/bin/bash -c 'sleep 10; servatrice --config /tmp/servatrice.ini --log-to-console'"
restart: always
diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt
index addfecd78..130dfdaab 100644
--- a/oracle/CMakeLists.txt
+++ b/oracle/CMakeLists.txt
@@ -15,9 +15,11 @@ set(oracle_SOURCES
src/oraclewizard.cpp
src/oracleimporter.cpp
src/pagetemplates.cpp
+ src/parsehelpers.cpp
src/qt-json/json.cpp
../cockatrice/src/game/cards/card_database.cpp
../cockatrice/src/game/cards/card_database_manager.cpp
+ ../cockatrice/src/game/cards/card_info.cpp
../cockatrice/src/client/ui/picture_loader/picture_loader.cpp
../cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp
../cockatrice/src/client/ui/picture_loader/picture_to_load.cpp
diff --git a/oracle/oracle_en@source.ts b/oracle/oracle_en@source.ts
index 433176c57..301de42e3 100644
--- a/oracle/oracle_en@source.ts
+++ b/oracle/oracle_en@source.ts
@@ -1,6 +1,29 @@
+
+ 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
@@ -63,7 +86,8 @@
- Sets JSON file (%1)
+ Sets file (%1)
+ Sets JSON file (%1)
@@ -247,7 +271,7 @@
OracleImporter
-
+ Dummy set containing tokens
@@ -283,6 +307,15 @@
+
+ PictureLoader
+
+
+ en
+ code for scryfall's language property, not available for all languages
+
+
+SaveSetsPage
@@ -357,6 +390,21 @@
+
+ ShortcutsSettings
+
+
+ Your configuration file contained invalid shortcuts.
+Please check your shortcut settings!
+
+
+
+
+ The following shortcuts have been set to default:
+
+
+
+SimpleDownloadFilePage
@@ -392,6 +440,34 @@
+
+ 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
@@ -537,6 +613,7 @@
i18n
+ English
diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp
index 07aad804d..4187c4c9f 100644
--- a/oracle/src/oracleimporter.cpp
+++ b/oracle/src/oracleimporter.cpp
@@ -1,6 +1,7 @@
#include "oracleimporter.h"
#include "game/cards/card_database_parser/cockatrice_xml_4.h"
+#include "parsehelpers.h"
#include "qt-json/json.h"
#include
@@ -157,9 +158,7 @@ CardInfoPtr OracleImporter::addCard(QString name,
// DETECT CARD POSITIONING INFO
// cards that enter the field tapped
- QRegularExpression ciptRegex("( it|" + QRegularExpression::escape(name) +
- ") enters( the battlefield)? tapped(?! unless)");
- bool cipt = ciptRegex.match(text).hasMatch();
+ bool cipt = parseCipt(name, text);
bool landscapeOrientation = properties.value("maintype").toString() == "Battle" ||
properties.value("layout").toString() == "split" ||
@@ -207,12 +206,14 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
{
// mtgjson name => xml name
static const QMap cardProperties{
- {"manaCost", "manacost"}, {"manaValue", "cmc"}, {"type", "type"},
- {"loyalty", "loyalty"}, {"layout", "layout"}, {"side", "side"},
+ {"manaCost", "manacost"}, {"manaValue", "cmc"}, {"type", "type"},
+ {"loyalty", "loyalty"}, {"layout", "layout"}, {"side", "side"},
+ {"convertedManaCost", "cmc"}, // old name for manaValue, for backwards compatibility
};
// mtgjson name => xml name
- static const QMap setInfoProperties{{"number", "num"}, {"rarity", "rarity"}};
+ static const QMap setInfoProperties{
+ {"number", "num"}, {"rarity", "rarity"}, {"isOnlineOnly", "isOnlineOnly"}};
// mtgjson name => xml name
static const QMap identifierProperties{{"multiverseId", "muid"}, {"scryfallId", "uuid"}};
@@ -350,7 +351,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
// add other face for split cards as card relation
if (!getStringPropertyFromMap(card, "side").isEmpty()) {
- properties["cmc"] = getStringPropertyFromMap(card, "faceManaValue");
+ auto faceManaValue = getStringPropertyFromMap(card, "faceManaValue");
+ if (faceManaValue.isEmpty()) {
+ // check the old name for the property, for backwards compatibility purposes
+ faceManaValue = getStringPropertyFromMap(card, "faceConvertedManaCost");
+ }
+ properties["cmc"] = faceManaValue;
+
if (layout == "meld") { // meld cards don't work
static const QRegularExpression meldNameRegex{"then meld them into ([^\\.]*)"};
QString additionalName = meldNameRegex.match(text).captured(1);
diff --git a/oracle/src/parsehelpers.cpp b/oracle/src/parsehelpers.cpp
new file mode 100644
index 000000000..a97bf67c9
--- /dev/null
+++ b/oracle/src/parsehelpers.cpp
@@ -0,0 +1,69 @@
+#include "parsehelpers.h"
+
+#include
+#include
+
+/**
+ * Parses the card text to determine if the card should have the cipt tag
+ *
+ * The parsing logic is able to handle the following cases:
+ * - " enters tapped"
+ * - " enters tapped", if the card name starts with the shortname
+ * - "This enters tapped"
+ * - "..., it enters tapped"
+ * - Any naming scheme that appends a non-alphanumeric character plus extra text to the end of the name.
+ * (e.g. name is "Card Name_SET" or "Card Name (Set)" and text contains "Card Name enters tapped")
+ *
+ * However, it will still miss on certain cases:
+ * - shortnames that aren't the at the beginning of the card name
+ *
+ * Note that "...enters tapped unless..." returns false.
+ *
+ * @param name The name of the card
+ * @param text The oracle text of the card
+ */
+bool parseCipt(const QString &name, const QString &text)
+{
+ // Use precompiled regex to check if text is a possible candidate, and early return if not
+ static auto prelimCheck = QRegularExpression(" enters( the battlefield)? tapped(?! unless)");
+ if (!prelimCheck.match(text).hasMatch()) {
+ return false;
+ }
+
+ // Try to split shortname on most non-alphanumeric characters (including _)
+ auto isShortnameDivider = [](const QChar &c) {
+ return c == '_' || (!c.isLetterOrNumber() && c != '\'' && c != '\"');
+ };
+
+ // Try all possible shortnames.
+ // This also handles the case of extra text appended at end.
+ QStringList possibleNames;
+ bool inAlphanumericPart = true;
+ for (int i = 0; i < name.length(); ++i) {
+ if (isShortnameDivider(name.at(i))) {
+ if (inAlphanumericPart) {
+ // only add to names on a "falling edge", in order to reduce the amount of redundant splits
+ possibleNames.append(QRegularExpression::escape(name.left(i)));
+ inAlphanumericPart = false;
+ }
+ } else {
+ inAlphanumericPart = true;
+ }
+ }
+
+ // and the full name
+ possibleNames.append(QRegularExpression::escape(name));
+
+ QString subject = "(it|" // "..., it enters tapped"
+ "(T|t)his [^ ]+|" // "This enters tapped"
+ + possibleNames.join("|") + ")";
+
+ auto ciptPattern = QRegularExpression(
+ // cipt phrase is either first sentence of line, or is after a punctuation mark
+ "(^|(, |\\. ))" + subject +
+ // support old wording, and exclude the "unless" case
+ " enters( the battlefield)? tapped(?! unless)",
+ QRegularExpression::MultilineOption);
+
+ return ciptPattern.match(text).hasMatch();
+}
diff --git a/oracle/src/parsehelpers.h b/oracle/src/parsehelpers.h
new file mode 100644
index 000000000..40a36c753
--- /dev/null
+++ b/oracle/src/parsehelpers.h
@@ -0,0 +1,8 @@
+#ifndef PARSEHELPERS_H
+#define PARSEHELPERS_H
+
+#include
+
+bool parseCipt(const QString &name, const QString &text);
+
+#endif // PARSEHELPERS_H
diff --git a/oracle/translations/oracle_it.ts b/oracle/translations/oracle_it.ts
index 9bb09707d..728067573 100644
--- a/oracle/translations/oracle_it.ts
+++ b/oracle/translations/oracle_it.ts
@@ -1,4 +1,27 @@
+
+ BetaReleaseChannel
+
+
+ Beta
+ Beta
+
+
+
+ No reply received from the release update server.
+ Nessuna risposta ricevuta dal server degli aggiornamenti di versione.
+
+
+
+ Invalid reply received from the release update server.
+ Ricevuta risposta non valida dal server degli aggiornamenti.
+
+
+
+ No reply received from the file update server.
+ Nessuna risposta ricevuta dal server degli aggiornamenti.
+
+IntroPage
@@ -62,8 +85,9 @@ e pedine che verranno usate da Cockatrice.
- Sets JSON file (%1)
- File dei set JSON (%1)
+ Sets file (%1)
+ Sets JSON file (%1)
+ File dei set (%1)
@@ -246,7 +270,7 @@ e pedine che verranno usate da Cockatrice.
OracleImporter
-
+ Dummy set containing tokensSet finto contenente i token
@@ -282,6 +306,15 @@ e pedine che verranno usate da Cockatrice.
Se il database delle carte non si ricarica in automatico, riavvia il programma Cockatrice.
+
+ PictureLoader
+
+
+ en
+ code for scryfall's language property, not available for all languages
+ it
+
+SaveSetsPage
@@ -356,6 +389,23 @@ e pedine che verranno usate da Cockatrice.
Impossibile salvare il file su %1
+
+ ShortcutsSettings
+
+
+ Your configuration file contained invalid shortcuts.
+Please check your shortcut settings!
+ Il file di configurazione conteneva combinazioni di tasti non valide.
+Controlla le impostazioni delle combinazioni!
+
+
+
+ The following shortcuts have been set to default:
+
+ Le seguenti combinazioni di tasti sono state reimpostate al valore predefinito:
+
+
+SimpleDownloadFilePage
@@ -391,6 +441,34 @@ e pedine che verranno usate da Cockatrice.
Impossibile salvare il file su %1
+
+ StableReleaseChannel
+
+
+ Default
+ Predefinito
+
+
+
+ No reply received from the release update server.
+ Nessuna risposta ricevuta dal server degli aggiornamenti di versione.
+
+
+
+ Invalid reply received from the release update server.
+ Ricevuta risposta non valida dal server degli aggiornamenti.
+
+
+
+ No reply received from the tag update server.
+ Nessuna risposta ricevuta dal server dei tag di aggiornamento.
+
+
+
+ Invalid reply received from the tag update server.
+ Ricevuta risposta non valida dal server dei tag di aggiornamento.
+
+UnZip
@@ -536,6 +614,7 @@ e pedine che verranno usate da Cockatrice.
i18n
+ EnglishItaliano (Italian)
diff --git a/servatrice/scripts/register.py b/servatrice/scripts/register.py
index 427c9e72b..d33db2e21 100755
--- a/servatrice/scripts/register.py
+++ b/servatrice/scripts/register.py
@@ -9,7 +9,7 @@ from pypb.event_server_identification_pb2 import Event_ServerIdentification as S
from pypb.response_pb2 import Response
HOST = "localhost"
-PORT = 4747
+PORT = 4748
CMD_ID = 1
diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example
index 8a214f39d..fac743c39 100644
--- a/servatrice/servatrice.ini.example
+++ b/servatrice/servatrice.ini.example
@@ -20,7 +20,8 @@ id=1
host=any
-; The TCP port number servatrice will listen on for clients; default is 4747
+; The TCP port number Servatrice will listen on for clients; default is 4747;
+; Will be removed in the future, use websocket connection instead
port=4747
; Servatrice can scale up to serve big number of users using more than one parallel thread of execution;
@@ -421,7 +422,7 @@ enable_forgotpassword_audit=true
; "servers" table of the database. Default is 0 (disabled)
active=0
-; The TCP port number servatrice will listen on for other servers; default is 14747
+; The TCP port number Servatrice will listen on for other servers; default is 14747
port=14747
; Server-to-server communication needs a valid certificate in PEM format. Enter its filename in this setting
diff --git a/shell.nix b/shell.nix
index 5315170d0..404e9e1b6 100644
--- a/shell.nix
+++ b/shell.nix
@@ -4,6 +4,7 @@
# Build tools
cmake
cmake-format
+ ninja
bash
curl
git
@@ -12,6 +13,7 @@
# Debug / Test
valgrind
gdb
+ clang-tools
# Compiler
gcc
@@ -23,4 +25,8 @@
qt6.full
qt6.wrapQtAppsHook
];
+
+ # Make debug builds work
+ # https://github.com/NixOS/nixpkgs/issues/18995
+ hardeningDisable = [ "fortify" ];
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d9e6b6345..f6b2d12d2 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -51,3 +51,4 @@ target_link_libraries(password_hash_test cockatrice_common Threads::Threads ${GT
add_subdirectory(carddatabase)
add_subdirectory(loading_from_clipboard)
+add_subdirectory(oracle)
diff --git a/tests/carddatabase/CMakeLists.txt b/tests/carddatabase/CMakeLists.txt
index 3ca812c22..403d14d1d 100644
--- a/tests/carddatabase/CMakeLists.txt
+++ b/tests/carddatabase/CMakeLists.txt
@@ -22,6 +22,7 @@ add_executable(
../../cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp
../../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp
../../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp
+ ../../cockatrice/src/game/cards/card_info.cpp
../../cockatrice/src/settings/settings_manager.cpp
carddatabase_test.cpp
mocks.cpp
@@ -35,6 +36,7 @@ add_executable(
../../cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp
../../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp
../../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp
+ ../../cockatrice/src/game/cards/card_info.cpp
../../cockatrice/src/game/filters/filter_card.cpp
../../cockatrice/src/game/filters/filter_string.cpp
../../cockatrice/src/game/filters/filter_tree.cpp
diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp
index 18172a0eb..4124abd17 100644
--- a/tests/carddatabase/mocks.cpp
+++ b/tests/carddatabase/mocks.cpp
@@ -203,6 +203,9 @@ void SettingsCache::setPrintingSelectorSortOrder(int /* _printingSelectorSortOrd
void SettingsCache::setPrintingSelectorCardSize(int /* _printingSelectorCardSize */)
{
}
+void SettingsCache::setIncludeOnlineOnlyCards(bool /* _includeOnlineOnlyCards */)
+{
+}
void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */)
{
}
@@ -391,6 +394,9 @@ void SettingsCache::setUpdateReleaseChannelIndex(int /* value */)
void SettingsCache::setMaxFontSize(int /* _max */)
{
}
+void SettingsCache::setRoundCardCorners(bool /* _roundCardCorners */)
+{
+}
void PictureLoader::clearPixmapCache(CardInfoPtr /* card */)
{
diff --git a/tests/loading_from_clipboard/clipboard_testing.cpp b/tests/loading_from_clipboard/clipboard_testing.cpp
index b9093364a..ad8db3dbf 100644
--- a/tests/loading_from_clipboard/clipboard_testing.cpp
+++ b/tests/loading_from_clipboard/clipboard_testing.cpp
@@ -18,7 +18,7 @@ void testEmpty(const QString &clipboard)
QString cp(clipboard);
DeckList deckList;
QTextStream stream(&cp); // text stream requires local copy
- deckList.loadFromStream_Plain(stream);
+ deckList.loadFromStream_Plain(stream, false);
ASSERT_TRUE(deckList.getCardList().isEmpty());
}
@@ -28,7 +28,7 @@ void testDeck(const QString &clipboard, const Result &result)
QString cp(clipboard);
DeckList deckList;
QTextStream stream(&cp); // text stream requires local copy
- deckList.loadFromStream_Plain(stream);
+ deckList.loadFromStream_Plain(stream, false);
ASSERT_EQ(result.name, deckList.getName().toStdString());
ASSERT_EQ(result.comments, deckList.getComments().toStdString());
diff --git a/tests/oracle/CMakeLists.txt b/tests/oracle/CMakeLists.txt
new file mode 100644
index 000000000..0c11eb751
--- /dev/null
+++ b/tests/oracle/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_executable(parse_cipt_test ../../oracle/src/parsehelpers.cpp parse_cipt_test.cpp)
+
+if(NOT GTEST_FOUND)
+ add_dependencies(parse_cipt_test gtest)
+endif()
+
+set(TEST_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Widgets)
+
+target_link_libraries(parse_cipt_test cockatrice_common Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
+
+add_test(NAME parse_cipt_test COMMAND parse_cipt_test)
diff --git a/tests/oracle/parse_cipt_test.cpp b/tests/oracle/parse_cipt_test.cpp
new file mode 100644
index 000000000..f5036f962
--- /dev/null
+++ b/tests/oracle/parse_cipt_test.cpp
@@ -0,0 +1,219 @@
+#include "../../oracle/src/parsehelpers.h"
+
+#include "gtest/gtest.h"
+
+TEST(ParseCiptTest, parsesThisEntersTapped)
+{
+ auto name = "Boring Fields";
+ auto text = "This land enters tapped.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesThisEntersTheBattlefieldTapped)
+{
+ auto name = "Boring Fields";
+ auto text = "This land enters the battlefield tapped.\n"
+ "{T}: Add {G}.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesItEntersTappedAtEndOfSentence)
+{
+ auto name = "Shocking Fields";
+ auto text = "As this land enters, you may pay 2 life. If you don't, it enters tapped.\n"
+ "{T}: Add {G}.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesThisEntersTappedWhenNotOnFirstLine)
+{
+ auto name = "Boring Fields";
+ auto text = "Flying\n"
+ "This land enters tapped.\n"
+ "{T}: Add {G}.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesFullNameWithUnderscoreAppendedText)
+{
+ auto name = "Boring Fields_SL50";
+ auto text = "Boring Fields enters tapped.\n"
+ "{T}: Add {G}.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesFullNameWithBracketsAppendedText)
+{
+ auto name = "Boring Fields (SL50)";
+ auto text = "Boring Fields enters tapped.\n"
+ "{T}: Add {G}.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesFullNameWithComma)
+{
+ auto name = "Bob, the Legend";
+ auto text = "Bob, the Legend enters tapped.\n"
+ "Whenever Bob attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesFullNameWithCommaAtEndOfSentence)
+{
+ auto name = "Bob, the Legend";
+ auto text = "As Bob, the Legend enters, you may pay 2 life. If you don't, Bob, the Legend enters tapped.\n"
+ "Whenever Bob attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesFullNameWithApostropheAtEndOfSentence)
+{
+ auto name = "Bob's Bobber";
+ auto text = "As Bob's Bobber enters, you may pay 2 life. If you don't, Bob's Bobber enters tapped.\n"
+ "Whenever Bob attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesShortnameEndingWithComma)
+{
+ auto name = "Bob, the Legend";
+ auto text = "Bob enters tapped.\n"
+ "Whenever Bob attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesShortnameEndingWithSpace)
+{
+ auto name = "Bob the Legend";
+ auto text = "Bob enters tapped.\n"
+ "Whenever Bob attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesMultiWordShortnameEndingWithComma)
+{
+ auto name = "Bob Dod, the Legend";
+ auto text = "Bob Dod enters tapped.\n"
+ "Whenever Bob Dod attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesMultiWordShortnameEndingWithSpace)
+{
+ auto name = "Bob Dod the Legend";
+ auto text = "Bob Dod enters tapped.\n"
+ "Whenever Bob Dod attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesShortnameEndingWithSpaceWithUnderscoreAppendedText)
+{
+ auto name = "Bob the Legend_SL50";
+ auto text = "Bob enters tapped.\n"
+ "Whenever Bob attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesShortnameEndingWithSpaceWithBracketsAppendedText)
+{
+ auto name = "Bob the Legend (SL50)";
+ auto text = "Bob enters tapped.\n"
+ "Whenever Bob attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesMultiWordShortnameEndingWithSpaceWithUnderscoreAppendedText)
+{
+ auto name = "Bob Dod the Legend_SL50";
+ auto text = "Bob Dod enters tapped.\n"
+ "Whenever Bob Dod attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesMultiWordShortnameEndingWithSpaceWithBracketsAppendedText)
+{
+ auto name = "Bob Dod the Legend (SL50)";
+ auto text = "Bob Dod enters tapped.\n"
+ "Whenever Bob Dod attacks, you win the game.";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, rejectsEmptyText)
+{
+ auto name = "Vanilla Dude";
+ auto text = "";
+
+ ASSERT_FALSE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, rejectsEntersTappedUnless)
+{
+ auto name = "Fast Fields";
+ auto text = "This land enters tapped unless you control another land.";
+
+ ASSERT_FALSE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, rejectsWhenNameIsDifferent)
+{
+ auto name = "Boring Fields";
+ auto text = "Fast Fields enters tapped.";
+
+ ASSERT_FALSE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, rejectsOtherCreaturesEnterTapped)
+{
+ auto name = "Imposing Guy";
+ auto text = "Other creatures enter tapped.";
+
+ ASSERT_FALSE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, rejectsAbilityGrantingEntersTapped)
+{
+ auto name = "Imposing Guy";
+ auto text = "Other creatures have \"This creature enters tapped\".";
+
+ ASSERT_FALSE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, parsesEntersTappedAndAbilityGrantingEntersTappedOnSameCard)
+{
+ auto name = "Imposing Guy";
+ auto text = "This creature enters tapped."
+ "Other creatures have \"This creature enters tapped\".";
+
+ ASSERT_TRUE(parseCipt(name, text));
+}
+
+TEST(ParseCiptTest, rejectsItEntersTappedAndAttacking)
+{
+ auto name = "Token Maker";
+ auto text = "When Token Maker attacks, create a token. It enters tapped and attacking.";
+
+ ASSERT_FALSE(parseCipt(name, text));
+}
+
+int main(int argc, char **argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}