Compare commits

..

28 Commits

Author SHA1 Message Date
tooomm
067d9797be Add doxygen-awesome-css theme 2025-12-21 13:45:38 +01:00
RickyRister
d6db21419c [Refactor] Pass around LoadedDeck instead of DeckLoader (#6422) 2025-12-20 13:39:00 +01:00
RickyRister
367507e054 [DeckListModel] Refactor: Don't access underlying decklist for iteration (#6427)
* [DeckListModel] Refactor: Don't access underlying decklist for iteration

* add docs

* extract method
2025-12-20 13:25:30 +01:00
BruebachL
715ee1d6fe Revert "Enable internal documentation in Doxyfile (#6432)" (#6433)
This reverts commit ad06a81765.
2025-12-19 23:55:19 +01:00
tooomm
ad06a81765 Enable internal documentation in Doxyfile (#6432) 2025-12-19 23:51:56 +01:00
BruebachL
ebb02b27b2 [Card DB] Properly pass along set priority controller to parsers (#6430)
* [Card DB] Properly pass along set priority controller to parsers

Took 16 minutes

Took 35 seconds

* More adjustments.

Took 13 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-18 09:01:06 +01:00
BruebachL
d47dc35885 [TabArchidekt] Set game format when importing (#6416)
* [TabArchidekt] Set game format when importing

Took 5 minutes

* Move formats to file.

Took 9 minutes

Took 4 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-16 13:39:19 +01:00
BruebachL
41aca8467a [CardInfoPicture] Defer enlargedPixmap creation until needed (#6426)
* [CardInfoPicture] Defer enlargedPixmap creation until needed

Took 4 minutes


Took 1 minute

* Disregard const_cast

Took 2 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-16 13:12:51 +01:00
BruebachL
cd44392866 Static helpers. (#6425)
Took 2 minutes


Took 29 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-16 13:03:14 +01:00
dependabot[bot]
64bb5355ff Bump peter-evans/create-pull-request from 7 to 8 (#6423) 2025-12-15 23:38:49 +01:00
dependabot[bot]
1198db8891 Bump actions/cache from 4 to 5 (#6424) 2025-12-15 23:35:18 +01:00
tooomm
9471adb4f7 Add repo activity with top contributors acknowledgment (#6420) 2025-12-15 22:55:57 +01:00
RickyRister
b29909bdbe [DeckList] Refactor: Create class to RAII underlying tree (#6412)
* [DeckList] Create class to RAII underlying tree

* Update usages

* fixes after rebase

* update docs
2025-12-14 15:56:58 -08:00
RickyRister
589e9a15a6 [DeckFilterString] Add search query for format (#6414)
* [DeckFilterString] Rename file search expression

* [DeckFilterString] Add search query for format
2025-12-15 00:14:19 +01:00
RickyRister
c218a66bcd [DeckFilterString] Rename file search expression (#6413) 2025-12-15 00:14:11 +01:00
tooomm
8485bbe575 Docs: Use Doxygen \todo comments (#6421)
* format todo comments for doxygen

* Mention Todo List & link search
2025-12-15 00:13:16 +01:00
RickyRister
5d9d7d3aa5 [DeckLoader] remove unused private methods (#6417) 2025-12-14 14:26:06 -08:00
BruebachL
ccdda39e78 Deck format legality checker (#6166)
* Deck legality checker.

Took 51 seconds

Took 1 minute

Took 1 minute

Took 5 minutes

Took 3 minutes

* Adjust format parsing.

Took 8 minutes


Took 3 seconds

* toString() the xmlName

Took 4 minutes

* more toStrings()

Took 5 minutes

* Comments

Took 3 minutes

* Layout

Took 2 minutes

* Layout part 2: Electric boogaloo

Took 59 seconds

* Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

* Move layout.

Took 4 minutes


Took 10 seconds

* Emit deckModified

Took 6 minutes

* Fix qOverloads

Took 4 minutes

* Fix qOverloads

Took 12 seconds

* Consider text and name in a special way.

Took 11 minutes

* Adjust "Any number of" oracle text

Took 5 minutes

* Store allowedCounts by format

Took 15 minutes

Took 6 seconds

* Only restrict vintage.

Took 2 minutes

* Adjust for DBConverter.

Took 6 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2025-12-13 15:17:55 +01:00
RickyRister
2e2682aad4 [DeckList] Refactor and cleanup methods that iterate over nodes (#6407)
* remove helpers

* create getZoneNodes method

* replace direct calls to getRoot and forEachCard

* remove more non-const uses of forEachCard

* make node getter return const lists

* one more usage

* address comment

* address comment again

* fix hash

* fix hashes (for real this time)
2025-12-12 12:37:44 -08:00
BruebachL
a390c8ada7 [NetworkManager] Set Version string as user agent (#6411)
* [NetworkManager] Set Version string as user agent

Took 13 minutes

* Update in oracle.

Took 14 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-12 11:29:18 +01:00
BruebachL
da70344547 [VDD] Implement ExactMatch Name filter (#6409)
* [VDD] Implement ExactMatch Name filter

Took 7 minutes

Took 4 minutes

* Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2025-12-12 03:57:18 +01:00
RickyRister
2b690f8c87 [DeckLoader] Extract cardNode functions to own file (#6408)
* [DeckLoader] Extract cardNode functions to own file

* update usages
2025-12-08 00:47:24 -08:00
RickyRister
c8b419888a [DeckLoader] Extract LoadedDeck struct (#6406)
* [DeckLoader] Extract LoadedDeck struct

* update usages

* Move enum to separate namespace

* format

* format

* format
2025-12-07 15:03:52 +01:00
Lily Huang
d3302d521f Fix flipped svg for donator/judge/vip (#6400) 2025-12-06 14:09:55 +01:00
tooomm
5c1bb27d5c README: Add code docs + flathub repo links (#6384)
* Add code docs + flathub repo links

* Update README.md
2025-12-05 23:28:25 +01:00
BruebachL
dde36183ce [VDE] Proper parent lookup syncs group-by box again (#6396)
* [VDE] Proper parent lookup syncs group-by box again

* [VDE] Proper lib inclusion.

* [VDE] Lint.
2025-12-05 23:27:27 +01:00
BruebachL
7c7f2dd8d5 [Doxygen] Logging (#6399)
* [Doxygen] Logging

Took 50 minutes

Took 36 seconds

* [Doxygen] Newline.

Took 2 minutes

* [Doxygen] Add another example.

Took 7 minutes

* [Doxygen] \note and \warning

Took 4 minutes

Took 32 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2025-12-05 18:42:45 +01:00
BruebachL
edb0a954e2 [GameInformation] Check for existence of room for create as judge checkbox (#6398) 2025-12-05 17:26:35 +01:00
140 changed files with 2567 additions and 1023 deletions

View File

@@ -166,7 +166,7 @@ jobs:
- name: Restore compiler cache (ccache)
id: ccache_restore
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
@@ -205,7 +205,7 @@ jobs:
- name: Save compiler cache (ccache)
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
uses: actions/cache/save@v5
with:
path: ${{env.CACHE}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}

View File

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

View File

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

4
.gitmodules vendored
View File

@@ -1,3 +1,7 @@
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg.git
[submodule "doxygen-awesome-css"]
path = doc/doxygen/theme
url = https://github.com/jothepro/doxygen-awesome-css.git

View File

@@ -1068,7 +1068,8 @@ RECURSIVE = YES
EXCLUDE = build/ \
cmake/ \
dbconverter/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
vcpkg/ \
webclient/
@@ -1431,7 +1432,8 @@ HTML_STYLESHEET =
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET = doc/doxygen/css/doxygen_style.css
HTML_EXTRA_STYLESHEET = doc/doxygen/css/doxygen_style.css \
doc/doxygen/theme/doxygen-awesome.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@@ -1454,7 +1456,7 @@ HTML_EXTRA_FILES = doc/doxygen/js/graph_toggle.js
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = AUTO_DARK
HTML_COLORSTYLE = LIGHT # required with doxygen-awesome-css theme, Auto Dark Mode will still work
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
@@ -1765,7 +1767,7 @@ ECLIPSE_DOC_ID = org.doxygen.Project
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
DISABLE_INDEX = YES
DISABLE_INDEX = NO # YES is bugged in the theme, see jothepro/doxygen-awesome-css/issues/201
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information. If the tag

View File

@@ -46,7 +46,8 @@ Latest <kbd>beta</kbd> version:
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to use in Cockatrice
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official webpage of the Cockatrice project
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
- [Cockatrice @Flathub](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration for our Linux `flatpak` package
# Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
@@ -54,6 +55,7 @@ Latest <kbd>beta</kbd> version:
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
- [Official Website](https://cockatrice.github.io)
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
- [Official Code Documentation](https://cockatrice.github.io/docs)
- [Official Discord](https://discord.gg/3Z9yzmA)
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
@@ -75,10 +77,15 @@ This tag is used for issues that we are looking for somebody to pick up. Often t
For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code reviews for submitted changes.<br>
We'll happily advice on how best to implement a feature, or we can show you where the codebase is doing something similar before you get too far along - put a note on an issue you want to discuss more on!
You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code).
Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
#### Repository Activity
![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats")
<details>
<summary><b>Kudos to our amazing contributors ❤️</b></summary>
<summary><b>Kudos to all our amazing contributors ❤️</b></summary>
<br>
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />

View File

@@ -19,7 +19,10 @@ set(cockatrice_SOURCES
src/client/settings/card_counter_settings.cpp
src/client/settings/shortcut_treeview.cpp
src/client/settings/shortcuts_settings.cpp
src/interface/deck_loader/card_node_function.cpp
src/interface/deck_loader/deck_file_format.cpp
src/interface/deck_loader/deck_loader.cpp
src/interface/deck_loader/loaded_deck.cpp
src/interface/widgets/dialogs/dlg_connect.cpp
src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.cpp
src/interface/widgets/dialogs/dlg_create_game.cpp
@@ -199,6 +202,7 @@ set(cockatrice_SOURCES
src/interface/widgets/utility/sequence_edit.cpp
src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp
@@ -226,6 +230,7 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp

View File

@@ -1,6 +1,10 @@
[Rules]
# The default log level is info
*.debug = false
#*.info = true
#*.warning = true
#*.critical = true
#*.fatal = true
# Uncomment a rule to see debug level logs for that category,
# or set <category> = false to disable logging

View File

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

View File

@@ -350,11 +350,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="right" />
id="left" />
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="left"
id="right"
inkscape:connector-curvature="0" />
<path
style="display:inline;fill:url(#linearGradient3);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.77952756;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -321,11 +321,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="right" />
id="left" />
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="left"
id="right"
inkscape:connector-curvature="0" />
<path
d="m 46.656521,12.167234 18.055171,18.054184 a 6.6081919,6.6078288 0 0 1 -0.126303,9.352065 6.6804126,6.6800456 0 0 1 -8.233169,1.011048 l -7.944268,7.943843 6.463762,6.445343 a 6.9331851,6.9328042 0 0 1 5.741536,2.022073 l 28.057729,28.092294 a 6.9962797,6.9958953 0 0 1 -9.894222,9.893685 L 50.719018,66.907526 A 7.0595711,7.0591833 0 0 1 49.18433,59.270613 l -5.741527,-5.741238 -7.944298,7.943843 A 6.716523,6.7161541 0 0 1 25.134866,69.832263 L 7.079684,51.778091 a 6.716523,6.7161541 0 0 1 8.39566,-10.345064 L 36.31101,20.59853 a 6.716523,6.7161541 0 0 1 10.345612,-8.431329 z"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -340,11 +340,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)"
id="right" />
id="left" />
<path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="left"
id="right"
inkscape:connector-curvature="0" />
<path
sodipodi:type="star"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -8,6 +8,7 @@
#include <QUrlQuery>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <version_string.h>
DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent)
: QObject(parent), cardDatabase(_cardDatabase)
@@ -62,6 +63,7 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck)
QNetworkRequest request(QUrl("https://deckstats.net/index.php"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
manager->post(request, data);
}

View File

@@ -8,6 +8,7 @@
#include <QUrlQuery>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <version_string.h>
TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent)
: QObject(parent), cardDatabase(_cardDatabase)
@@ -87,6 +88,7 @@ void TappedOutInterface::analyzeDeck(DeckList *deck)
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
manager->post(request, data);
}

View File

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

View File

@@ -14,6 +14,7 @@
#include <QtConcurrent>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <version_string.h>
#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
@@ -39,7 +40,9 @@ void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool sav
void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
{
auto *nam = new QNetworkAccessManager(this);
QNetworkReply *reply = nam->get(QNetworkRequest(url));
auto request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
QNetworkReply *reply = nam->get(request);
if (saveResults) {
// This will write out to the file (used for spoiler.xml)

View File

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

View File

@@ -343,10 +343,7 @@ void DeckViewScene::rebuildTree()
if (!deck)
return;
InnerDecklistNode *listRoot = deck->getRoot();
for (int i = 0; i < listRoot->size(); i++) {
auto *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
for (auto *currentZone : deck->getZoneNodes()) {
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
if (!container) {
container = new DeckViewCardContainer(currentZone->getName());

View File

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

View File

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

View File

@@ -5,6 +5,7 @@
#include "../player_actions.h"
#include "player_menu.h"
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <libcockatrice/deck_list/tree/inner_deck_list_node.h>
UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player)
@@ -59,21 +60,19 @@ void UtilityMenu::populatePredefinedTokensMenu()
clear();
setEnabled(false);
predefinedTokens.clear();
DeckLoader *_deck = player->getDeck();
const DeckList &deckList = player->getDeck();
if (!_deck) {
if (deckList.isEmpty()) {
return;
}
InnerDecklistNode *tokenZone =
dynamic_cast<InnerDecklistNode *>(_deck->getDeckList()->getRoot()->findChild(DECK_ZONE_TOKENS));
auto tokenCardNodes = deckList.getCardNodes({DECK_ZONE_TOKENS});
if (tokenZone) {
if (!tokenZone->empty())
setEnabled(true);
if (!tokenCardNodes.isEmpty()) {
setEnabled(true);
for (int i = 0; i < tokenZone->size(); ++i) {
const QString tokenName = tokenZone->at(i)->getName();
for (int i = 0; i < tokenCardNodes.size(); ++i) {
const QString tokenName = tokenCardNodes[i]->getName();
predefinedTokens.append(tokenName);
QAction *a = addAction(tokenName);
if (i < 10) {

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@
#include <QNetworkReply>
#include <QThread>
#include <utility>
#include <version_string.h>
static constexpr int MAX_REQUESTS_PER_SEC = 10;
@@ -86,6 +87,7 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture
}
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
if (!picDownload) {
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
}

View File

@@ -0,0 +1,40 @@
#include "card_node_function.h"
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
void CardNodeFunction::SetProviderIdToPreferred::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
{
Q_UNUSED(node);
PrintingInfo preferredPrinting = CardDatabaseManager::query()->getPreferredPrinting(card->getName());
QString providerId = preferredPrinting.getUuid();
QString setShortName = preferredPrinting.getSet()->getShortName();
QString collectorNumber = preferredPrinting.getProperty("num");
card->setCardProviderId(providerId);
card->setCardCollectorNumber(collectorNumber);
card->setCardSetShortName(setShortName);
}
void CardNodeFunction::ClearPrintingData::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
{
Q_UNUSED(node);
card->setCardSetShortName(nullptr);
card->setCardCollectorNumber(nullptr);
card->setCardProviderId(nullptr);
}
void CardNodeFunction::ResolveProviderId::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
{
Q_UNUSED(node);
// Retrieve the providerId based on setName and collectorNumber
QString providerId =
CardDatabaseManager::getInstance()
->query()
->getSpecificPrinting(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber())
.getUuid();
// Set the providerId on the card
card->setCardProviderId(providerId);
}

View File

@@ -0,0 +1,39 @@
#ifndef COCKATRICE_DECK_FUNCTION_H
#define COCKATRICE_DECK_FUNCTION_H
class DecklistCardNode;
class InnerDecklistNode;
/**
* Functions to be used with DeckList::forEachCard
*/
namespace CardNodeFunction
{
/**
* @brief Sets the providerId of the card to the preferred printing.
*/
struct SetProviderIdToPreferred
{
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const;
};
/**
* @brief Clears all fields on the card related to the printing
*/
struct ClearPrintingData
{
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const;
};
/**
* @brief Sets the providerId of the card based on its set name and collector number.
*/
struct ResolveProviderId
{
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const;
};
} // namespace CardNodeFunction
#endif // COCKATRICE_DECK_FUNCTION_H

View File

@@ -0,0 +1,9 @@
#include "deck_file_format.h"
DeckFileFormat::Format DeckFileFormat::getFormatFromName(const QString &fileName)
{
if (fileName.endsWith(".cod", Qt::CaseInsensitive)) {
return Cockatrice;
}
return PlainText;
}

View File

@@ -0,0 +1,36 @@
#ifndef COCKATRICE_DECK_FILE_FORMAT_H
#define COCKATRICE_DECK_FILE_FORMAT_H
#include <QString>
namespace DeckFileFormat
{
/**
* The deck file formats that Cockatrice supports.
*/
enum Format
{
/**
* Plaintext deck files, a format that is intended to be widely supported among different programs.
* This format does not support Cockatrice specific features such as banner cards or tags.
*/
PlainText,
/**
* This is cockatrice's native deck file format, and supports deck metadata such as banner cards and tags.
* Stored as .cod files.
*/
Cockatrice
};
/**
* Determines what deck file format the given filename corresponds to.
*
* @param fileName The filename
* @return The deck format
*/
Format getFormatFromName(const QString &fileName);
} // namespace DeckFileFormat
#endif // COCKATRICE_DECK_FILE_FORMAT_H

View File

@@ -25,15 +25,11 @@ const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.d
const QStringList DeckLoader::FILE_NAME_FILTERS = {
tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")};
DeckLoader::DeckLoader(QObject *parent) : QObject(parent), deckList(new DeckList())
DeckLoader::DeckLoader(QObject *parent) : QObject(parent)
{
}
DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) : QObject(parent), deckList(_deckList)
{
}
bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest)
bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
@@ -41,18 +37,19 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool user
}
bool result = false;
DeckList deckList = DeckList();
switch (fmt) {
case PlainTextFormat:
result = deckList->loadFromFile_Plain(&file);
case DeckFileFormat::PlainText:
result = deckList.loadFromFile_Plain(&file);
break;
case CockatriceFormat: {
result = deckList->loadFromFile_Native(&file);
case DeckFileFormat::Cockatrice: {
result = deckList.loadFromFile_Native(&file);
qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result;
if (!result) {
qCInfo(DeckLoaderLog) << "Retrying as plain format";
file.seek(0);
result = deckList->loadFromFile_Plain(&file);
fmt = PlainTextFormat;
result = deckList.loadFromFile_Plain(&file);
fmt = DeckFileFormat::PlainText;
}
break;
}
@@ -62,7 +59,8 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool user
}
if (result) {
lastLoadInfo = {
loadedDeck.deckList = deckList;
loadedDeck.lastLoadInfo = {
.fileName = fileName,
.fileFormat = fmt,
};
@@ -77,7 +75,7 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool user
return result;
}
bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest)
bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest)
{
auto *watcher = new QFutureWatcher<bool>(this);
@@ -86,7 +84,7 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool
watcher->deleteLater();
if (result) {
lastLoadInfo = {
loadedDeck.lastLoadInfo = {
.fileName = fileName,
.fileFormat = fmt,
};
@@ -106,14 +104,14 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool
}
switch (fmt) {
case PlainTextFormat:
return deckList->loadFromFile_Plain(&file);
case CockatriceFormat: {
case DeckFileFormat::PlainText:
return loadedDeck.deckList.loadFromFile_Plain(&file);
case DeckFileFormat::Cockatrice: {
bool result = false;
result = deckList->loadFromFile_Native(&file);
result = loadedDeck.deckList.loadFromFile_Native(&file);
if (!result) {
file.seek(0);
return deckList->loadFromFile_Plain(&file);
return loadedDeck.deckList.loadFromFile_Plain(&file);
}
return result;
}
@@ -129,9 +127,9 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool
bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
{
bool result = deckList->loadFromString_Native(nativeString);
bool result = loadedDeck.deckList.loadFromString_Native(nativeString);
if (result) {
lastLoadInfo = {
loadedDeck.lastLoadInfo = {
.remoteDeckId = remoteDeckId,
};
@@ -140,7 +138,7 @@ bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
return result;
}
bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
@@ -149,17 +147,17 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
bool result = false;
switch (fmt) {
case PlainTextFormat:
result = deckList->saveToFile_Plain(&file);
case DeckFileFormat::PlainText:
result = loadedDeck.deckList.saveToFile_Plain(&file);
break;
case CockatriceFormat:
result = deckList->saveToFile_Native(&file);
case DeckFileFormat::Cockatrice:
result = loadedDeck.deckList.saveToFile_Native(&file);
qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result;
break;
}
if (result) {
lastLoadInfo = {
loadedDeck.lastLoadInfo = {
.fileName = fileName,
.fileFormat = fmt,
};
@@ -172,7 +170,7 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
return result;
}
bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt)
bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt)
{
QFileInfo fileInfo(fileName);
if (!fileInfo.exists()) {
@@ -193,19 +191,19 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat f
// Perform file modifications
switch (fmt) {
case PlainTextFormat:
result = deckList->saveToFile_Plain(&file);
case DeckFileFormat::PlainText:
result = loadedDeck.deckList.saveToFile_Plain(&file);
break;
case CockatriceFormat:
deckList->setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
result = deckList->saveToFile_Native(&file);
case DeckFileFormat::Cockatrice:
loadedDeck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
result = loadedDeck.deckList.saveToFile_Native(&file);
break;
}
file.close(); // Close the file to ensure changes are flushed
if (result) {
lastLoadInfo = {
loadedDeck.lastLoadInfo = {
.fileName = fileName,
.fileFormat = fmt,
};
@@ -269,6 +267,20 @@ static QString toDecklistExportString(const DecklistCardNode *card)
return cardString;
}
/**
* Converts all cards in the list to their decklist export string and joins them into one string
*/
static QString toDecklistExportString(const QList<const DecklistCardNode *> &cardNodes)
{
QString result;
for (auto cardNode : cardNodes) {
result += toDecklistExportString(cardNode);
}
return result;
}
/**
* Export deck to decklist function, called to format the deck in a way to be sent to a server
*
@@ -279,29 +291,11 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi
{
// Add the base url
QString deckString = "https://" + getDomainForWebsite(website) + "/?";
// Create two strings to pass to function
QString mainBoardCards, sideBoardCards;
// Set up the function to call
auto formatDeckListForExport = [&mainBoardCards, &sideBoardCards](const auto *node, const auto *card) {
// Get the card name
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
if (!dbCard || dbCard->getIsToken()) {
// If it's a token, we don't care about the card.
return;
}
// export all cards in zone
QString mainBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_MAIN}));
QString sideBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_SIDE}));
// Check if it's a sideboard card.
if (node->getName() == DECK_ZONE_SIDE) {
sideBoardCards += toDecklistExportString(card);
} else {
// If it's a mainboard card, do the same thing, but for the mainboard card string
mainBoardCards += toDecklistExportString(card);
}
};
// call our struct function for each card in the deck
deckList->forEachCard(formatDeckListForExport);
// Remove the extra return at the end of the last cards
mainBoardCards.chop(3);
sideBoardCards.chop(3);
@@ -316,112 +310,6 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi
return deckString;
}
// This struct is here to support the forEachCard function call, defined in decklist.
// It requires a function to be called for each card, and it will set the providerId to the preferred printing.
struct SetProviderIdToPreferred
{
// Main operator for struct, allowing the foreachcard to work.
SetProviderIdToPreferred()
{
}
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
{
Q_UNUSED(node);
PrintingInfo preferredPrinting = CardDatabaseManager::query()->getPreferredPrinting(card->getName());
QString providerId = preferredPrinting.getUuid();
QString setShortName = preferredPrinting.getSet()->getShortName();
QString collectorNumber = preferredPrinting.getProperty("num");
card->setCardProviderId(providerId);
card->setCardCollectorNumber(collectorNumber);
card->setCardSetShortName(setShortName);
}
};
/**
* This function iterates through each card in the decklist and sets the providerId
* on each card based on its set name and collector number.
*
* @param deckList The decklist to modify
*/
void DeckLoader::setProviderIdToPreferredPrinting(const DeckList *deckList)
{
// Set up the struct to call.
SetProviderIdToPreferred setProviderIdToPreferred;
// Call the forEachCard method for each card in the deck
deckList->forEachCard(setProviderIdToPreferred);
}
/**
* Sets the providerId on each card in the decklist based on its set name and collector number.
*
* @param deckList The decklist to modify
*/
void DeckLoader::resolveSetNameAndNumberToProviderID(const DeckList *deckList)
{
auto setProviderId = [](const auto node, const auto card) {
Q_UNUSED(node);
// Retrieve the providerId based on setName and collectorNumber
QString providerId =
CardDatabaseManager::getInstance()
->query()
->getSpecificPrinting(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber())
.getUuid();
// Set the providerId on the card
card->setCardProviderId(providerId);
};
deckList->forEachCard(setProviderId);
}
// This struct is here to support the forEachCard function call, defined in decklist.
// It requires a function to be called for each card, and it will set the providerId.
struct ClearSetNameNumberAndProviderId
{
// Main operator for struct, allowing the foreachcard to work.
ClearSetNameNumberAndProviderId()
{
}
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
{
Q_UNUSED(node);
// Set the providerId on the card
card->setCardSetShortName(nullptr);
card->setCardCollectorNumber(nullptr);
card->setCardProviderId(nullptr);
}
};
/**
* Clears the set name and numbers on each card in the decklist.
*
* @param deckList The decklist to modify
*/
void DeckLoader::clearSetNamesAndNumbers(const DeckList *deckList)
{
auto clearSetNameAndNumber = [](const auto node, auto card) {
Q_UNUSED(node)
// Set the providerId on the card
card->setCardSetShortName(nullptr);
card->setCardCollectorNumber(nullptr);
card->setCardProviderId(nullptr);
};
deckList->forEachCard(clearSetNameAndNumber);
}
DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName)
{
if (fileName.endsWith(".cod", Qt::CaseInsensitive)) {
return CockatriceFormat;
}
return PlainTextFormat;
}
void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber)
{
QString buffer;
@@ -441,9 +329,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out,
}
// loop zones
for (int i = 0; i < deckList->getRoot()->size(); i++) {
const auto *zoneNode = dynamic_cast<InnerDecklistNode *>(deckList->getRoot()->at(i));
for (auto zoneNode : deckList->getZoneNodes()) {
saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber);
// end of zone
@@ -564,12 +450,12 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName)
bool result = false;
// Perform file modifications based on the detected format
switch (getFormatFromName(fileName)) {
case PlainTextFormat:
switch (DeckFileFormat::getFormatFromName(fileName)) {
case DeckFileFormat::PlainText:
// Save in Cockatrice's native format
result = deckList->saveToFile_Native(&file);
result = loadedDeck.deckList.saveToFile_Native(&file);
break;
case CockatriceFormat:
case DeckFileFormat::Cockatrice:
qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed.";
result = true;
break;
@@ -588,39 +474,16 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName)
} else {
qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName;
}
lastLoadInfo = {
loadedDeck.lastLoadInfo = {
.fileName = newFileName,
.fileFormat = CockatriceFormat,
.fileFormat = DeckFileFormat::Cockatrice,
};
}
return result;
}
QString DeckLoader::getCardZoneFromName(const QString &cardName, QString currentZoneName)
{
CardInfoPtr card = CardDatabaseManager::query()->getCardInfo(cardName);
if (card && card->getIsToken()) {
return DECK_ZONE_TOKENS;
}
return currentZoneName;
}
QString DeckLoader::getCompleteCardName(const QString &cardName)
{
if (CardDatabaseManager::getInstance()) {
ExactCard temp = CardDatabaseManager::query()->guessCard({cardName});
if (temp) {
return temp.getName();
}
}
return cardName;
}
void DeckLoader::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node)
void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node)
{
const int totalColumns = 2;
@@ -703,12 +566,11 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
cursor.insertText(deckList->getComments());
cursor.insertBlock(headerBlockFormat, headerCharFormat);
for (int i = 0; i < deckList->getRoot()->size(); i++) {
for (auto zoneNode : deckList->getZoneNodes()) {
cursor.insertHtml("<br><img src=theme:hr.jpg>");
// cursor.insertHtml("<hr>");
cursor.insertBlock(headerBlockFormat, headerCharFormat);
printDeckListNode(&cursor, dynamic_cast<InnerDecklistNode *>(deckList->getRoot()->at(i)));
printDeckListNode(&cursor, zoneNode);
}
doc.print(printer);

View File

@@ -7,14 +7,16 @@
#ifndef DECK_LOADER_H
#define DECK_LOADER_H
#include "loaded_deck.h"
#include <QLoggingCategory>
#include <QPrinter>
#include <QTextCursor>
#include <libcockatrice/deck_list/deck_list.h>
inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader")
inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader");
class DeckLoader : public QObject
class DeckLoader : public QObject
{
Q_OBJECT
signals:
@@ -22,27 +24,6 @@ signals:
void loadFinished(bool success);
public:
enum FileFormat
{
PlainTextFormat,
CockatriceFormat
};
/**
* @brief Information about where the deck was loaded from.
*
* For local decks, the remoteDeckId field will always be -1.
* For remote decks, fileName will be empty and fileFormat will always be CockatriceFormat
*/
struct LoadInfo
{
static constexpr int NON_REMOTE_ID = -1;
QString fileName = "";
FileFormat fileFormat = CockatriceFormat;
int remoteDeckId = NON_REMOTE_ID;
};
/**
* Supported file extensions for decklist files
*/
@@ -60,44 +41,26 @@ public:
};
private:
DeckList *deckList;
LoadInfo lastLoadInfo;
LoadedDeck loadedDeck;
public:
DeckLoader(QObject *parent);
DeckLoader(QObject *parent, DeckList *_deckList);
DeckLoader(const DeckLoader &) = delete;
DeckLoader &operator=(const DeckLoader &) = delete;
const LoadInfo &getLastLoadInfo() const
{
return lastLoadInfo;
}
void setLastLoadInfo(const LoadInfo &info)
{
lastLoadInfo = info;
}
[[nodiscard]] bool hasNotBeenLoaded() const
{
return lastLoadInfo.fileName.isEmpty() && lastLoadInfo.remoteDeckId == LoadInfo::NON_REMOTE_ID;
return loadedDeck.lastLoadInfo.isEmpty();
}
static void clearSetNamesAndNumbers(const DeckList *deckList);
static FileFormat getFormatFromName(const QString &fileName);
bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false);
bool loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest);
bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false);
bool loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest);
bool loadFromRemote(const QString &nativeString, int remoteDeckId);
bool saveToFile(const QString &fileName, FileFormat fmt);
bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt);
bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt);
bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt);
static QString exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website);
static void setProviderIdToPreferredPrinting(const DeckList *deckList);
static void resolveSetNameAndNumberToProviderID(const DeckList *deckList);
static void saveToClipboard(const DeckList *deckList, bool addComments = true, bool addSetNameAndNumber = true);
static bool saveToStream_Plain(QTextStream &out,
const DeckList *deckList,
@@ -113,13 +76,21 @@ public:
bool convertToCockatriceFormat(QString fileName);
DeckList *getDeckList()
LoadedDeck &getDeck()
{
return deckList;
return loadedDeck;
}
const LoadedDeck &getDeck() const
{
return loadedDeck;
}
void setDeck(const LoadedDeck &deck)
{
loadedDeck = deck;
}
private:
static void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node);
static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node);
static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList);
static void saveToStream_DeckZone(QTextStream &out,
@@ -131,9 +102,6 @@ private:
QList<DecklistCardNode *> cards,
bool addComments = true,
bool addSetNameAndNumber = true);
[[nodiscard]] static QString getCardZoneFromName(const QString &cardName, QString currentZoneName);
[[nodiscard]] static QString getCompleteCardName(const QString &cardName);
};
#endif

View File

@@ -0,0 +1,11 @@
#include "loaded_deck.h"
bool LoadedDeck::LoadInfo::isEmpty() const
{
return fileName.isEmpty() && remoteDeckId == NON_REMOTE_ID;
}
bool LoadedDeck::isEmpty() const
{
return deckList.isEmpty() && lastLoadInfo.isEmpty();
}

View File

@@ -0,0 +1,39 @@
#ifndef COCKATRICE_LOADED_DECK_H
#define COCKATRICE_LOADED_DECK_H
#include "deck_file_format.h"
#include "libcockatrice/deck_list/deck_list.h"
#include <QString>
/**
* @brief Represents a deck that was loaded from somewhere.
* Contains the DeckList itself, as well as info about where it was loaded from.
*/
struct LoadedDeck
{
/**
* @brief Information about where the deck was loaded from.
*
* For local decks, the remoteDeckId field will always be -1.
* For remote decks, fileName will be empty and fileFormat will always be CockatriceFormat
*/
struct LoadInfo
{
static constexpr int NON_REMOTE_ID = -1;
QString fileName = "";
DeckFileFormat::Format fileFormat = DeckFileFormat::Cockatrice;
int remoteDeckId = NON_REMOTE_ID;
bool isEmpty() const;
};
DeckList deckList; ///< The decklist itself
LoadInfo lastLoadInfo = {}; ///< info about where the deck was loaded from
bool isEmpty() const;
};
#endif // COCKATRICE_LOADED_DECK_H

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,7 +56,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
deckModel->setObjectName("deckModel");
connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
deckLoader = new DeckLoader(this, deckModel->getDeckList());
deckLoader = new DeckLoader(this);
proxy = new DeckListStyleProxy(this);
proxy->setSourceModel(deckModel);
@@ -124,6 +124,12 @@ void DeckEditorDeckDockWidget::createDeckDock()
quickSettingsWidget->addSettingsWidget(showBannerCardCheckBox);
quickSettingsWidget->addSettingsWidget(showTagsWidgetCheckBox);
formatLabel = new QLabel(this);
formatComboBox = new QComboBox(this);
formatComboBox->addItem(tr("Loading Database..."));
formatComboBox->setEnabled(false); // Disable until loaded
commentsLabel = new QLabel();
commentsLabel->setObjectName("commentsLabel");
commentsEdit = new QTextEdit;
@@ -208,13 +214,16 @@ void DeckEditorDeckDockWidget::createDeckDock()
upperLayout->addWidget(commentsLabel, 1, 0);
upperLayout->addWidget(commentsEdit, 1, 1);
upperLayout->addWidget(bannerCardLabel, 2, 0);
upperLayout->addWidget(bannerCardComboBox, 2, 1);
upperLayout->addWidget(formatLabel, 2, 0);
upperLayout->addWidget(formatComboBox, 2, 1);
upperLayout->addWidget(deckTagsDisplayWidget, 3, 1);
upperLayout->addWidget(bannerCardLabel, 3, 0);
upperLayout->addWidget(bannerCardComboBox, 3, 1);
upperLayout->addWidget(activeGroupCriteriaLabel, 4, 0);
upperLayout->addWidget(activeGroupCriteriaComboBox, 4, 1);
upperLayout->addWidget(deckTagsDisplayWidget, 4, 1);
upperLayout->addWidget(activeGroupCriteriaLabel, 5, 0);
upperLayout->addWidget(activeGroupCriteriaComboBox, 5, 1);
hashLabel1 = new QLabel();
hashLabel1->setObjectName("hashLabel1");
@@ -263,6 +272,46 @@ void DeckEditorDeckDockWidget::createDeckDock()
refreshShortcuts();
retranslateUi();
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
&DeckEditorDeckDockWidget::initializeFormats);
if (CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok) {
initializeFormats();
}
}
void DeckEditorDeckDockWidget::initializeFormats()
{
QMap<QString, int> allFormats = CardDatabaseManager::query()->getAllFormatsWithCount();
formatComboBox->clear(); // Remove "Loading Database..."
formatComboBox->setEnabled(true);
// Populate with formats
formatComboBox->addItem("", "");
for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); ++it) {
QString displayText = QString("%1").arg(it.key());
formatComboBox->addItem(displayText, it.key()); // store the raw key in itemData
}
if (!deckModel->getDeckList()->getGameFormat().isEmpty()) {
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
} else {
// Ensure no selection is visible initially
formatComboBox->setCurrentIndex(-1);
}
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
if (index >= 0) {
QString formatKey = formatComboBox->itemData(index).toString();
deckModel->setActiveFormat(formatKey);
} else {
deckModel->setActiveFormat(QString()); // clear format if deselected
}
emit deckModified();
});
}
ExactCard DeckEditorDeckDockWidget::getCurrentCard()
@@ -298,7 +347,7 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*&current*/, const
void DeckEditorDeckDockWidget::updateName(const QString &name)
{
emit requestDeckHistorySave(
QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName()));
QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeck().deckList.getName()));
deckModel->getDeckList()->setName(name);
deckEditor->setModified(name.isEmpty());
emit nameChanged();
@@ -308,7 +357,7 @@ void DeckEditorDeckDockWidget::updateName(const QString &name)
void DeckEditorDeckDockWidget::updateComments()
{
emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)")
.arg(deckLoader->getDeckList()->getComments().size())
.arg(deckLoader->getDeck().deckList.getComments().size())
.arg(commentsEdit->toPlainText().size()));
deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
@@ -337,7 +386,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
// Collect unique (name, providerId) pairs
QSet<QPair<QString, QString>> bannerCardSet;
QList<DecklistCardNode *> cardsInDeck = deckModel->getDeckList()->getCardNodes();
QList<const DecklistCardNode *> cardsInDeck = deckModel->getDeckList()->getCardNodes();
for (auto currentCard : cardsInDeck) {
if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) {
@@ -425,13 +474,12 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
/**
* Sets the currently active deck for this tab
* @param _deck The deck. Takes ownership of the object
* @param _deck The deck.
*/
void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck)
void DeckEditorDeckDockWidget::setDeck(const LoadedDeck &_deck)
{
deckLoader = _deck;
deckLoader->setParent(this);
deckModel->setDeckList(deckLoader->getDeckList());
deckLoader->setDeck(_deck);
deckModel->setDeckList(&deckLoader->getDeck().deckList);
connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree);
emit requestDeckHistoryClear();
@@ -466,6 +514,12 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
{
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
deckView->expandAll();
deckView->expandAll();
emit deckChanged();
}
DeckLoader *DeckEditorDeckDockWidget::getDeckLoader()
@@ -588,7 +642,7 @@ bool DeckEditorDeckDockWidget::swapCard(const QModelIndex &currentIndex)
QModelIndex newCardIndex = card ? deckModel->addCard(card, otherZoneName)
// Third argument (true) says create the card no matter what, even if not in DB
: deckModel->addPreferredPrintingCard(cardName, otherZoneName, true);
recursiveExpand(proxy->mapFromSource(newCardIndex));
recursiveExpand(proxy->mapToSource(newCardIndex));
return true;
}
@@ -609,18 +663,8 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z
}
deckView->clearSelection();
setCurrentProxyIndex(idx);
offsetCountAtIndex(proxy->mapFromSource(idx), -1);
}
void DeckEditorDeckDockWidget::setCurrentProxyIndex(const QModelIndex &index)
{
deckView->setCurrentIndex(proxy->mapFromSource(index));
}
void DeckEditorDeckDockWidget::scrollToProxyIndex(const QModelIndex &index)
{
deckView->setCurrentIndex(proxy->mapFromSource(index));
deckView->setCurrentIndex(proxy->mapToSource(idx));
offsetCountAtIndex(idx, -1);
}
void DeckEditorDeckDockWidget::actDecrementSelection()
@@ -734,6 +778,8 @@ void DeckEditorDeckDockWidget::retranslateUi()
showTagsWidgetCheckBox->setText(tr("Show tags selection menu"));
commentsLabel->setText(tr("&Comments:"));
activeGroupCriteriaLabel->setText(tr("Group by:"));
formatLabel->setText(tr("Format:"));
hashLabel1->setText(tr("Hash:"));
aIncrement->setText(tr("&Increment number"));

View File

@@ -57,7 +57,7 @@ public:
public slots:
void cleanDeck();
void updateBannerCardComboBox();
void setDeck(DeckLoader *_deck);
void setDeck(const LoadedDeck &_deck);
void syncDisplayWidgetsToModel();
void sortDeckModelToDeckView();
DeckLoader *getDeckLoader();
@@ -69,9 +69,8 @@ public slots:
void actSwapCard();
void actRemoveCard();
void offsetCountAtIndex(const QModelIndex &idx, int offset);
void initializeFormats();
void expandAll();
void setCurrentProxyIndex(const QModelIndex &index);
void scrollToProxyIndex(const QModelIndex &index);
signals:
void nameChanged();
@@ -102,6 +101,8 @@ private:
LineEditUnfocusable *hashLabel;
QLabel *activeGroupCriteriaLabel;
QComboBox *activeGroupCriteriaComboBox;
QLabel *formatLabel;
QComboBox *formatComboBox;
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;

View File

@@ -23,8 +23,7 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const
if (role == Qt::BackgroundRole) {
if (isCard) {
const bool legal =
true; // TODO: Not implemented yet. QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool();
const bool legal = QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool();
int base = 255 - (index.row() % 2) * 30;
return legal ? QBrush(QColor(base, base, base)) : QBrush(QColor(255, base / 3, base / 3));
} else {

View File

@@ -109,7 +109,7 @@ void DlgCreateGame::sharedCtor()
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0);
if (room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0);
} else {
createGameAsJudgeCheckBox->setChecked(false);

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@
#include <QJsonObject>
#include <QMessageBox>
#include <QNetworkReply>
#include <version_string.h>
DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent)
{
@@ -67,6 +68,7 @@ void DlgLoadDeckFromWebsite::accept()
}
QNetworkRequest request(QUrl(info.fullUrl));
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
QNetworkReply *reply = nam->get(request);
QEventLoop loop;
@@ -95,11 +97,11 @@ void DlgLoadDeckFromWebsite::accept()
}
// Parse the plain text deck here
DeckLoader *loader = new DeckLoader(this);
DeckList deckList;
QTextStream stream(&deckText);
loader->getDeckList()->loadFromStream_Plain(stream, false);
DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
deck = loader;
deckList.loadFromStream_Plain(stream, false);
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
deck = deckList;
QDialog::accept();
return;

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
#include "dlg_select_set_for_cards.h"
#include "../../deck_loader/card_node_function.h"
#include "../../deck_loader/deck_loader.h"
#include "../interface/widgets/cards/card_info_picture_widget.h"
#include "../interface/widgets/general/layout_containers/flow_widget.h"
@@ -177,7 +178,7 @@ void DlgSelectSetForCards::actOK()
void DlgSelectSetForCards::actClear()
{
emit deckAboutToBeModified(tr("Cleared all printing information."));
DeckLoader::clearSetNamesAndNumbers(model->getDeckList());
model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData());
emit deckModified();
accept();
}
@@ -185,8 +186,8 @@ void DlgSelectSetForCards::actClear()
void DlgSelectSetForCards::actSetAllToPreferred()
{
emit deckAboutToBeModified(tr("Set all printings to preferred."));
DeckLoader::clearSetNamesAndNumbers(model->getDeckList());
DeckLoader::setProviderIdToPreferredPrinting(model->getDeckList());
model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData());
model->getDeckList()->forEachCard(CardNodeFunction::SetProviderIdToPreferred());
emit deckModified();
accept();
}
@@ -223,14 +224,10 @@ QMap<QString, int> DlgSelectSetForCards::getSetsForCards()
if (!model)
return setCounts;
DeckList *decklist = model->getDeckList();
if (!decklist)
return setCounts;
QList<QString> cardNames = model->getCardNames();
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
for (auto currentCard : cardsInDeck) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
for (auto cardName : cardNames) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName);
if (!infoPtr)
continue;
@@ -266,19 +263,15 @@ void DlgSelectSetForCards::updateCardLists()
}
}
DeckList *decklist = model->getDeckList();
if (!decklist)
return;
QList<QString> cardNames = model->getCardNames();
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
for (auto currentCard : cardsInDeck) {
for (auto cardName : cardNames) {
bool found = false;
QString foundSetName;
// Check across all sets if the card is present
for (auto it = selectedCardsBySet.begin(); it != selectedCardsBySet.end(); ++it) {
if (it.value().contains(currentCard->getName())) {
if (it.value().contains(cardName)) {
found = true;
foundSetName = it.key(); // Store the set name where it was found
break; // Stop at the first match
@@ -287,16 +280,16 @@ void DlgSelectSetForCards::updateCardLists()
if (!found) {
// The card was not in any selected set
ExactCard card = CardDatabaseManager::query()->getCard({currentCard->getName()});
ExactCard card = CardDatabaseManager::query()->getCard({cardName});
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(uneditedCardsFlowWidget);
picture_widget->setCard(card);
uneditedCardsFlowWidget->addWidget(picture_widget);
} else {
ExactCard card = CardDatabaseManager::query()->getCard(
{currentCard->getName(), CardDatabaseManager::getInstance()
->query()
->getSpecificPrinting(currentCard->getName(), foundSetName, "")
.getUuid()});
ExactCard card =
CardDatabaseManager::query()->getCard({cardName, CardDatabaseManager::getInstance()
->query()
->getSpecificPrinting(cardName, foundSetName, "")
.getUuid()});
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(modifiedCardsFlowWidget);
picture_widget->setCard(card);
modifiedCardsFlowWidget->addWidget(picture_widget);
@@ -355,20 +348,16 @@ QMap<QString, QStringList> DlgSelectSetForCards::getCardsForSets()
if (!model)
return setCards;
DeckList *decklist = model->getDeckList();
if (!decklist)
return setCards;
QList<QString> cardNames = model->getCardNames();
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
for (auto currentCard : cardsInDeck) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
for (auto cardName : cardNames) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName);
if (!infoPtr)
continue;
SetToPrintingsMap setMap = infoPtr->getSets();
for (auto it = setMap.begin(); it != setMap.end(); ++it) {
setCards[it.key()].append(currentCard->getName());
setCards[it.key()].append(cardName);
}
}

View File

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

View File

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

View File

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

View File

@@ -190,7 +190,7 @@ void CardAmountWidget::addPrinting(const QString &zone)
newCardIndex = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
rootCard.getPrinting().getProperty("num"));
deckEditor->deckDockWidget->setCurrentProxyIndex(newCardIndex);
deckView->setCurrentIndex(newCardIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
deckEditor->setModified(true);
}
@@ -256,7 +256,7 @@ void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset)
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
const int new_count = count + offset;
deckEditor->deckDockWidget->setCurrentProxyIndex(numberIndex);
deckView->setCurrentIndex(numberIndex);
if (new_count <= 0) {
deckModel->removeRow(idx.row(), idx.parent());
@@ -306,19 +306,12 @@ int CardAmountWidget::countCardsInZone(const QString &deckZone)
return -1;
}
DeckList *decklist = deckModel->getDeckList();
if (!decklist) {
return -1;
}
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes({deckZone});
QList<ExactCard> cards = deckModel->getCardsForZone(deckZone);
int count = 0;
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
if (currentCard->getCardProviderId() == rootCard.getPrinting().getProperty("uuid")) {
count++;
}
for (auto currentCard : cards) {
if (currentCard.getPrinting().getUuid() == rootCard.getPrinting().getProperty("uuid")) {
count++;
}
}

View File

@@ -185,7 +185,7 @@ void PrintingSelector::selectCard(const int changeBy)
}
if (nextIndex.isValid()) {
deckEditor->deckDockWidget->setCurrentProxyIndex(nextIndex);
deckView->setCurrentIndex(nextIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
}
}

View File

@@ -206,7 +206,7 @@ void ChatView::appendMessage(QString message,
defaultFormat = QTextCharFormat();
if (!isUserMessage) {
if (messageType == Event_RoomSay::ChatHistory) {
defaultFormat.setForeground(Qt::gray); // FIXME : hardcoded color
defaultFormat.setForeground(Qt::gray); //! \todo hardcoded color
defaultFormat.setFontWeight(QFont::Light);
defaultFormat.setFontItalic(true);
static const QRegularExpression userNameRegex("^(\\[[^\\]]*\\]\\s)(\\S+):\\s");
@@ -229,7 +229,7 @@ void ChatView::appendMessage(QString message,
message.remove(0, pos.relativePosition - 2); // do not remove semicolon
}
} else {
defaultFormat.setForeground(Qt::darkGreen); // FIXME : hardcoded color
defaultFormat.setForeground(Qt::darkGreen); //! \todo hardcoded color
defaultFormat.setFontWeight(QFont::Bold);
}
}

View File

@@ -155,7 +155,7 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneNam
QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName);
deckDockWidget->expandAll();
deckDockWidget->deckView->clearSelection();
deckDockWidget->setCurrentProxyIndex(newCardIndex);
deckDockWidget->deckView->setCurrentIndex(newCardIndex);
setModified(true);
databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length());
@@ -213,22 +213,22 @@ void AbstractTabDeckEditor::actSwapCard(const ExactCard &card, const QString &zo
/**
* @brief Opens a deck in this tab.
* @param deck DeckLoader object (takes ownership).
* @param deck The deck
*/
void AbstractTabDeckEditor::openDeck(DeckLoader *deck)
void AbstractTabDeckEditor::openDeck(const LoadedDeck &deck)
{
setDeck(deck);
if (!deck->getLastLoadInfo().fileName.isEmpty()) {
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastLoadInfo().fileName);
if (!deck.lastLoadInfo.fileName.isEmpty()) {
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck.lastLoadInfo.fileName);
}
}
/**
* @brief Sets the currently active deck.
* @param _deck DeckLoader object.
* @param _deck The deck
*/
void AbstractTabDeckEditor::setDeck(DeckLoader *_deck)
void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck)
{
deckDockWidget->setDeck(_deck);
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList()->getCardRefList()));
@@ -265,8 +265,8 @@ void AbstractTabDeckEditor::setModified(bool _modified)
*/
bool AbstractTabDeckEditor::isBlankNewDeck() const
{
DeckLoader *deck = deckDockWidget->getDeckLoader();
return !modified && deck->getDeckList()->isBlankDeck() && deck->hasNotBeenLoaded();
const LoadedDeck &loadedDeck = deckDockWidget->getDeckLoader()->getDeck();
return !modified && loadedDeck.isEmpty();
}
/** @brief Creates a new deck. Handles opening in new tab if needed. */
@@ -277,7 +277,7 @@ void AbstractTabDeckEditor::actNewDeck()
return;
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(nullptr);
emit openDeckEditor(LoadedDeck());
return;
}
@@ -380,19 +380,17 @@ void AbstractTabDeckEditor::actOpenRecent(const QString &fileName)
*/
void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation)
{
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName);
auto *l = new DeckLoader(this);
if (l->loadFromFile(fileName, fmt, true)) {
auto l = DeckLoader(this);
if (l.loadFromFile(fileName, fmt, true)) {
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(l);
l->deleteLater();
emit openDeckEditor(l.getDeck());
} else {
deckMenu->setSaveStatus(false);
openDeck(l);
openDeck(l.getDeck());
}
} else {
l->deleteLater();
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName));
}
deckMenu->setSaveStatus(true);
@@ -405,16 +403,16 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo
*/
bool AbstractTabDeckEditor::actSaveDeck()
{
DeckLoader *const deck = getDeckLoader();
if (deck->getLastLoadInfo().remoteDeckId != DeckLoader::LoadInfo::NON_REMOTE_ID) {
QString deckString = deck->getDeckList()->writeToString_Native();
const LoadedDeck &loadedDeck = getDeckLoader()->getDeck();
if (loadedDeck.lastLoadInfo.remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) {
QString deckString = loadedDeck.deckList.writeToString_Native();
if (deckString.length() > MAX_FILE_LENGTH) {
QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck"));
return false;
}
Command_DeckUpload cmd;
cmd.set_deck_id(static_cast<google::protobuf::uint32>(deck->getLastLoadInfo().remoteDeckId));
cmd.set_deck_id(static_cast<google::protobuf::uint32>(loadedDeck.lastLoadInfo.remoteDeckId));
cmd.set_deck_list(deckString.toStdString());
PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd);
@@ -422,9 +420,11 @@ bool AbstractTabDeckEditor::actSaveDeck()
tabSupervisor->getClient()->sendCommand(pend);
return true;
} else if (deck->getLastLoadInfo().fileName.isEmpty())
}
if (loadedDeck.lastLoadInfo.fileName.isEmpty())
return actSaveDeckAs();
else if (deck->saveToFile(deck->getLastLoadInfo().fileName, deck->getLastLoadInfo().fileFormat)) {
if (getDeckLoader()->saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) {
setModified(false);
return true;
}
@@ -452,7 +452,7 @@ bool AbstractTabDeckEditor::actSaveDeckAs()
return false;
QString fileName = dialog.selectedFiles().at(0);
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName);
if (!getDeckLoader()->saveToFile(fileName, fmt)) {
QMessageBox::critical(
@@ -493,9 +493,9 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard()
return;
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeckList());
emit openDeckEditor({.deckList = dlg.getDeckList()});
} else {
setDeck(dlg.getDeckList());
setDeck({.deckList = dlg.getDeckList()});
setModified(true);
}
@@ -508,11 +508,11 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard()
*/
void AbstractTabDeckEditor::editDeckInClipboard(bool annotated)
{
DlgEditDeckInClipboard dlg(getDeckLoader(), annotated, this);
DlgEditDeckInClipboard dlg(getDeckLoader()->getDeck().deckList, annotated, this);
if (!dlg.exec())
return;
setDeck(dlg.getDeckList());
setDeck({dlg.getDeckList(), getDeckLoader()->getDeck().lastLoadInfo});
setModified(true);
deckMenu->setSaveStatus(true);
}
@@ -576,9 +576,9 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite()
return;
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeck());
emit openDeckEditor({.deckList = dlg.getDeck()});
} else {
setDeck(dlg.getDeck());
setDeck({.deckList = dlg.getDeck()});
setModified(true);
}
@@ -591,8 +591,8 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite()
*/
void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
{
if (DeckLoader *const deck = getDeckLoader()) {
QString decklistUrlString = deck->exportDeckToDecklist(getDeckList(), website);
if (DeckList *deckList = getDeckList()) {
QString decklistUrlString = DeckLoader::exportDeckToDecklist(deckList, website);
// Check to make sure the string isn't empty.
if (decklistUrlString.isEmpty()) {
// Show an error if the deck is empty, and return.

View File

@@ -114,9 +114,9 @@ public:
virtual void retranslateUi() override = 0;
/** @brief Opens a deck in this tab.
* @param deck Pointer to a DeckLoader object.
* @param deck The deck to open
*/
void openDeck(DeckLoader *deck);
void openDeck(const LoadedDeck &deck);
/** @brief Returns the currently active deck loader. */
DeckLoader *getDeckLoader() const;
@@ -198,7 +198,7 @@ public slots:
signals:
/** @brief Emitted when a deck should be opened in a new editor tab. */
void openDeckEditor(DeckLoader *deckLoader);
void openDeckEditor(const LoadedDeck &deck);
/** @brief Emitted before the tab is closed. */
void deckEditorClosing(AbstractTabDeckEditor *tab);
@@ -286,7 +286,7 @@ private:
/** @brief Sets the deck for this tab.
* @param _deck The deck object.
*/
virtual void setDeck(DeckLoader *_deck);
virtual void setDeck(const LoadedDeck &_deck);
/** @brief Helper for editing decks from the clipboard. */
void editDeckInClipboard(bool annotated);

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,12 @@
#include "archidekt_api_response_deck_display_widget.h"
#include "../../../../../deck_loader/card_node_function.h"
#include "../../../../../deck_loader/deck_loader.h"
#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
#include "../../../../cards/card_size_widget.h"
#include "../../../../cards/deck_card_zone_display_widget.h"
#include "../../../../visual_deck_editor/visual_deck_display_options_widget.h"
#include "../api_response/archidekt_formats.h"
#include "../api_response/deck/archidekt_api_response_deck.h"
#include <QSortFilterProxyModel>
@@ -68,7 +70,7 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset);
model->getDeckList()->loadFromStream_Plain(deckStream, false);
DeckLoader::resolveSetNameAndNumberToProviderID(model->getDeckList());
model->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId());
model->rebuildTree();
@@ -88,12 +90,14 @@ void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString
void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor()
{
auto loader = new DeckLoader(this);
loader->getDeckList()->loadFromString_Native(model->getDeckList()->writeToString_Native());
DeckList deckList(*model->getDeckList());
deckList.setName(response.getDeckName());
deckList.setGameFormat(
ArchidektFormats::formatToCockatriceName(ArchidektFormats::DeckFormat(response.getDeckFormat() - 1)));
loader->getDeckList()->setName(response.getDeckName());
LoadedDeck loadedDeck = {deckList, {}};
emit openInDeckEditor(loader);
emit openInDeckEditor(loadedDeck);
}
void ArchidektApiResponseDeckDisplayWidget::clearAllDisplayWidgets()

View File

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

View File

@@ -12,10 +12,11 @@
#include <QNetworkReply>
#include <QPixmap>
#include <QWidget>
#include <version_string.h>
#define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg"
QString timeAgo(const QString &timestamp)
static QString timeAgo(const QString &timestamp)
{
QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate);
@@ -82,6 +83,7 @@ ArchidektApiResponseDeckEntryDisplayWidget::ArchidektApiResponseDeckEntryDisplay
imageUrl = response.getFeatured().isEmpty() ? QUrl(ARCHIDEKT_DEFAULT_IMAGE) : QUrl(response.getFeatured());
QNetworkRequest req(imageUrl);
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
QNetworkReply *reply = imageNetworkManager->get(req);
// tag the reply with "this" so we know it belongs to us later

View File

@@ -22,6 +22,7 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
#include <libcockatrice/models/database/card/card_search_model.h>
#include <version_string.h>
TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
{
@@ -213,7 +214,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
minDeckSizeLogicCombo->addItems({"Exact", "", ""}); // Exact = unset, ≥ = GTE, ≤ = LTE
minDeckSizeLogicCombo->setCurrentIndex(1); // default GTE
connect(minDeckSizeSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
connect(minDeckSizeSpin, qOverload<int>(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
// Page number
@@ -223,7 +224,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
pageSpin->setRange(1, 9999);
pageSpin->setValue(1);
connect(pageSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
connect(pageSpin, qOverload<int>(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
// Page display
currentPageDisplay = new QWidget(container);
@@ -426,18 +427,21 @@ void TabArchidekt::doSearchImmediate()
{
QString url = buildSearchUrl();
QNetworkRequest req{QUrl(url)};
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
networkManager->get(req);
}
void TabArchidekt::actNavigatePage(QString url)
{
QNetworkRequest request{QUrl(url)};
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
networkManager->get(request);
}
void TabArchidekt::getTopDecks()
{
QNetworkRequest request{QUrl(buildSearchUrl())};
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
networkManager->get(request);
}

View File

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

View File

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

View File

@@ -25,6 +25,7 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
#include <libcockatrice/models/database/card/card_search_model.h>
#include <version_string.h>
static bool canBeCommander(const CardInfoPtr &cardInfo)
{
@@ -166,6 +167,7 @@ void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander)
}
QNetworkRequest request{QUrl(url)};
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
networkManager->get(request);
}
@@ -173,6 +175,7 @@ void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander)
void TabEdhRecMain::actNavigatePage(QString url)
{
QNetworkRequest request{QUrl("https://json.edhrec.com/pages" + url + ".json")};
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
networkManager->get(request);
}
@@ -180,6 +183,7 @@ void TabEdhRecMain::actNavigatePage(QString url)
void TabEdhRecMain::getTopCards()
{
QNetworkRequest request{QUrl("https://json.edhrec.com/pages/top/year.json")};
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
networkManager->get(request);
}
@@ -187,6 +191,7 @@ void TabEdhRecMain::getTopCards()
void TabEdhRecMain::getTopCommanders()
{
QNetworkRequest request{QUrl("https://json.edhrec.com/pages/commanders/year.json")};
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
networkManager->get(request);
}
@@ -194,7 +199,7 @@ void TabEdhRecMain::getTopCommanders()
void TabEdhRecMain::getTopTags()
{
QNetworkRequest request{QUrl("https://json.edhrec.com/pages/tags.json")};
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
networkManager->get(request);
}
@@ -358,7 +363,7 @@ void TabEdhRecMain::processAverageDeckResponse(QJsonObject reply)
{
EdhrecAverageDeckApiResponse deckData;
deckData.fromJson(reply);
tabSupervisor->openDeckInNewTab(deckData.deck.deckLoader);
tabSupervisor->openDeckInNewTab({deckData.deck.deck, {}});
}
void TabEdhRecMain::prettyPrintJson(const QJsonValue &value, int indentLevel)

View File

@@ -12,7 +12,6 @@ class CardDatabaseDisplayModel;
class DeckListModel;
class QLabel;
class DeckLoader;
/**
* @class TabDeckEditor

View File

@@ -242,10 +242,10 @@ void TabDeckStorage::actOpenLocalDeck()
QString filePath = localDirModel->filePath(curLeft);
auto deckLoader = new DeckLoader(this);
if (!deckLoader->loadFromFile(filePath, DeckLoader::CockatriceFormat, true))
if (!deckLoader->loadFromFile(filePath, DeckFileFormat::Cockatrice, true))
continue;
emit openDeckEditor(deckLoader);
emit openDeckEditor(deckLoader->getDeck());
}
}
@@ -307,13 +307,15 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa
QFile deckFile(filePath);
QFileInfo deckFileInfo(deckFile);
DeckLoader deck(this);
if (!deck.loadFromFile(filePath, DeckLoader::CockatriceFormat)) {
DeckLoader deckLoader(this);
if (!deckLoader.loadFromFile(filePath, DeckFileFormat::Cockatrice)) {
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
return;
}
if (deck.getDeckList()->getName().isEmpty()) {
DeckList deck = deckLoader.getDeck().deckList;
if (deck.getName().isEmpty()) {
bool ok;
QString deckName =
getTextWithMax(this, tr("Enter deck name"), tr("This decklist does not have a name.\nPlease enter a name:"),
@@ -322,12 +324,12 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa
return;
if (deckName.isEmpty())
deckName = tr("Unnamed deck");
deck.getDeckList()->setName(deckName);
deck.setName(deckName);
} else {
deck.getDeckList()->setName(deck.getDeckList()->getName().left(MAX_NAME_LENGTH));
deck.setName(deck.getName().left(MAX_NAME_LENGTH));
}
QString deckString = deck.getDeckList()->writeToString_Native();
QString deckString = deck.writeToString_Native();
if (deckString.length() > MAX_FILE_LENGTH) {
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
return;
@@ -436,7 +438,7 @@ void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandCont
if (!loader.loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id()))
return;
emit openDeckEditor(&loader);
emit openDeckEditor(loader.getDeck());
}
void TabDeckStorage::actDownload()
@@ -492,8 +494,12 @@ void TabDeckStorage::downloadFinished(const Response &r,
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
QString filePath = extraData.toString();
DeckLoader deck(this, new DeckList(QString::fromStdString(resp.deck())));
deck.saveToFile(filePath, DeckLoader::CockatriceFormat);
DeckList deckList = DeckList(QString::fromStdString(resp.deck()));
DeckLoader deckLoader(this);
deckLoader.setDeck({deckList, {}});
deckLoader.saveToFile(filePath, DeckFileFormat::Cockatrice);
}
void TabDeckStorage::actNewFolder()

View File

@@ -13,6 +13,7 @@
#include <libcockatrice/network/client/abstract/abstract_client.h>
struct LoadedDeck;
class ServerInfo_User;
class AbstractClient;
class QTreeView;
@@ -23,7 +24,6 @@ class QTreeWidgetItem;
class QGroupBox;
class CommandContainer;
class Response;
class DeckLoader;
class TabDeckStorage : public Tab
{
@@ -87,7 +87,7 @@ public:
return tr("Deck Storage");
}
signals:
void openDeckEditor(DeckLoader *deckLoader);
void openDeckEditor(const LoadedDeck &deck);
};
#endif

View File

@@ -749,11 +749,10 @@ void TabGame::loadDeckForLocalPlayer(Player *localPlayer, int playerId, ServerIn
{
TabbedDeckViewContainer *deckViewContainer = deckViewContainers.value(playerId);
if (playerInfo.has_deck_list()) {
DeckLoader newDeck(this, new DeckList(QString::fromStdString(playerInfo.deck_list())));
CardPictureLoader::cacheCardPixmaps(
CardDatabaseManager::query()->getCards(newDeck.getDeckList()->getCardRefList()));
deckViewContainer->playerDeckView->setDeck(newDeck);
localPlayer->setDeck(newDeck);
DeckList deckList = DeckList(QString::fromStdString(playerInfo.deck_list()));
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(deckList.getCardRefList()));
deckViewContainer->playerDeckView->setDeck(deckList);
localPlayer->setDeck(deckList);
}
}

View File

@@ -45,7 +45,6 @@ class ReplayTimelineWidget;
class CardZone;
class AbstractCardItem;
class CardItem;
class DeckLoader;
class QVBoxLayout;
class QHBoxLayout;
class GameReplay;
@@ -119,7 +118,7 @@ signals:
void containerProcessingStarted(const GameEventContext &context);
void containerProcessingDone();
void openMessageDialog(const QString &userName, bool focus);
void openDeckEditor(DeckLoader *deck);
void openDeckEditor(const LoadedDeck &deck);
void notIdle();
void phaseChanged(int phase);

View File

@@ -133,10 +133,10 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
// create tabs menu actions
aTabDeckEditor = new QAction(this);
connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(nullptr); });
connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(LoadedDeck()); });
aTabVisualDeckEditor = new QAction(this);
connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(nullptr); });
connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(LoadedDeck()); });
aTabEdhRec = new QAction(this);
connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); });
@@ -846,9 +846,9 @@ void TabSupervisor::talkLeft(TabMessage *tab)
/**
* Creates a new deck editor tab and loads the deck into it.
* Creates either a classic or visual deck editor tab depending on settings
* @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance.
* @param deckToOpen The deck to open in the tab.
*/
void TabSupervisor::openDeckInNewTab(DeckLoader *deckToOpen)
void TabSupervisor::openDeckInNewTab(const LoadedDeck &deckToOpen)
{
int type = SettingsCache::instance().getDefaultDeckEditorType();
switch (type) {
@@ -868,13 +868,12 @@ void TabSupervisor::openDeckInNewTab(DeckLoader *deckToOpen)
/**
* Creates a new deck editor tab
* @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance.
* @param deckToOpen The deck to open in the tab.
*/
TabDeckEditor *TabSupervisor::addDeckEditorTab(DeckLoader *deckToOpen)
TabDeckEditor *TabSupervisor::addDeckEditorTab(const LoadedDeck &deckToOpen)
{
auto *tab = new TabDeckEditor(this);
if (deckToOpen)
tab->openDeck(deckToOpen);
tab->openDeck(deckToOpen);
connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed);
connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addDeckEditorTab);
myAddTab(tab);
@@ -883,11 +882,10 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(DeckLoader *deckToOpen)
return tab;
}
TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(DeckLoader *deckToOpen)
TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(const LoadedDeck &deckToOpen)
{
auto *tab = new TabDeckEditorVisual(this);
if (deckToOpen)
tab->openDeck(deckToOpen);
tab->openDeck(deckToOpen);
connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed);
connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addVisualDeckEditorTab);
myAddTab(tab);

View File

@@ -168,9 +168,9 @@ signals:
void showWindowIfHidden();
public slots:
void openDeckInNewTab(DeckLoader *deckToOpen);
TabDeckEditor *addDeckEditorTab(DeckLoader *deckToOpen);
TabDeckEditorVisual *addVisualDeckEditorTab(DeckLoader *deckToOpen);
void openDeckInNewTab(const LoadedDeck &deckToOpen);
TabDeckEditor *addDeckEditorTab(const LoadedDeck &deckToOpen);
TabDeckEditorVisual *addVisualDeckEditorTab(const LoadedDeck &deckToOpen);
TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab();
TabEdhRecMain *addEdhrecMainTab();
TabArchidekt *addArchidektTab();

View File

@@ -232,8 +232,8 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event,
return;
} else {
// Normal click = clear selection, select this, set current
deckDockWidget->setCurrentProxyIndex(idx);
deckDockWidget->scrollToProxyIndex(idx);
deckDockWidget->deckView->setCurrentIndex(idx);
deckDockWidget->deckView->scrollTo(idx);
return;
}

View File

@@ -24,11 +24,11 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor)
void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath)
{
auto deckLoader = new DeckLoader(this);
if (!deckLoader->loadFromFile(filePath, DeckLoader::getFormatFromName(filePath), true)) {
auto deckLoader = DeckLoader(this);
if (!deckLoader.loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true)) {
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath));
return;
}
emit openDeckEditor(deckLoader);
emit openDeckEditor(deckLoader.getDeck());
}

View File

@@ -9,9 +9,9 @@
#include "../tab.h"
struct LoadedDeck;
class AbstractClient;
class CommandContainer;
class DeckLoader;
class DeckPreviewWidget;
class QFileSystemModel;
class QGroupBox;
@@ -39,7 +39,7 @@ public slots:
void actOpenLocalDeck(const QString &filePath);
signals:
void openDeckEditor(DeckLoader *deckLoader);
void openDeckEditor(const LoadedDeck &deck);
private:
VisualDeckStorageWidget *visualDeckStorageWidget;

View File

@@ -0,0 +1,205 @@
#include "visual_database_display_format_legality_filter_widget.h"
#include "../../../filters/filter_tree_model.h"
#include <QPushButton>
#include <QSpinBox>
#include <QTimer>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/filters/filter_tree.h>
VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLegalityFilterWidget(
QWidget *parent,
FilterTreeModel *_filterModel)
: QWidget(parent), filterModel(_filterModel)
{
allFormatsWithCount = CardDatabaseManager::query()->getAllFormatsWithCount();
setMaximumHeight(75);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
layout = new QHBoxLayout(this);
setLayout(layout);
layout->setContentsMargins(0, 1, 0, 1);
layout->setSpacing(1);
layout->setAlignment(Qt::AlignTop);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
// Create the spinbox
spinBox = new QSpinBox(this);
spinBox->setMinimum(1);
spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically
spinBox->setValue(150);
layout->addWidget(spinBox);
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility);
// Create the toggle button for Exact Match/Includes mode
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
connect(toggleButton, &QPushButton::toggled, this,
&VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode);
connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() {
QTimer::singleShot(100, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel);
});
createFormatButtons(); // Populate buttons initially
updateFilterMode(false); // Initialize toggle button text
retranslateUi();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::retranslateUi()
{
spinBox->setToolTip(tr("Do not display formats with less than this amount of cards in the database"));
toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)"));
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons()
{
// Iterate through main types and create buttons
for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) {
auto *button = new QPushButton(it.key(), flowWidget);
button->setCheckable(true);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:checked { background-color: green; color: white; }");
flowWidget->addWidget(button);
formatButtons[it.key()] = button;
// Connect toggle signal
connect(button, &QPushButton::toggled, this,
[this, mainType = it.key()](bool checked) { handleFormatToggled(mainType, checked); });
}
updateFormatButtonsVisibility(); // Ensure visibility is updated initially
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility()
{
int threshold = spinBox->value(); // Get the current spinbox value
// Iterate through buttons and hide/disable those below the threshold
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
bool visible = allFormatsWithCount[it.key()] >= threshold;
it.value()->setVisible(visible);
it.value()->setEnabled(visible);
}
}
int VisualDatabaseDisplayFormatLegalityFilterWidget::getMaxMainTypeCount() const
{
int maxCount = 1;
for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) {
maxCount = qMax(maxCount, it.value());
}
return maxCount;
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::handleFormatToggled(const QString &format, bool active)
{
activeFormats[format] = active;
if (formatButtons.contains(format)) {
formatButtons[format]->setChecked(active);
}
updateFormatFilter();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatFilter()
{
// Clear existing filters related to main type
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
filterModel->clearFiltersOfType(CardFilter::Attr::AttrFormat);
if (exactMatchMode) {
// Exact Match: Only selected main types are allowed
QSet<QString> selectedTypes;
for (const auto &type : activeFormats.keys()) {
if (activeFormats[type]) {
selectedTypes.insert(type);
}
}
if (!selectedTypes.isEmpty()) {
// Require all selected types (TypeAnd)
for (const auto &type : selectedTypes) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat));
}
// Exclude any other types (TypeAndNot)
for (const auto &type : formatButtons.keys()) {
if (!selectedTypes.contains(type)) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrFormat));
}
}
}
} else {
// Default Includes Mode (TypeOr) - match any selected main types
for (const auto &type : activeFormats.keys()) {
if (activeFormats[type]) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat));
}
}
}
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
emit filterModel->filterTree()->changed();
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode(bool checked)
{
exactMatchMode = checked;
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
updateFormatFilter();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel()
{
// Temporarily block signals for each button to prevent toggling while updating button states
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
it.value()->blockSignals(true);
}
// Uncheck all buttons
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
it.value()->setChecked(false);
}
// Get active filters for main types
QSet<QString> activeTypes;
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrFormat)) {
if (filter->type() == CardFilter::Type::TypeAnd) {
activeTypes.insert(filter->term());
}
}
// Check the buttons for active types
for (const auto &type : activeTypes) {
activeFormats[type] = true;
if (formatButtons.contains(type)) {
formatButtons[type]->setChecked(true);
}
}
// Re-enable signal emissions for each button
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
it.value()->blockSignals(false);
}
// Update the visibility of buttons
updateFormatButtonsVisibility();
}

View File

@@ -0,0 +1,43 @@
#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H
#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H
#include "../../../filters/filter_tree_model.h"
#include "../general/layout_containers/flow_widget.h"
#include <QMap>
#include <QPushButton>
#include <QSpinBox>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidget>
class VisualDatabaseDisplayFormatLegalityFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplayFormatLegalityFilterWidget(QWidget *parent, FilterTreeModel *filterModel);
void retranslateUi();
void createFormatButtons();
void updateFormatButtonsVisibility();
int getMaxMainTypeCount() const;
void handleFormatToggled(const QString &format, bool active);
void updateFormatFilter();
void updateFilterMode(bool checked);
void syncWithFilterModel();
private:
FilterTreeModel *filterModel;
QMap<QString, int> allFormatsWithCount;
QSpinBox *spinBox;
QHBoxLayout *layout;
FlowWidget *flowWidget;
QPushButton *toggleButton; // Mode switch button
QMap<QString, bool> activeFormats; // Track active filters
QMap<QString, QPushButton *> formatButtons; // Store toggle buttons
bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes"
};
#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H

View File

@@ -33,7 +33,7 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi
spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically
spinBox->setValue(150);
layout->addWidget(spinBox);
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility);
// Create the toggle button for Exact Match/Includes mode

View File

@@ -51,7 +51,7 @@ VisualDatabaseDisplayNameFilterWidget::VisualDatabaseDisplayNameFilterWidget(QWi
void VisualDatabaseDisplayNameFilterWidget::retranslateUi()
{
searchBox->setPlaceholderText(tr("Filter by name..."));
searchBox->setPlaceholderText(tr("Filter by name... (Exact match)"));
loadFromDeckButton->setText(tr("Load from Deck"));
loadFromDeckButton->setToolTip(tr("Apply all card names in currently loaded deck as exact match name filters"));
loadFromClipboardButton->setText(tr("Load from Clipboard"));
@@ -64,14 +64,10 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck()
if (!deckListModel)
return;
DeckList *decklist = deckListModel->getDeckList();
if (!decklist)
return;
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
for (auto currentCard : cardsInDeck) {
createNameFilter(currentCard->getName());
QList<QString> cardNames = deckListModel->getCardNames();
for (auto cardName : cardNames) {
createNameFilter(cardName);
}
updateFilterModel();
@@ -83,7 +79,7 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromClipboard()
if (!dlg.exec())
return;
QStringList cardsInClipboard = dlg.getDeckList()->getDeckList()->getCardList();
QStringList cardsInClipboard = dlg.getDeckList().getCardList();
for (QString cardName : cardsInClipboard) {
createNameFilter(cardName);
}
@@ -123,14 +119,14 @@ void VisualDatabaseDisplayNameFilterWidget::updateFilterModel()
{
// Clear existing name filters
emit filterModel->layoutAboutToBeChanged();
filterModel->clearFiltersOfType(CardFilter::Attr::AttrName);
filterModel->clearFiltersOfType(CardFilter::Attr::AttrNameExact);
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
for (const auto &name : activeFilters.keys()) {
QString nameString = name;
filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrName));
filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrNameExact));
}
filterModel->blockSignals(false);
@@ -146,7 +142,7 @@ void VisualDatabaseDisplayNameFilterWidget::updateFilterModel()
void VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel()
{
QStringList currentFilters;
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrName)) {
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrNameExact)) {
if (filter->type() == CardFilter::Type::TypeOr) {
currentFilters.append(filter->term());
}

View File

@@ -27,7 +27,7 @@ VisualDatabaseDisplayRecentSetFilterSettingsWidget::VisualDatabaseDisplayRecentS
filterToMostRecentSetsAmount->setMaximum(100);
filterToMostRecentSetsAmount->setValue(
SettingsCache::instance().getVisualDatabaseDisplayFilterToMostRecentSetsAmount());
connect(filterToMostRecentSetsAmount, QOverload<int>::of(&QSpinBox::valueChanged), &SettingsCache::instance(),
connect(filterToMostRecentSetsAmount, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance(),
&SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount);
layout->addWidget(filterToMostRecentSetsCheckBox);

View File

@@ -26,7 +26,7 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg
spinBox->setMaximum(getMaxSubTypeCount());
spinBox->setValue(150);
layout->addWidget(spinBox);
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility);
// Create search box

View File

@@ -206,6 +206,7 @@ void VisualDatabaseDisplayWidget::initialize()
saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel);
nameFilterWidget = new VisualDatabaseDisplayNameFilterWidget(this, deckEditor, filterModel);
mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel);
formatLegalityWidget = new VisualDatabaseDisplayFormatLegalityFilterWidget(this, filterModel);
subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel);
setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel);
@@ -223,6 +224,7 @@ void VisualDatabaseDisplayWidget::initialize()
filterContainerLayout->addWidget(quickFilterSubTypeWidget);
filterContainerLayout->addWidget(quickFilterSetWidget);
filterContainerLayout->addWidget(mainTypeFilterWidget);
filterContainerLayout->addWidget(formatLegalityWidget);
searchLayout->addWidget(colorFilterWidget);
searchLayout->addWidget(clearFilterWidget);

View File

@@ -17,6 +17,7 @@
#include "../utility/custom_line_edit.h"
#include "visual_database_display_color_filter_widget.h"
#include "visual_database_display_filter_save_load_widget.h"
#include "visual_database_display_format_legality_filter_widget.h"
#include "visual_database_display_main_type_filter_widget.h"
#include "visual_database_display_name_filter_widget.h"
#include "visual_database_display_set_filter_widget.h"
@@ -91,6 +92,7 @@ private:
SettingsButtonWidget *quickFilterNameWidget;
VisualDatabaseDisplayNameFilterWidget *nameFilterWidget;
VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget;
VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget;
SettingsButtonWidget *quickFilterSubTypeWidget;
VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget;
SettingsButtonWidget *quickFilterSetWidget;

View File

@@ -2,7 +2,9 @@
#include "../tabs/visual_deck_editor/tab_deck_editor_visual.h"
VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent)
#include <libcockatrice/utility/qt_utils.h>
VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) : QWidget(parent)
{
groupAndSortLayout = new QHBoxLayout(this);
groupAndSortLayout->setAlignment(Qt::AlignLeft);
@@ -11,23 +13,19 @@ VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent)
groupByLabel = new QLabel(this);
groupByComboBox = new QComboBox(this);
if (auto visualDeckEditorWidget = qobject_cast<VisualDeckEditorWidget *>(parent)) {
if (auto tabWidget = qobject_cast<TabDeckEditorVisualTabWidget *>(visualDeckEditorWidget)) {
// Inside a central widget QWidget container inside TabDeckEditorVisual
if (auto tab = qobject_cast<TabDeckEditorVisual *>(tabWidget->parent()->parent())) {
auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox();
groupByComboBox->setModel(originalBox->model());
groupByComboBox->setModelColumn(originalBox->modelColumn());
// Original -> clone
connect(originalBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this](int index) { groupByComboBox->setCurrentIndex(index); });
if (auto tab = QtUtils::findParentOfType<TabDeckEditorVisual>(this)) {
auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox();
groupByComboBox->setModel(originalBox->model());
groupByComboBox->setModelColumn(originalBox->modelColumn());
// Clone -> original
connect(groupByComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
[originalBox](int index) { originalBox->setCurrentIndex(index); });
}
}
// Original -> clone
connect(originalBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this](int index) { groupByComboBox->setCurrentIndex(index); });
// Clone -> original
connect(groupByComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
[originalBox](int index) { originalBox->setCurrentIndex(index); });
} else {
groupByComboBox->addItem(
tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::MAIN_TYPE))),

View File

@@ -24,9 +24,9 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare
handSizeSpinBox = new QSpinBox(this);
handSizeSpinBox->setValue(SettingsCache::instance().getVisualDeckEditorSampleHandSize());
handSizeSpinBox->setMinimum(1);
connect(handSizeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), &SettingsCache::instance(),
connect(handSizeSpinBox, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance(),
&SettingsCache::setVisualDeckEditorSampleHandSize);
connect(handSizeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
connect(handSizeSpinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDeckEditorSampleHandWidget::updateDisplay);
resetAndHandSizeLayout->addWidget(handSizeSpinBox);
@@ -76,25 +76,11 @@ void VisualDeckEditorSampleHandWidget::updateDisplay()
QList<ExactCard> VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGet)
{
QList<ExactCard> mainDeckCards;
QList<ExactCard> randomCards;
if (!deckListModel)
return randomCards;
DeckList *decklist = deckListModel->getDeckList();
if (!decklist)
return randomCards;
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes({DECK_ZONE_MAIN});
// Collect all cards in the main deck, allowing duplicates based on their count
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef());
if (card) {
mainDeckCards.append(card);
}
}
}
QList<ExactCard> mainDeckCards = deckListModel->getCardsForZone(DECK_ZONE_MAIN);
if (mainDeckCards.isEmpty())
return randomCards;

View File

@@ -79,8 +79,8 @@ static QStringList findAllKnownTags()
QStringList knownTags;
auto loader = DeckLoader(nullptr);
for (const QString &file : allFiles) {
loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false);
QStringList tags = loader.getDeckList()->getTags();
loader.loadFromFile(file, DeckFileFormat::getFormatFromName(file), false);
QStringList tags = loader.getDeck().deckList.getTags();
knownTags.append(tags);
knownTags.removeDuplicates();
}
@@ -125,7 +125,7 @@ static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath)
static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget)
{
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName;
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getDeck().lastLoadInfo.fileName;
deckPreviewWidget->refreshBannerCardText();
}
@@ -136,7 +136,7 @@ static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget)
*/
bool DeckPreviewDeckTagsDisplayWidget::promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget)
{
if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) == DeckLoader::CockatriceFormat) {
if (DeckFileFormat::getFormatFromName(deckPreviewWidget->filePath) == DeckFileFormat::Cockatrice) {
return true;
}

View File

@@ -32,7 +32,7 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent,
many deck loads have finished already and if we've loaded all decks and THEN load all the tags at once. */
connect(deckLoader, &DeckLoader::loadFinished, visualDeckStorageWidget->tagFilterWidget,
&VisualDeckStorageTagFilterWidget::refreshTags);
deckLoader->loadFromFileAsync(filePath, DeckLoader::getFormatFromName(filePath), false);
deckLoader->loadFromFileAsync(filePath, DeckFileFormat::getFormatFromName(filePath), false);
bannerCardDisplayWidget =
new DeckPreviewCardPictureWidget(this, false, visualDeckStorageWidget->deckPreviewSelectionAnimationEnabled);
@@ -74,16 +74,16 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess)
if (!deckLoadSuccess) {
return;
}
auto bannerCard = deckLoader->getDeckList()->getBannerCard().name.isEmpty()
auto bannerCard = deckLoader->getDeck().deckList.getBannerCard().name.isEmpty()
? ExactCard()
: CardDatabaseManager::query()->getCard(deckLoader->getDeckList()->getBannerCard());
: CardDatabaseManager::query()->getCard(deckLoader->getDeck().deckList.getBannerCard());
bannerCardDisplayWidget->setCard(bannerCard);
bannerCardDisplayWidget->setFontSize(24);
setFilePath(deckLoader->getLastLoadInfo().fileName);
setFilePath(deckLoader->getDeck().lastLoadInfo.fileName);
colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()->getTags());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeck().deckList.getTags());
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, &DeckPreviewWidget::setTags);
bannerCardLabel = new QLabel(this);
@@ -91,7 +91,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess)
bannerCardComboBox = new QComboBox(this);
bannerCardComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
bannerCardComboBox->setObjectName("bannerCardComboBox");
bannerCardComboBox->setCurrentText(deckLoader->getDeckList()->getBannerCard().name);
bannerCardComboBox->setCurrentText(deckLoader->getDeck().deckList.getBannerCard().name);
bannerCardComboBox->installEventFilter(new NoScrollFilter());
connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&DeckPreviewWidget::setBannerCard);
@@ -153,7 +153,7 @@ void DeckPreviewWidget::updateTagsVisibility(bool visible)
QString DeckPreviewWidget::getColorIdentity()
{
QStringList cardList = deckLoader->getDeckList()->getCardList();
QStringList cardList = deckLoader->getDeck().deckList.getCardList();
if (cardList.isEmpty()) {
return {};
}
@@ -187,8 +187,8 @@ QString DeckPreviewWidget::getColorIdentity()
*/
QString DeckPreviewWidget::getDisplayName() const
{
return deckLoader->getDeckList()->getName().isEmpty() ? QFileInfo(deckLoader->getLastLoadInfo().fileName).fileName()
: deckLoader->getDeckList()->getName();
QString deckName = deckLoader->getDeck().deckList.getName();
return !deckName.isEmpty() ? deckName : QFileInfo(deckLoader->getDeck().lastLoadInfo.fileName).fileName();
}
void DeckPreviewWidget::setFilePath(const QString &_filePath)
@@ -235,7 +235,7 @@ void DeckPreviewWidget::updateBannerCardComboBox()
// Prepare the new items with deduplication
QSet<QPair<QString, QString>> bannerCardSet;
QList<DecklistCardNode *> cardsInDeck = deckLoader->getDeckList()->getCardNodes();
QList<const DecklistCardNode *> cardsInDeck = deckLoader->getDeck().deckList.getCardNodes();
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
@@ -269,7 +269,7 @@ void DeckPreviewWidget::updateBannerCardComboBox()
bannerCardComboBox->setCurrentIndex(restoredIndex);
} else {
// Add a placeholder "-" and set it as the current selection
int bannerIndex = bannerCardComboBox->findText(deckLoader->getDeckList()->getBannerCard().name);
int bannerIndex = bannerCardComboBox->findText(deckLoader->getDeck().deckList.getBannerCard().name);
if (bannerIndex != -1) {
bannerCardComboBox->setCurrentIndex(bannerIndex);
} else {
@@ -287,8 +287,8 @@ void DeckPreviewWidget::setBannerCard(int /* changedIndex */)
{
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
CardRef cardRef = {name, id};
deckLoader->getDeckList()->setBannerCard(cardRef);
deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath));
deckLoader->getDeck().deckList.setBannerCard(cardRef);
deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath));
bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef));
}
@@ -310,8 +310,8 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC
void DeckPreviewWidget::setTags(const QStringList &tags)
{
deckLoader->getDeckList()->setTags(tags);
deckLoader->saveToFile(filePath, DeckLoader::CockatriceFormat);
deckLoader->getDeck().deckList.setTags(tags);
deckLoader->saveToFile(filePath, DeckFileFormat::Cockatrice);
}
QMenu *DeckPreviewWidget::createRightClickMenu()
@@ -320,7 +320,7 @@ QMenu *DeckPreviewWidget::createRightClickMenu()
menu->setAttribute(Qt::WA_DeleteOnClose);
connect(menu->addAction(tr("Open in deck editor")), &QAction::triggered, this,
[this] { emit openDeckEditor(deckLoader); });
[this] { emit openDeckEditor(deckLoader->getDeck()); });
connect(menu->addAction(tr("Edit Tags")), &QAction::triggered, deckTagsDisplayWidget,
&DeckPreviewDeckTagsDisplayWidget::openTagEditDlg);
@@ -334,13 +334,13 @@ QMenu *DeckPreviewWidget::createRightClickMenu()
auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard"));
connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this,
[this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, true); });
[this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, true, true); });
connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this,
[this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, false); });
[this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, true, false); });
connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this,
[this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, true); });
[this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, false, true); });
connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this,
[this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, false); });
[this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, false, false); });
menu->addSeparator();
@@ -376,7 +376,7 @@ void DeckPreviewWidget::addSetBannerCardMenu(QMenu *menu)
void DeckPreviewWidget::actRenameDeck()
{
// read input
const QString oldName = deckLoader->getDeckList()->getName();
const QString oldName = deckLoader->getDeck().deckList.getName();
bool ok;
QString newName = QInputDialog::getText(this, "Rename deck", tr("New name:"), QLineEdit::Normal, oldName, &ok);
@@ -385,8 +385,8 @@ void DeckPreviewWidget::actRenameDeck()
}
// write change
deckLoader->getDeckList()->setName(newName);
deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath));
deckLoader->getDeck().deckList.setName(newName);
deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath));
// update VDS
refreshBannerCardText();
@@ -416,9 +416,7 @@ void DeckPreviewWidget::actRenameFile()
return;
}
DeckLoader::LoadInfo lastLoadInfo = deckLoader->getLastLoadInfo();
lastLoadInfo.fileName = newFilePath;
deckLoader->setLastLoadInfo(lastLoadInfo);
deckLoader->getDeck().lastLoadInfo.fileName = newFilePath;
// update VDS
setFilePath(newFilePath);

View File

@@ -51,7 +51,7 @@ public:
signals:
void deckLoadRequested(const QString &filePath);
void openDeckEditor(DeckLoader *deck);
void openDeckEditor(const LoadedDeck &deck);
public slots:
void setFilePath(const QString &filePath);

View File

@@ -211,7 +211,7 @@ QStringList VisualDeckStorageFolderDisplayWidget::gatherAllTagsFromFlowWidget()
// Iterate through all DeckPreviewWidgets
for (DeckPreviewWidget *display : flowWidget->findChildren<DeckPreviewWidget *>()) {
// Get tags from each DeckPreviewWidget
QStringList tags = display->deckLoader->getDeckList()->getTags();
QStringList tags = display->deckLoader->getDeck().deckList.getTags();
// Add tags to the list while avoiding duplicates
allTags.append(tags);

View File

@@ -61,10 +61,10 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg
unusedColorIdentitiesOpacitySpinBox->setMaximum(100);
unusedColorIdentitiesOpacitySpinBox->setValue(
SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity());
connect(unusedColorIdentitiesOpacitySpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
connect(unusedColorIdentitiesOpacitySpinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDeckStorageQuickSettingsWidget::unusedColorIdentitiesOpacityChanged);
connect(unusedColorIdentitiesOpacitySpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
&SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
connect(unusedColorIdentitiesOpacitySpinBox, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox);

View File

@@ -95,14 +95,17 @@ QList<DeckPreviewWidget *> VisualDeckStorageSortWidget::filterFiles(QList<DeckPr
switch (sortOrder) {
case ByName:
return widget1->deckLoader->getDeckList()->getName() < widget2->deckLoader->getDeckList()->getName();
return widget1->deckLoader->getDeck().deckList.getName() <
widget2->deckLoader->getDeck().deckList.getName();
case Alphabetical:
return QString::localeAwareCompare(info1.fileName(), info2.fileName()) <= 0;
case ByLastModified:
return info1.lastModified() > info2.lastModified();
case ByLastLoaded: {
QDateTime time1 = QDateTime::fromString(widget1->deckLoader->getDeckList()->getLastLoadedTimestamp());
QDateTime time2 = QDateTime::fromString(widget2->deckLoader->getDeckList()->getLastLoadedTimestamp());
QDateTime time1 =
QDateTime::fromString(widget1->deckLoader->getDeck().deckList.getLastLoadedTimestamp());
QDateTime time2 =
QDateTime::fromString(widget2->deckLoader->getDeck().deckList.getLastLoadedTimestamp());
return time1 > time2;
}
}

View File

@@ -57,7 +57,7 @@ void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList<Dec
}
for (DeckPreviewWidget *deckPreview : deckPreviews) {
QStringList deckTags = deckPreview->deckLoader->getDeckList()->getTags();
QStringList deckTags = deckPreview->deckLoader->getDeck().deckList.getTags();
bool hasAllSelected = std::all_of(selectedTags.begin(), selectedTags.end(),
[&deckTags](const QString &tag) { return deckTags.contains(tag); });
@@ -153,7 +153,7 @@ QSet<QString> VisualDeckStorageTagFilterWidget::gatherAllTags() const
for (DeckPreviewWidget *widget : deckWidgets) {
if (widget->checkVisibility()) {
for (const QString &tag : widget->deckLoader->getDeckList()->getTags()) {
for (const QString &tag : widget->deckLoader->getDeck().deckList.getTags()) {
allTags.insert(tag);
}
}

View File

@@ -53,7 +53,7 @@ public slots:
signals:
void bannerCardsRefreshed();
void deckLoadRequested(const QString &filePath);
void openDeckEditor(DeckLoader *deck);
void openDeckEditor(const LoadedDeck &deck);
private:
QVBoxLayout *layout;

View File

@@ -1,10 +1,16 @@
#ifndef MAIN_H
#define MAIN_H
#include "libcockatrice/interfaces/noop_card_set_priority_controller.h"
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/parser/cockatrice_xml_4.h>
#include <libcockatrice/interfaces/noop_card_preference_provider.h>
static const QList<AllowedCount> kConstructedCounts = {{4, "legal"}, {0, "banned"}};
static const QList<AllowedCount> kSingletonCounts = {{1, "legal"}, {0, "banned"}};
class CardDatabaseConverter : public CardDatabase
{
public:
@@ -22,8 +28,74 @@ public:
bool saveCardDatabase(const QString &fileName)
{
CockatriceXml4Parser parser(new NoopCardPreferenceProvider());
return parser.saveToFile(sets, cards, fileName);
CockatriceXml4Parser parser(new NoopCardPreferenceProvider(), new NoopCardSetPriorityController());
return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName);
}
FormatRulesNameMap createDefaultMagicFormats()
{
// Predefined common exceptions
CardCondition superTypeIsBasic;
superTypeIsBasic.field = "type";
superTypeIsBasic.matchType = "contains";
superTypeIsBasic.value = "Basic Land";
ExceptionRule basicLands;
basicLands.conditions.append(superTypeIsBasic);
CardCondition anyNumberAllowed;
anyNumberAllowed.field = "text";
anyNumberAllowed.matchType = "contains";
anyNumberAllowed.value = "A deck can have any number of";
ExceptionRule mayContainAnyNumber;
mayContainAnyNumber.conditions.append(anyNumberAllowed);
// Map to store default rules
FormatRulesNameMap defaultFormatRulesNameMap;
// ----------------- Helper lambda to create format -----------------
auto makeFormat = [&](const QString &name, int minDeck = 60, int maxDeck = -1, int maxSideboardSize = 15,
const QList<AllowedCount> &allowedCounts = kConstructedCounts) -> FormatRulesPtr {
FormatRulesPtr f(new FormatRules);
f->formatName = name;
f->allowedCounts = allowedCounts;
f->minDeckSize = minDeck;
f->maxDeckSize = maxDeck;
f->maxSideboardSize = maxSideboardSize;
f->exceptions.append(basicLands);
f->exceptions.append(mayContainAnyNumber);
defaultFormatRulesNameMap.insert(name.toLower(), f);
return f;
};
// ----------------- Standard formats -----------------
makeFormat("Standard");
makeFormat("Modern");
makeFormat("Legacy");
makeFormat("Pioneer");
makeFormat("Historic");
makeFormat("Timeless");
makeFormat("Future");
makeFormat("OldSchool");
makeFormat("Premodern");
makeFormat("Pauper");
makeFormat("Penny");
// ----------------- Singleton formats -----------------
makeFormat("Commander", 100, 100, 15, kSingletonCounts);
makeFormat("Duel", 100, 100, 15, kSingletonCounts);
makeFormat("Brawl", 60, 60, 15, kSingletonCounts);
makeFormat("StandardBrawl", 60, 60, 15, kSingletonCounts);
makeFormat("Oathbreaker", 60, 60, 15, kSingletonCounts);
makeFormat("PauperCommander", 100, 100, 15, kSingletonCounts);
makeFormat("Predh", 100, 100, 15, kSingletonCounts);
// ----------------- Restricted formats -----------------
makeFormat("Vintage", 60, -1, 15, {{4, "legal"}, {1, "restricted"}, {0, "banned"}});
return defaultFormatRulesNameMap;
}
};

View File

@@ -1,5 +1,7 @@
@page developer_reference Developer Reference
- @subpage logging
- @subpage primer_cards
- @subpage card_database_schema_and_parsing

View File

@@ -0,0 +1,184 @@
@page logging Logging
Cockatrice uses QtLogging from the QtCore module for its logging. See
the [official documentation](https://doc.qt.io/qt-6/qtlogging.html) for further details.
# Log Message Pattern
Any message logged through the QtLogging system automatically conforms to this message pattern:
Generic:
```
[<timestamp> <log_level>] [<class:function>] - <message> [<filename>:<line_no>]
```
Example:
```
[2025-12-05 14:48:25.908 I] [MainWindow::startupConfigCheck] - Startup: found config with current version [window_main.cpp:951]
```
For more information, see [Logging Setup](#logging-setup).
# Log Level and Categories
\note The default log level for the application is info.
This means that you should only use qInfo() in production-level code if you are truly sure that this message is
beneficial to end-users and other developers. As a general rule, if your functionality logs to info more than twice in
response to a user interaction, you are advised to consider moving some of these logs down to the debug level.
\warning You are strongly advised to avoid the use of the generic logging macros (e.g. qDebug(), qInfo(), qWarn()).
\note You should instead use the corresponding category logging macros (qCDebug(), qCInfo(), qCWarn()) and define
logging
categories for your log statements.
Example:
```c++
in .h
inline Q_LOGGING_CATEGORY(ExampleCategory, "cockatrice_example_category");
inline Q_LOGGING_CATEGORY(ExampleSubCategory, "cockatrice_example_category.sub_category");
in .cpp
qCInfo(ExampleCategory) << "Info level logs are usually sent through the main category"
qCDebug(ExampleSubCategory) << "Debug level logs are permitted their own category to allow selective silencing"
```
For more information on how to enable or disable logging categories,
see [Logging Configuration](#logging-configuration).
# Logging Configuration
For configuring our logging, we use the qtlogging.ini, located under cockatrice/resources/config/qtlogging.ini, which is
baked into the application in release version and set as the QT_LOGGING_CONF environment variable in main.cpp.
```c++
#ifdef Q_OS_APPLE
// <build>/cockatrice/cockatrice.app/Contents/MacOS/cockatrice
const QByteArray configPath = "../../../qtlogging.ini";
#elif defined(Q_OS_UNIX)
// <build>/cockatrice/cockatrice
const QByteArray configPath = "./qtlogging.ini";
#elif defined(Q_OS_WIN)
// <build>/cockatrice/Debug/cockatrice.exe
const QByteArray configPath = "../qtlogging.ini";
#else
const QByteArray configPath = "";
#endif
if (!qEnvironmentVariableIsSet(("QT_LOGGING_CONF"))) {
// Set the QT_LOGGING_CONF environment variable
qputenv("QT_LOGGING_CONF", configPath);
}
```
For more information on how to use this file and on how Qt evaluates which logging rules/file to use, please
see the [official Qt documentation](https://doc.qt.io/qt-6/qloggingcategory.html#configuring-categories).
Some examples:
```
# Turn off all logging except everything from card_picture_loader and all sub categories
[Rules]
# The default log level is info
*.debug = false
*.info = false
*.warning = false
*.critical = false
*.fatal = false
card_picture_loader.* = true
```
```
# Turn off all logging except info level logs from card_picture_loader and all sub categories
[Rules]
# The default log level is info
*.debug = false
*.info = false
*.warning = false
*.critical = false
*.fatal = false
card_picture_loader.*.info = true
```
```
[Rules]
# Turn on debug level logs for card_picture_loader but keep logging for sub categories suppressed
*.debug = false
card_picture_loader.debug = true
```
```
[Rules]
# Turn on all logs for worker subcategory of card_picture_loader
*.debug = false
card_picture_loader.worker = true
```
```
[Rules]
# Turn off some noisy and irrelevant startup logging for local development
*.debug = false
qt_translator = false
window_main.* = false
release_channel = false
spoiler_background_updater = false
theme_manager = false
sound_engine = false
tapped_out_interface = false
card_database = false
card_database.loading = false
card_database.loading.success_or_failure = true
cockatrice_xml.* = false
```
# Logging Setup
This is achieved through our logging setup in @ref main.cpp, where we set the message pattern and install a custom
logger which replaces the full file path at the end with just the file name (Qt shows the full and quite lengthy path by
default).
```c++
qSetMessagePattern(
"\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} "
"%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{"
"endif}%{if-fatal}\033[1;31mF%{endif}\033[0m] [%{function}] - %{message} [%{file}:%{line}]");
QApplication app(argc, argv);
QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit);
qInstallMessageHandler(CockatriceLogger);
```
```c++
static void CockatriceLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &message)
{
QString logMessage = qFormatLogMessage(type, ctx, message);
// Regular expression to match the full path in the square brackets and extract only the filename and line number
QRegularExpression regex(R"(\[(?:.:)?[\/\\].*[\/\\]([^\/\\]+\:\d+)\])");
QRegularExpressionMatch match = regex.match(logMessage);
if (match.hasMatch()) {
// Extract the filename and line number (e.g., "main.cpp:211")
QString filenameLine = match.captured(1);
// Replace the full path in square brackets with just the filename and line number
logMessage.replace(match.captured(0), QString("[%1]").arg(filenameLine));
}
Logger::getInstance().log(type, ctx, logMessage);
}
```

1
doc/doxygen/theme Submodule

Submodule doc/doxygen/theme added at 1f3620084f

View File

@@ -41,6 +41,8 @@ add_library(
libcockatrice/card/relation/card_relation.cpp
libcockatrice/card/set/card_set.cpp
libcockatrice/card/set/card_set_list.cpp
libcockatrice/card/format/format_legality_rules.cpp
libcockatrice/card/format/format_legality_rules.h
)
target_include_directories(

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