diff --git a/.ci/Fedora40/Dockerfile b/.ci/Fedora42/Dockerfile similarity index 95% rename from .ci/Fedora40/Dockerfile rename to .ci/Fedora42/Dockerfile index 598bf7562..cf56ff604 100644 --- a/.ci/Fedora40/Dockerfile +++ b/.ci/Fedora42/Dockerfile @@ -1,4 +1,4 @@ -FROM fedora:40 +FROM fedora:42 RUN dnf install -y \ ccache \ diff --git a/.ci/release_template.md b/.ci/release_template.md index 75717bb5f..671ba7021 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -22,8 +22,8 @@ Available pre-compiled binaries for installation: • Ubuntu 20.04 LTS Focal FossaDebian 12 BookwormDebian 11 Bullseye + • Fedora 42Fedora 41 - • Fedora 40 We are also packaged in Arch Linux's official extra repository, courtesy of @FFY00. General Linux support is available via a flatpak package at Flathub! diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index a1045334e..4d106c44a 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -9,6 +9,7 @@ on: - 'webclient/**' - '.github/workflows/web-*.yml' - '.github/workflows/translations-*.yml' + - '.github/workflows/docker-release.yml' tags: - '*' pull_request: @@ -101,12 +102,12 @@ jobs: package: DEB - distro: Fedora - version: 40 + version: 41 package: RPM test: skip # Running tests on all distros is superfluous - distro: Fedora - version: 41 + version: 42 package: RPM - distro: Ubuntu diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml index 4ebfd9b79..aee6e81d5 100644 --- a/.github/workflows/desktop-lint.yml +++ b/.github/workflows/desktop-lint.yml @@ -7,6 +7,7 @@ on: - 'webclient/**' - '.github/workflows/web-*.yml' - '.github/workflows/translations-*.yml' + - '.github/workflows/docker-release.yml' jobs: format: diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml new file mode 100644 index 000000000..5a03b86c8 --- /dev/null +++ b/.github/workflows/docker-release.yml @@ -0,0 +1,71 @@ +name: Build Docker Image + +on: + push: + tags: + - '*Release*' + branches: + - master + pull_request: + branches: + - master + paths: + - '.github/workflows/docker-release.yml' + - 'CMakeLists.txt' + - 'Dockerfile' + - 'servatrice/**' + - 'common/**' + - 'cmake/**' + - '!**.md' + +jobs: + docker: + name: amd64 & arm64 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Docker metadata + id: metadata + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/cockatrice/servatrice + labels: | + org.opencontainers.image.title=Servatrice + org.opencontainers.image.url=https://cockatrice.github.io/ + org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games + annotations: | + org.opencontainers.image.title=Servatrice + org.opencontainers.image.url=https://cockatrice.github.io/ + org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + if: github.ref_type == 'tag' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.ref_type == 'tag' }} + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index 330c51b4a..2a0d10eba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,11 @@ RUN apt-get update && apt-get install -y\ qt6-tools-dev \ qt6-tools-dev-tools -COPY . /home/servatrice/code/ +COPY ./CMakeLists.txt ./LICENSE ./README.md /home/servatrice/code/ +COPY ./cmake /home/servatrice/code/cmake +COPY ./common /home/servatrice/code/common +COPY ./servatrice /home/servatrice/code/servatrice + WORKDIR /home/servatrice/code WORKDIR build diff --git a/README.md b/README.md index 0a8058bba..acc2e4110 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Related | Community | Contribute | - Build | + Build | Run

@@ -38,8 +38,8 @@ Latest stable release:
 Latest beta version:
 [![Download Beta Release](https://img.shields.io/github/release/cockatrice/cockatrice/all.svg?label=version&colorB=f37f40 "Download Latest Beta Release")](https://github.com/cockatrice/cockatrice/releases) ![](https://img.shields.io/github/release-date-pre/Cockatrice/Cockatrice.svg?label=released&colorB=f37f40 "Release Date") [![](https://img.shields.io/github/downloads-pre/cockatrice/cockatrice/latest/total.svg?label=downloads&colorB=f37f40 "Number of Downloads")](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0) [![](https://img.shields.io/github/commits-since/Cockatrice/Cockatrice/latest.svg?label=changes&colorB=f37f40 "Changes over Stable Release")](https://github.com/Cockatrice/Cockatrice/pulls?q=is%3Apr+is%3Aclosed)
-While incorporating the latest fixes and features, beta builds may not be stable and/or contain new bugs!
-Please report any findings when testing them!
+While incorporating the latest fixes and features, beta builds may not be stable or contain new bugs!
+Please report any findings and open new issues when testing them!
 
# Related Repositories @@ -79,10 +79,11 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/
Kudos to our amazing contributors ❤️ +

- Made with contrib.rocks. + Made with contrib.rocks
### Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://transifex.com/cockatrice/cockatrice/) @@ -92,7 +93,7 @@ Cockatrice uses Transifex to manage translations. You can help us bring Coc Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting invovled, and join a group of hundreds of others!
-# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Web](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush) +# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [![CI Web](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush) Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))* - [Qt](https://www.qt.io/developers/) @@ -143,17 +144,16 @@ The following flags (with their non-default values) can be passed to `cmake`: # Run - Cockatrice is the game client
Oracle fetches card data
Servatrice is the server
#### Docker -You can run an instance of Servatrice (the Cockatrice server) using [Docker](https://www.docker.com/resources/what-container/) and our Dockerfile.
+You can build an image & deploy a Servatrice (Cockatrice server) container using [Docker](https://www.docker.com/resources/what-container/) and our Dockerfile yourself.
-For more information, have a look in our wiki section on [Setting up Servatrice](https://github.com/Cockatrice/Cockatrice/wiki/Setting-up-Servatrice#using-docker).
-There, you'll also find more hints on our **docker-compose** file which will configure and run both a MySQL server and Servatrice. +For more details, look into our wiki section on [Setting up Servatrice](https://github.com/Cockatrice/Cockatrice/wiki/Setting-up-Servatrice#using-docker).
+You'll also find more hints on our **pre-build image** there, or the **docker-compose** file which will configure and run both a MySQL server and Servatrice. # License [![GPLv2 License](https://img.shields.io/github/license/Cockatrice/Cockatrice.svg)](https://github.com/Cockatrice/Cockatrice/blob/master/LICENSE) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index cbd0ed1d7..50c721a6f 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -24,7 +24,9 @@ set(cockatrice_SOURCES src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp src/client/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.cpp src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp + src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.cpp src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.cpp + src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.cpp src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp src/client/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp src/client/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp @@ -99,6 +101,7 @@ set(cockatrice_SOURCES src/client/ui/widgets/general/display/dynamic_font_size_label.cpp src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp src/client/ui/widgets/general/display/labeled_input.cpp + src/client/ui/widgets/general/display/percent_bar_widget.cpp src/client/ui/widgets/general/display/shadow_background_label.cpp src/client/ui/widgets/general/layout_containers/flow_widget.cpp src/client/ui/widgets/general/layout_containers/overlap_control_widget.cpp @@ -132,6 +135,7 @@ set(cockatrice_SOURCES src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp + src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp @@ -165,13 +169,16 @@ set(cockatrice_SOURCES src/dialogs/dlg_tip_of_the_day.cpp src/dialogs/dlg_update.cpp src/dialogs/dlg_view_log.cpp + src/game/board/abstract_card_drag_item.cpp + src/game/board/abstract_card_item.cpp src/game/board/abstract_counter.cpp src/game/board/abstract_graphics_item.cpp src/game/board/arrow_item.cpp src/game/board/arrow_target.cpp + src/game/board/card_drag_item.cpp + src/game/board/card_item.cpp + src/game/board/card_list.cpp src/game/board/counter_general.cpp - src/game/cards/abstract_card_drag_item.cpp - src/game/cards/abstract_card_item.cpp src/game/cards/card_completer_proxy_model.cpp src/game/cards/card_database.cpp src/game/cards/card_database_manager.cpp @@ -179,10 +186,7 @@ set(cockatrice_SOURCES src/game/cards/card_database_parser/card_database_parser.cpp src/game/cards/card_database_parser/cockatrice_xml_3.cpp src/game/cards/card_database_parser/cockatrice_xml_4.cpp - src/game/cards/card_drag_item.cpp src/game/cards/card_info.cpp - src/game/cards/card_item.cpp - src/game/cards/card_list.cpp src/game/cards/card_search_model.cpp src/game/deckview/deck_view.cpp src/game/deckview/deck_view_container.cpp diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp index 1c599034b..eba0bfb0f 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp @@ -50,6 +50,7 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta printingSelectorDockWidget = new DeckEditorPrintingSelectorDockWidget(this); connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged); + connect(deckDockWidget, &DeckEditorDeckDockWidget::deckModified, this, &AbstractTabDeckEditor::onDeckModified); connect(deckDockWidget, &DeckEditorDeckDockWidget::cardChanged, this, &AbstractTabDeckEditor::updateCard); connect(this, &AbstractTabDeckEditor::decrementCard, deckDockWidget, &DeckEditorDeckDockWidget::actDecrementCard); connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this, @@ -77,6 +78,10 @@ void AbstractTabDeckEditor::updateCard(CardInfoPtr _card) } void AbstractTabDeckEditor::onDeckChanged() +{ +} + +void AbstractTabDeckEditor::onDeckModified() { setModified(!isBlankNewDeck()); deckMenu->setSaveStatus(!isBlankNewDeck()); diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h index 796213d27..1642ac107 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h @@ -70,6 +70,7 @@ public: public slots: virtual void onDeckChanged(); + virtual void onDeckModified(); void updateCard(CardInfoPtr _card); void actAddCard(CardInfoPtr info); void actAddCardToSideboard(CardInfoPtr info); diff --git a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index f6e0cf4fa..674f011cd 100644 --- a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -14,34 +14,18 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi cardPictureWidget = new CardInfoPictureWidget(this); cardPictureWidget->setCard(CardDatabaseManager::getInstance()->guessCard(toDisplay.sanitized)); - label = new QLabel(this); - label->setText(toDisplay.name + "\n" + toDisplay.label); - label->setAlignment(Qt::AlignHCenter); + nameLabel = new QLabel(this); + nameLabel->setText(toDisplay.name); + nameLabel->setAlignment(Qt::AlignHCenter); - int inclusionRate = 0; - // Set label color based on inclusion rate - if (toDisplay.potentialDecks != 0) { - inclusionRate = (toDisplay.numDecks * 100) / toDisplay.potentialDecks; - } + inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay); - QColor labelColor; - if (inclusionRate <= 30) { - labelColor = QColor(255, 0, 0); // Red - } else if (inclusionRate <= 60) { - int red = 255 - ((inclusionRate - 30) * 2); - int green = (inclusionRate - 30) * 4; // Adjust green to make the transition smoother - labelColor = QColor(red, green, 0); // purple-ish - } else if (inclusionRate <= 90) { - int green = (inclusionRate - 60) * 5; // Increase green - labelColor = QColor(100, green, 100); // Green shades - } else { - labelColor = QColor(100, 200, 100); // Dark Green - } - - label->setStyleSheet(QString("color: %1").arg(labelColor.name())); + synergyDisplayWidget = new EdhrecApiResponseCardSynergyDisplayWidget(this, toDisplay); + layout->addWidget(nameLabel); layout->addWidget(cardPictureWidget); - layout->addWidget(label); + layout->addWidget(inclusionDisplayWidget); + layout->addWidget(synergyDisplayWidget); QWidget *currentParent = parentWidget(); TabEdhRecMain *parentTab = nullptr; @@ -54,8 +38,11 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi } if (parentTab) { + cardPictureWidget->setScaleFactor(parentTab->getCardSizeSlider()->getSlider()->value()); connect(cardPictureWidget, &CardInfoPictureWidget::cardClicked, this, &EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation); + connect(parentTab->getCardSizeSlider()->getSlider(), &QSlider::valueChanged, cardPictureWidget, + &CardInfoPictureWidget::setScaleFactor); connect(this, &EdhrecApiResponseCardDetailsDisplayWidget::requestUrl, parentTab, &TabEdhRecMain::actNavigatePage); } diff --git a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h index 7492659e9..820434612 100644 --- a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h @@ -3,6 +3,8 @@ #include "../../../../../ui/widgets/cards/card_info_picture_widget.h" #include "../../api_response/cards/edhrec_api_response_card_details.h" +#include "edhrec_api_response_card_inclusion_display_widget.h" +#include "edhrec_api_response_card_synergy_display_widget.h" #include #include @@ -22,7 +24,9 @@ private: EdhrecApiResponseCardDetails toDisplay; QVBoxLayout *layout; CardInfoPictureWidget *cardPictureWidget; - QLabel *label; + QLabel *nameLabel; + EdhrecApiResponseCardInclusionDisplayWidget *inclusionDisplayWidget; + EdhrecApiResponseCardSynergyDisplayWidget *synergyDisplayWidget; }; #endif // EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.cpp new file mode 100644 index 000000000..8292b00ea --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.cpp @@ -0,0 +1,41 @@ +#include "edhrec_api_response_card_inclusion_display_widget.h" + +EdhrecApiResponseCardInclusionDisplayWidget::EdhrecApiResponseCardInclusionDisplayWidget( + QWidget *parent, + const EdhrecApiResponseCardDetails &_toDisplay) + : QWidget(parent), toDisplay(_toDisplay) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + commanderLabel = new QLabel(this); + commanderLabel->setAlignment(Qt::AlignCenter); + amountLabel = new QLabel(this); + amountLabel->setAlignment(Qt::AlignCenter); + inclusionLabel = new QLabel(this); + inclusionLabel->setAlignment(Qt::AlignCenter); + percentBarWidget = new PercentBarWidget(this, toDisplay.inclusion / (toDisplay.potentialDecks / 100.0)); + + if (toDisplay.inclusion != 0 && toDisplay.potentialDecks != 0) { + layout->addWidget(amountLabel); + layout->addWidget(inclusionLabel); + layout->addWidget(percentBarWidget); + commanderLabel->hide(); + } else { + amountLabel->hide(); + inclusionLabel->hide(); + percentBarWidget->hide(); + layout->addWidget(commanderLabel); + } + + retranslateUi(); +} + +void EdhrecApiResponseCardInclusionDisplayWidget::retranslateUi() +{ + commanderLabel->setText(toDisplay.label); + amountLabel->setText(tr("In %1 decks").arg(QString::number(toDisplay.inclusion))); + inclusionLabel->setText(tr("%1% of %2 decks") + .arg(QString::number(toDisplay.inclusion / (toDisplay.potentialDecks / 100.0), 'f', 2), + QString::number(toDisplay.potentialDecks))); +} diff --git a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h new file mode 100644 index 000000000..f0fbfdfc8 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h @@ -0,0 +1,27 @@ +#ifndef EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H +#define EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H + +#include "../../../../../ui/widgets/general/display/percent_bar_widget.h" +#include "../../api_response/cards/edhrec_api_response_card_details.h" + +#include +#include +#include + +class EdhrecApiResponseCardInclusionDisplayWidget : public QWidget +{ + Q_OBJECT +public: + EdhrecApiResponseCardInclusionDisplayWidget(QWidget *parent, const EdhrecApiResponseCardDetails &_toDisplay); + void retranslateUi(); + +private: + QVBoxLayout *layout; + EdhrecApiResponseCardDetails toDisplay; + QLabel *commanderLabel; + QLabel *amountLabel; + QLabel *inclusionLabel; + PercentBarWidget *percentBarWidget; +}; + +#endif // EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.cpp new file mode 100644 index 000000000..9d5571a8f --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.cpp @@ -0,0 +1,28 @@ +#include "edhrec_api_response_card_synergy_display_widget.h" + +EdhrecApiResponseCardSynergyDisplayWidget::EdhrecApiResponseCardSynergyDisplayWidget( + QWidget *parent, + const EdhrecApiResponseCardDetails &_toDisplay) + : QWidget(parent), toDisplay(_toDisplay) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + label = new QLabel(this); + label->setAlignment(Qt::AlignCenter); + percentBarWidget = new PercentBarWidget(this, toDisplay.synergy * 100.0); + + if (toDisplay.synergy != 0) { + layout->addWidget(label); + layout->addWidget(percentBarWidget); + } else { + hide(); + } + + retranslateUi(); +} + +void EdhrecApiResponseCardSynergyDisplayWidget::retranslateUi() +{ + label->setText(tr("%1% Synergy").arg(QString::number(toDisplay.synergy * 100.0, 'f', 1))); +} diff --git a/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h new file mode 100644 index 000000000..ebb252880 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h @@ -0,0 +1,25 @@ +#ifndef EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H +#define EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H + +#include "../../../../../ui/widgets/general/display/percent_bar_widget.h" +#include "../../api_response/cards/edhrec_api_response_card_details.h" + +#include +#include +#include + +class EdhrecApiResponseCardSynergyDisplayWidget : public QWidget +{ + Q_OBJECT +public: + EdhrecApiResponseCardSynergyDisplayWidget(QWidget *parent, const EdhrecApiResponseCardDetails &_toDisplay); + void retranslateUi(); + +private: + QVBoxLayout *layout; + EdhrecApiResponseCardDetails toDisplay; + QLabel *label; + PercentBarWidget *percentBarWidget; +}; + +#endif // EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index e04325a35..2001cf6d1 100644 --- a/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp @@ -2,6 +2,7 @@ #include "../../../../../../game/cards/card_database_manager.h" #include "../../../../../ui/widgets/cards/card_info_picture_widget.h" +#include "../../tab_edhrec_main.h" #include "../card_prices/edhrec_api_response_card_prices_display_widget.h" EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCommanderDetailsDisplayWidget( @@ -16,6 +17,22 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom commanderPicture = new CardInfoPictureWidget(this); commanderPicture->setCard(CardDatabaseManager::getInstance()->getCard(commanderDetails.getName())); + QWidget *currentParent = parentWidget(); + TabEdhRecMain *parentTab = nullptr; + + while (currentParent) { + if ((parentTab = qobject_cast(currentParent))) { + break; + } + currentParent = currentParent->parentWidget(); + } + + if (parentTab) { + connect(parentTab->getCardSizeSlider()->getSlider(), &QSlider::valueChanged, commanderPicture, + &CardInfoPictureWidget::setScaleFactor); + commanderPicture->setScaleFactor(parentTab->getCardSizeSlider()->getSlider()->value()); + } + commanderDetails.debugPrint(); label = new QLabel(this); diff --git a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp index ac30cef4d..2eed749af 100644 --- a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp @@ -94,11 +94,18 @@ TabEdhRecMain::TabEdhRecMain(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor searchPushButton = new QPushButton(navigationContainer); connect(searchPushButton, &QPushButton::clicked, this, [=, this]() { doSearch(); }); + settingsButton = new SettingsButtonWidget(this); + + cardSizeSlider = new CardSizeWidget(this); + + settingsButton->addSettingsWidget(cardSizeSlider); + navigationLayout->addWidget(cardsPushButton); navigationLayout->addWidget(topCommandersPushButton); navigationLayout->addWidget(tagsPushButton); navigationLayout->addWidget(searchBar); navigationLayout->addWidget(searchPushButton); + navigationLayout->addWidget(settingsButton); currentPageDisplay = new QWidget(container); currentPageLayout = new QVBoxLayout(currentPageDisplay); @@ -349,7 +356,7 @@ void TabEdhRecMain::processAverageDeckResponse(QJsonObject reply) { EdhrecAverageDeckApiResponse deckData; deckData.fromJson(reply); - tabSupervisor->addVisualDeckEditorTab(deckData.deck.deckLoader); + tabSupervisor->openDeckInNewTab(deckData.deck.deckLoader); } void TabEdhRecMain::prettyPrintJson(const QJsonValue &value, int indentLevel) diff --git a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.h b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.h index 1c6c67e5c..ba64cdc0a 100644 --- a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.h +++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.h @@ -2,7 +2,9 @@ #define TAB_EDHREC_MAIN_H #include "../../../../game/cards/card_database.h" +#include "../../../ui/widgets/cards/card_size_widget.h" #include "../../../ui/widgets/general/layout_containers/flow_widget.h" +#include "../../../ui/widgets/quick_settings/settings_button_widget.h" #include "../../tab.h" #include "display/commander/edhrec_commander_api_response_display_widget.h" @@ -25,6 +27,11 @@ public: return tr("EDHREC: ") + cardName; } + CardSizeWidget *getCardSizeSlider() + { + return cardSizeSlider; + } + QNetworkAccessManager *networkManager; public slots: @@ -53,6 +60,8 @@ private: QPushButton *tagsPushButton; QLineEdit *searchBar; QPushButton *searchPushButton; + SettingsButtonWidget *settingsButton; + CardSizeWidget *cardSizeSlider; CardInfoPtr cardToQuery; EdhrecCommanderApiResponseDisplayWidget *displayWidget; }; diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index e01153d00..999b5b164 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -3,9 +3,9 @@ #include "../../client/ui/widgets/cards/card_info_frame_widget.h" #include "../../dialogs/dlg_create_game.h" #include "../../game/board/arrow_item.h" +#include "../../game/board/card_item.h" #include "../../game/cards/card_database.h" #include "../../game/cards/card_database_manager.h" -#include "../../game/cards/card_item.h" #include "../../game/deckview/deck_view_container.h" #include "../../game/game_scene.h" #include "../../game/game_view.h" diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp index 5d9600d11..4ed0e5a6b 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.cpp +++ b/cockatrice/src/client/tabs/tab_supervisor.cpp @@ -590,7 +590,7 @@ void TabSupervisor::actTabDeckStorage(bool checked) void TabSupervisor::openTabDeckStorage() { tabDeckStorage = new TabDeckStorage(this, client, userInfo); - connect(tabDeckStorage, &TabDeckStorage::openDeckEditor, this, &TabSupervisor::addDeckEditorTab); + connect(tabDeckStorage, &TabDeckStorage::openDeckEditor, this, &TabSupervisor::openDeckInNewTab); myAddTab(tabDeckStorage, aTabDeckStorage); connect(tabDeckStorage, &Tab::closed, this, [this] { tabDeckStorage = nullptr; @@ -691,7 +691,7 @@ void TabSupervisor::gameJoined(const Event_GameJoined &event) auto *tab = new TabGame(this, QList() << client, event, roomGameTypes); connect(tab, &TabGame::gameClosing, this, &TabSupervisor::gameLeft); connect(tab, &TabGame::openMessageDialog, this, &TabSupervisor::addMessageTab); - connect(tab, &TabGame::openDeckEditor, this, &TabSupervisor::addDeckEditorTab); + connect(tab, &TabGame::openDeckEditor, this, &TabSupervisor::openDeckInNewTab); myAddTab(tab); gameTabs.insert(event.game_info().game_id(), tab); setCurrentWidget(tab); @@ -701,7 +701,7 @@ void TabSupervisor::localGameJoined(const Event_GameJoined &event) { auto *tab = new TabGame(this, localClients, event, QMap()); connect(tab, &TabGame::gameClosing, this, &TabSupervisor::gameLeft); - connect(tab, &TabGame::openDeckEditor, this, &TabSupervisor::addDeckEditorTab); + connect(tab, &TabGame::openDeckEditor, this, &TabSupervisor::openDeckInNewTab); myAddTab(tab); gameTabs.insert(event.game_info().game_id(), tab); setCurrentWidget(tab); @@ -807,6 +807,29 @@ void TabSupervisor::talkLeft(TabMessage *tab) removeTab(indexOf(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. + */ +void TabSupervisor::openDeckInNewTab(const DeckLoader *deckToOpen) +{ + int type = SettingsCache::instance().getDefaultDeckEditorType(); + switch (type) { + case ClassicDeckEditor: + addDeckEditorTab(deckToOpen); + break; + case VisualDeckEditor: + addVisualDeckEditorTab(deckToOpen); + break; + default: + qCWarning(TabSupervisorLog) << "Unknown DeckEditorType [" << type + << "]; opening ClassicDeckEditor as fallback"; + addDeckEditorTab(deckToOpen); + break; + } +} + /** * Creates a new deck editor tab * @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance. diff --git a/cockatrice/src/client/tabs/tab_supervisor.h b/cockatrice/src/client/tabs/tab_supervisor.h index 0ca13ac41..1c367043e 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.h +++ b/cockatrice/src/client/tabs/tab_supervisor.h @@ -75,6 +75,14 @@ protected: class TabSupervisor : public QTabWidget { Q_OBJECT + +public: + enum DeckEditorType + { + ClassicDeckEditor, + VisualDeckEditor + }; + private: ServerInfo_User *userInfo; AbstractClient *client; @@ -152,6 +160,7 @@ signals: void showWindowIfHidden(); public slots: + void openDeckInNewTab(const DeckLoader *deckToOpen); TabDeckEditor *addDeckEditorTab(const DeckLoader *deckToOpen); TabDeckEditorVisual *addVisualDeckEditorTab(const DeckLoader *deckToOpen); TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab(); diff --git a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 224c4db15..68f1eee17 100644 --- a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -80,7 +80,7 @@ void TabDeckEditorVisual::createCentralFrame() void TabDeckEditorVisual::onDeckChanged() { - AbstractTabDeckEditor::onDeckChanged(); + AbstractTabDeckEditor::onDeckModified(); tabContainer->visualDeckView->decklistDataChanged(QModelIndex(), QModelIndex()); tabContainer->deckAnalytics->refreshDisplays(deckDockWidget->deckModel); tabContainer->sampleHandWidget->setDeckModel(deckDockWidget->deckModel); diff --git a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp index 38af03c59..65b4fcab0 100644 --- a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp +++ b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -12,7 +12,7 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor), visualDeckStorageWidget(new VisualDeckStorageWidget(this)) { - connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::addVisualDeckEditorTab); + connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::openDeckInNewTab); connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckLoadRequested, this, &TabDeckStorageVisual::actOpenLocalDeck); connect(visualDeckStorageWidget, &VisualDeckStorageWidget::openDeckEditor, this, diff --git a/cockatrice/src/client/tapped_out_interface.cpp b/cockatrice/src/client/tapped_out_interface.cpp index d6a47b018..4fd103c8c 100644 --- a/cockatrice/src/client/tapped_out_interface.cpp +++ b/cockatrice/src/client/tapped_out_interface.cpp @@ -92,16 +92,9 @@ void TappedOutInterface::analyzeDeck(DeckList *deck) manager->post(request, data); } -struct CopyMainOrSide +void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard) { - CardDatabase &cardDatabase; - DeckList &mainboard, &sideboard; - - CopyMainOrSide(CardDatabase &_cardDatabase, DeckList &_mainboard, DeckList &_sideboard) - : cardDatabase(_cardDatabase), mainboard(_mainboard), sideboard(_sideboard){}; - - void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) const - { + auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) { CardInfoPtr dbCard = cardDatabase.getCard(card->getName()); if (!dbCard || dbCard->getIsToken()) return; @@ -112,11 +105,7 @@ struct CopyMainOrSide else addedCard = mainboard.addCard(card->getName(), node->getName()); addedCard->setNumber(card->getNumber()); - } -}; + }; -void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard) -{ - CopyMainOrSide copyMainOrSide(cardDatabase, mainboard, sideboard); source.forEachCard(copyMainOrSide); } diff --git a/cockatrice/src/client/ui/picture_loader/picture_to_load.cpp b/cockatrice/src/client/ui/picture_loader/picture_to_load.cpp index 9800d7cc5..81210a983 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_to_load.cpp +++ b/cockatrice/src/client/ui/picture_loader/picture_to_load.cpp @@ -1,6 +1,7 @@ #include "picture_to_load.h" #include "../../../settings/cache_settings.h" +#include "../../../utility/card_set_comparator.h" #include #include @@ -20,7 +21,7 @@ PictureToLoad::PictureToLoad(CardInfoPtr _card) if (sortedSets.empty()) { sortedSets << CardSet::newInstance("", "", "", QDate()); } - std::sort(sortedSets.begin(), sortedSets.end(), SetDownloadPriorityComparator()); + std::sort(sortedSets.begin(), sortedSets.end(), SetPriorityComparator()); // If the user hasn't disabled arts other than their personal preference... if (!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { diff --git a/cockatrice/src/client/ui/picture_loader/picture_to_load.h b/cockatrice/src/client/ui/picture_loader/picture_to_load.h index 9d1a784f0..38ebe5763 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_to_load.h +++ b/cockatrice/src/client/ui/picture_loader/picture_to_load.h @@ -10,24 +10,6 @@ inline Q_LOGGING_CATEGORY(PictureToLoadLog, "picture_loader.picture_to_load"); class PictureToLoad { private: - class SetDownloadPriorityComparator - { - public: - /* - * Returns true if a has higher download priority than b - * Enabled sets have priority over disabled sets - * Both groups follows the user-defined order - */ - inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const - { - if (a->getEnabled()) { - return !b->getEnabled() || a->getSortKey() < b->getSortKey(); - } else { - return !b->getEnabled() && a->getSortKey() < b->getSortKey(); - } - } - }; - CardInfoPtr card; QList sortedSets; QList urlTemplates; diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp index dd4d1c1f6..b17b0ff47 100644 --- a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp @@ -20,12 +20,17 @@ ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isAct &ManaSymbolWidget::updateOpacity); } +void ManaSymbolWidget::toggleSymbol() +{ + setColorActive(!isActive); + emit colorToggled(getSymbolChar(), isActive); +} + void ManaSymbolWidget::setColorActive(bool active) { if (isActive != active) { isActive = active; updateOpacity(); - emit colorToggled(getSymbolChar(), isActive); } } @@ -46,9 +51,7 @@ void ManaSymbolWidget::mousePressEvent(QMouseEvent *event) { Q_UNUSED(event); if (mayBeToggled) { - isActive = !isActive; - updateOpacity(); - emit colorToggled(getSymbolChar(), isActive); + toggleSymbol(); } } @@ -63,7 +66,7 @@ void ManaSymbolWidget::loadManaIcon() QString filename = "theme:icons/mana/"; if (symbol == "W" || symbol == "U" || symbol == "B" || symbol == "R" || symbol == "G") { - filename += symbol + ".svg"; + filename += symbol; } manaIcon = QPixmap(filename); diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.h b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.h index 6ecf54b34..02aaf6087 100644 --- a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.h @@ -11,9 +11,13 @@ class ManaSymbolWidget : public QLabel public: ManaSymbolWidget(QWidget *parent, QString symbol, bool isActive = true, bool mayBeToggled = false); + void toggleSymbol(); void setColorActive(bool active); void updateOpacity(); - bool isColorActive() const; + bool isColorActive() const + { + return isActive; + }; QString getSymbol() const { return symbol; diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index 71569e92a..7e2caaccc 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -26,7 +26,7 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, banner = new BannerWidget(this, cardGroupCategory, Qt::Orientation::Vertical, bannerOpacity); layout->addWidget(banner); - updateCardDisplays(); + CardGroupDisplayWidget::updateCardDisplays(); } void CardGroupDisplayWidget::updateCardDisplays() diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp index d967c8477..ab6a9eb31 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp @@ -1,7 +1,7 @@ #include "card_info_display_widget.h" +#include "../../../../game/board/card_item.h" #include "../../../../game/cards/card_database_manager.h" -#include "../../../../game/cards/card_item.h" #include "../../../../main.h" #include "card_info_picture_widget.h" #include "card_info_text_widget.h" diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp index 4d85f38a0..08eafbf87 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp @@ -1,7 +1,7 @@ #include "card_info_frame_widget.h" +#include "../../../../game/board/card_item.h" #include "../../../../game/cards/card_database_manager.h" -#include "../../../../game/cards/card_item.h" #include "../../../../settings/cache_settings.h" #include "card_info_display_widget.h" #include "card_info_picture_widget.h" diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp index 4d76989b1..5e275f8e9 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp @@ -1,7 +1,7 @@ #include "card_info_picture_widget.h" +#include "../../../../game/board/card_item.h" #include "../../../../game/cards/card_database_manager.h" -#include "../../../../game/cards/card_item.h" #include "../../../../settings/cache_settings.h" #include "../../../tabs/tab_supervisor.h" #include "../../picture_loader/picture_loader.h" @@ -64,6 +64,12 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT }); } +CardInfoPictureWidget::~CardInfoPictureWidget() +{ + enlargedPixmapWidget->hide(); + enlargedPixmapWidget->deleteLater(); +} + /** * @brief Sets the card to be displayed and updates the pixmap. * @param card A shared pointer to the card information (CardInfoPtr). @@ -341,6 +347,12 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event) emit cardClicked(); } +void CardInfoPictureWidget::hideEvent(QHideEvent *event) +{ + enlargedPixmapWidget->hide(); + QWidget::hideEvent(event); +} + QMenu *CardInfoPictureWidget::createRightClickMenu() { auto *cardMenu = new QMenu(this); diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h index 10cc422aa..79de74526 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h @@ -21,6 +21,7 @@ public: explicit CardInfoPictureWidget(QWidget *parent = nullptr, bool hoverToZoomEnabled = false, bool raiseOnEnter = false); + ~CardInfoPictureWidget(); CardInfoPtr getInfo() { return info; @@ -52,6 +53,7 @@ protected: void moveEvent(QMoveEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; + void hideEvent(QHideEvent *event) override; void loadPixmap(); [[nodiscard]] const QPixmap &getResizedPixmap() const { diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_text_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_text_widget.cpp index 82944205f..5201bea2c 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_text_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_text_widget.cpp @@ -1,6 +1,6 @@ #include "card_info_text_widget.h" -#include "../../../../game/cards/card_item.h" +#include "../../../../game/board/card_item.h" #include "../../../../game/game_specific_terms.h" #include diff --git a/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp index be231e67a..7ce8e71c4 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp @@ -34,14 +34,7 @@ CardSizeWidget::CardSizeWidget(QWidget *parent, FlowWidget *_flowWidget, int def // Debounce setup debounceTimer.setSingleShot(true); - connect(&debounceTimer, &QTimer::timeout, this, [this]() { - // Check the type of the parent widget - if (qobject_cast(parentWidget())) { - SettingsCache::instance().setPrintingSelectorCardSize(pendingValue); - } else if (qobject_cast(parentWidget())) { - SettingsCache::instance().setVisualDeckStorageCardSize(pendingValue); - } - }); + connect(&debounceTimer, &QTimer::timeout, this, [this] { emit cardSizeSettingUpdated(pendingValue); }); connect(cardSizeSlider, &QSlider::valueChanged, this, &CardSizeWidget::updateCardSizeSetting); } diff --git a/cockatrice/src/client/ui/widgets/cards/card_size_widget.h b/cockatrice/src/client/ui/widgets/cards/card_size_widget.h index d79359f23..1631fa562 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_size_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_size_widget.h @@ -17,9 +17,17 @@ public: explicit CardSizeWidget(QWidget *parent, FlowWidget *flowWidget = nullptr, int defaultValue = 100); [[nodiscard]] QSlider *getSlider() const; -public slots: +private slots: void updateCardSizeSetting(int newValue); +signals: + /** + * Emitted when the slider value changes, but on a debounce timer. + * Any parents that care about saving the value to settings should use this signal to indicate when to save the new + * value to settings. + */ + void cardSizeSettingUpdated(int newValue); + private: QWidget *parent; FlowWidget *flowWidget; diff --git a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp index b074241fe..c5da91a2a 100644 --- a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp @@ -93,6 +93,8 @@ void DeckCardZoneDisplayWidget::addCardGroupIfItDoesNotExist() for (CardGroupDisplayWidget *cardGroupDisplayWidget : cardGroupsDisplayWidgets) { if (cardGroupDisplayWidget->cardGroupCategory == cardGroup) { found = true; + cardGroupDisplayWidget->updateCardDisplays(); + break; } } diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp index 02a4bfb5a..2131700fd 100644 --- a/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp @@ -6,14 +6,25 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckLi mainLayout = new QVBoxLayout(); setLayout(mainLayout); + scrollArea = new QScrollArea(this); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setWidgetResizable(true); + mainLayout->addWidget(scrollArea); + + container = new QWidget(scrollArea); + containerLayout = new QVBoxLayout(container); + container->setLayout(containerLayout); + scrollArea->setWidget(container); + manaCurveWidget = new ManaCurveWidget(this, deckListModel); - mainLayout->addWidget(manaCurveWidget); + containerLayout->addWidget(manaCurveWidget); manaDevotionWidget = new ManaDevotionWidget(this, deckListModel); - mainLayout->addWidget(manaDevotionWidget); + containerLayout->addWidget(manaDevotionWidget); manaBaseWidget = new ManaBaseWidget(this, deckListModel); - mainLayout->addWidget(manaBaseWidget); + containerLayout->addWidget(manaBaseWidget); } void DeckAnalyticsWidget::refreshDisplays(DeckListModel *_deckModel) diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h index 6e7a273cf..b379b435c 100644 --- a/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h +++ b/cockatrice/src/client/ui/widgets/deck_analytics/deck_analytics_widget.h @@ -8,9 +8,10 @@ #include "mana_devotion_widget.h" #include +#include +#include #include #include -#include class DeckAnalyticsWidget : public QWidget { @@ -26,6 +27,11 @@ private: DeckListModel *deckListModel; QVBoxLayout *mainLayout; + QWidget *container; + QVBoxLayout *containerLayout; + + QScrollArea *scrollArea; + ManaCurveWidget *manaCurveWidget; ManaDevotionWidget *manaDevotionWidget; ManaBaseWidget *manaBaseWidget; diff --git a/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp index acf240e0e..1cef0d794 100644 --- a/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp @@ -91,7 +91,7 @@ void ManaCurveWidget::updateDisplay() // Add new widgets to the layout in sorted order for (const auto &entry : sortedManaCurve) { BarWidget *barWidget = - new BarWidget(QString::number(entry.first), entry.second, highestEntry, QColor(11, 11, 11), this); + new BarWidget(QString::number(entry.first), entry.second, highestEntry, QColor(122, 122, 122), this); barLayout->addWidget(barWidget); } diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index 0dd1b513f..3683666a2 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -58,6 +58,28 @@ void DeckEditorDeckDockWidget::createDeckDock() nameEdit->setObjectName("nameEdit"); nameLabel->setBuddy(nameEdit); connect(nameEdit, &LineEditUnfocusable::textChanged, this, &DeckEditorDeckDockWidget::updateName); + + quickSettingsWidget = new SettingsButtonWidget(this); + + showBannerCardCheckBox = new QCheckBox(); + showBannerCardCheckBox->setObjectName("showBannerCardCheckBox"); + showBannerCardCheckBox->setChecked(SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); + connect(showBannerCardCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setDeckEditorBannerCardComboBoxVisible); + connect(&SettingsCache::instance(), &SettingsCache::deckEditorBannerCardComboBoxVisibleChanged, this, + &DeckEditorDeckDockWidget::updateShowBannerCardComboBox); + + showTagsWidgetCheckBox = new QCheckBox(); + showTagsWidgetCheckBox->setObjectName("showTagsWidgetCheckBox"); + showTagsWidgetCheckBox->setChecked(SettingsCache::instance().getDeckEditorTagsWidgetVisible()); + connect(showTagsWidgetCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setDeckEditorTagsWidgetVisible); + connect(&SettingsCache::instance(), &SettingsCache::deckEditorTagsWidgetVisibleChanged, this, + &DeckEditorDeckDockWidget::updateShowTagsWidget); + + quickSettingsWidget->addSettingsWidget(showBannerCardCheckBox); + quickSettingsWidget->addSettingsWidget(showTagsWidgetCheckBox); + commentsLabel = new QLabel(); commentsLabel->setObjectName("commentsLabel"); commentsEdit = new QTextEdit; @@ -69,6 +91,7 @@ void DeckEditorDeckDockWidget::createDeckDock() bannerCardLabel = new QLabel(); bannerCardLabel->setObjectName("bannerCardLabel"); bannerCardLabel->setText(tr("Banner Card")); + bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); bannerCardComboBox = new QComboBox(this); connect(deckModel, &DeckListModel::dataChanged, this, [this]() { // Delay the update to avoid race conditions @@ -76,8 +99,10 @@ void DeckEditorDeckDockWidget::createDeckDock() }); connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeckEditorDeckDockWidget::setBannerCard); + bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()); + deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); aIncrement = new QAction(QString(), this); aIncrement->setIcon(QPixmap("theme:icons/increment")); @@ -109,6 +134,7 @@ void DeckEditorDeckDockWidget::createDeckDock() upperLayout->addWidget(nameLabel, 0, 0); upperLayout->addWidget(nameEdit, 0, 1); + upperLayout->addWidget(quickSettingsWidget, 0, 2); upperLayout->addWidget(commentsLabel, 1, 0); upperLayout->addWidget(commentsEdit, 1, 1); @@ -202,19 +228,22 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const void DeckEditorDeckDockWidget::updateName(const QString &name) { deckModel->getDeckList()->setName(name); - emit deckChanged(); + emit nameChanged(); + emit deckModified(); } void DeckEditorDeckDockWidget::updateComments() { deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); - emit deckChanged(); + emit commentsChanged(); + emit deckModified(); } void DeckEditorDeckDockWidget::updateHash() { hashLabel->setText(deckModel->getDeckList()->getDeckHash()); - emit deckChanged(); + emit hashChanged(); + emit deckModified(); } void DeckEditorDeckDockWidget::updateBannerCardComboBox() @@ -268,6 +297,10 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() int restoredIndex = bannerCardComboBox->findText(currentText); if (restoredIndex != -1) { bannerCardComboBox->setCurrentIndex(restoredIndex); + if (deckModel->getDeckList()->getBannerCard().second != + bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()).toMap()["uuid"].toString()) { + setBannerCard(restoredIndex); + } } else { // Add a placeholder "-" and set it as the current selection int bannerIndex = bannerCardComboBox->findText(deckModel->getDeckList()->getBannerCard().first); @@ -288,7 +321,18 @@ void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) QVariantMap itemData = bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()).toMap(); deckModel->getDeckList()->setBannerCard( QPair(itemData["name"].toString(), itemData["uuid"].toString())); - emit deckChanged(); + emit deckModified(); +} + +void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible) +{ + bannerCardLabel->setHidden(!visible); + bannerCardComboBox->setHidden(!visible); +} + +void DeckEditorDeckDockWidget::updateShowTagsWidget(const bool visible) +{ + deckTagsDisplayWidget->setHidden(!visible); } /** @@ -325,8 +369,12 @@ void DeckEditorDeckDockWidget::cleanDeck() { deckModel->cleanList(); nameEdit->setText(QString()); + emit nameChanged(); commentsEdit->setText(QString()); + emit commentsChanged(); hashLabel->setText(QString()); + emit hashChanged(); + emit deckModified(); emit deckChanged(); updateBannerCardComboBox(); deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); @@ -385,7 +433,7 @@ void DeckEditorDeckDockWidget::actSwapCard() deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); if (isModified) { - emit deckChanged(); + emit deckModified(); } update(); @@ -480,7 +528,7 @@ void DeckEditorDeckDockWidget::actRemoveCard() deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); if (isModified) { - emit deckChanged(); + emit deckModified(); } } @@ -498,7 +546,7 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of else deckModel->setData(numberIndex, new_count, Qt::EditRole); - emit deckChanged(); + emit deckModified(); } void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point) @@ -526,6 +574,9 @@ void DeckEditorDeckDockWidget::retranslateUi() setWindowTitle(tr("Deck")); nameLabel->setText(tr("Deck &name:")); + quickSettingsWidget->setToolTip(tr("Banner Card/Tags Visibility Settings")); + showBannerCardCheckBox->setText(tr("Show banner card selection menu")); + showTagsWidgetCheckBox->setText(tr("Show tags selection menu")); commentsLabel->setText(tr("&Comments:")); hashLabel1->setText(tr("Hash:")); diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h index 89899b4bf..c81b4a6fa 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -49,7 +49,11 @@ public slots: void offsetCountAtIndex(const QModelIndex &idx, int offset); signals: + void nameChanged(); + void commentsChanged(); + void hashChanged(); void deckChanged(); + void deckModified(); void cardChanged(CardInfoPtr _card); private: @@ -57,6 +61,9 @@ private: KeySignals deckViewKeySignals; QLabel *nameLabel; LineEditUnfocusable *nameEdit; + SettingsButtonWidget *quickSettingsWidget; + QCheckBox *showBannerCardCheckBox; + QCheckBox *showTagsWidgetCheckBox; QLabel *commentsLabel; QTextEdit *commentsEdit; QLabel *bannerCardLabel; @@ -77,6 +84,8 @@ private slots: void setBannerCard(int); void updateHash(); void refreshShortcuts(); + void updateShowBannerCardComboBox(bool visible); + void updateShowTagsWidget(bool visible); }; #endif // DECK_EDITOR_DECK_DOCK_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/general/display/percent_bar_widget.cpp b/cockatrice/src/client/ui/widgets/general/display/percent_bar_widget.cpp new file mode 100644 index 000000000..3726dd061 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/percent_bar_widget.cpp @@ -0,0 +1,46 @@ +#include "percent_bar_widget.h" + +PercentBarWidget::PercentBarWidget(QWidget *parent, double initialValue) : QWidget(parent), valueToDisplay(initialValue) +{ + setMinimumSize(50, 10); +} + +void PercentBarWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter painter(this); + QRect rect = this->rect(); + + const int midX = rect.width() / 2; + const int height = rect.height(); + + // Draw background border (no fill) + painter.setPen(QPen(Qt::black, 1)); + painter.setBrush(Qt::NoBrush); + painter.drawRect(rect.adjusted(0, 0, -1, -1)); // Avoid right/bottom overflow + + const double halfWidth = rect.width() / 2.0; + + const int barLength = static_cast((qAbs(valueToDisplay) / 100.0) * halfWidth); + + QRect fillRect; + if (valueToDisplay > 0.0) { + fillRect = QRect(midX, 0, barLength, height); + painter.fillRect(fillRect, Qt::green); + } else if (valueToDisplay < 0.0) { + fillRect = QRect(midX - barLength, 0, barLength, height); + painter.fillRect(fillRect, Qt::red); + } + + // Draw center line at 0 + painter.fillRect(midX - 1, 0, 3, height, Qt::white); + + // Draw tick marks every 10% + const int tickHeight = 4; + + for (int percent = -100; percent <= 100; percent += 10) { + int x = midX + static_cast((percent / 100.0) * halfWidth); + painter.drawLine(x, height - tickHeight, x, height); + } +} diff --git a/cockatrice/src/client/ui/widgets/general/display/percent_bar_widget.h b/cockatrice/src/client/ui/widgets/general/display/percent_bar_widget.h new file mode 100644 index 000000000..c782b448b --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/percent_bar_widget.h @@ -0,0 +1,33 @@ +#ifndef PERCENT_BAR_WIDGET_H +#define PERCENT_BAR_WIDGET_H + +#include +#include +#include + +class PercentBarWidget : public QWidget +{ + Q_OBJECT + +public: + explicit PercentBarWidget(QWidget *parent, double initialValue); + + void setValue(double newValue) + { + valueToDisplay = qBound(-100.0, newValue, 100.0); // Clamp to [-100, 100] + update(); // Trigger repaint + } + + double value() const + { + return valueToDisplay; + } + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + double valueToDisplay; // Ranges from -100 to 100 +}; + +#endif // PERCENT_BAR_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp index ef3816049..b75ca213e 100644 --- a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp @@ -70,6 +70,8 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck cardSizeWidget = new CardSizeWidget(displayOptionsWidget, flowWidget, SettingsCache::instance().getPrintingSelectorCardSize()); + connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), + &SettingsCache::setPrintingSelectorCardSize); displayOptionsWidget->addSettingsWidget(sortToolBar); displayOptionsWidget->addSettingsWidget(navigationCheckBox); diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp index e7dfd6f43..e93253585 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp @@ -27,9 +27,6 @@ VisualDatabaseDisplayColorFilterWidget::VisualDatabaseDisplayColorFilterWidget(Q layout->addWidget(manaSymbol); - // Initialize the activeColors map - activeColors[color] = false; - // Connect the color toggled signal connect(manaSymbol, &ManaSymbolWidget::colorToggled, this, &VisualDatabaseDisplayColorFilterWidget::handleColorToggled); @@ -41,16 +38,8 @@ VisualDatabaseDisplayColorFilterWidget::VisualDatabaseDisplayColorFilterWidget(Q // Connect the button's toggled signal connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplayColorFilterWidget::updateFilterMode); - connect(this, &VisualDatabaseDisplayColorFilterWidget::activeColorsChanged, this, - &VisualDatabaseDisplayColorFilterWidget::updateColorFilter); - connect(this, &VisualDatabaseDisplayColorFilterWidget::filterModeChanged, this, - &VisualDatabaseDisplayColorFilterWidget::updateColorFilter); - connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { - if (blockSync) { - return; // Skip sync if we're blocking it - } - QTimer::singleShot(100, this, &VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel); - }); + connect(filterModel, &FilterTreeModel::layoutChanged, this, + [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel); }); // Call retranslateUi to set the initial text retranslateUi(); @@ -69,95 +58,94 @@ void VisualDatabaseDisplayColorFilterWidget::retranslateUi() toggleButton->setText(tr("Mode: Include/Exclude")); break; } + + toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)")); } void VisualDatabaseDisplayColorFilterWidget::handleColorToggled(QChar color, bool active) { - activeColors[color] = active; - emit activeColorsChanged(); // Notify listeners that the active colors have changed + if (active) { + addFilter(color); + } else { + removeFilter(color); + } } -void VisualDatabaseDisplayColorFilterWidget::updateColorFilter() +void VisualDatabaseDisplayColorFilterWidget::addFilter(QChar color) { - blockSync = true; + QString colorString = color; + QString typeStr; - // Clear previous filters - filterModel->blockSignals(true); - filterModel->filterTree()->blockSignals(true); - filterModel->clearFiltersOfType(CardFilter::Attr::AttrColor); + // Remove previous filters - QSet selectedColors; - QSet excludedColors; + QList allColorFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrColor); + QList matchingFilters; - // Collect active colors in the selected and excluded sets - for (const auto &color : activeColors.keys()) { - if (activeColors[color]) { - selectedColors.insert(color); // Include this color - } else { - excludedColors.insert(color); // Exclude this color + for (const CardFilter *filter : allColorFilters) { + if (filter->term() == color) { + matchingFilters.append(filter); } } + for (const CardFilter *filter : matchingFilters) { + filterModel->removeFilter(filter); + } + + // Add actual filter + switch (currentMode) { case FilterMode::ExactMatch: - // Exact Match Mode: Only selected colors are allowed - if (!selectedColors.isEmpty()) { - // Require all selected colors (TypeAnd) - for (const auto &color : selectedColors) { - QString colorString = color; - filterModel->addFilter( - new CardFilter(colorString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrColor)); - } - - // Exclude all other colors - QStringList allPossibleColors = {"W", "U", "B", "R", "G"}; - for (const auto &color : allPossibleColors) { - if (!selectedColors.contains(color)) { - QString colorString = color; - filterModel->addFilter( - new CardFilter(colorString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor)); - } - } - } + filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrColor)); break; case FilterMode::Includes: - // Includes Mode: Just include selected colors without restrictions - for (const auto &color : selectedColors) { - QString colorString = color; - filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, - CardFilter::Attr::AttrColor)); // OR for selected colors - } + filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrColor)); break; case FilterMode::IncludeExclude: - // Include/Exclude Mode: Include selected colors and exclude unselected colors - for (const auto &color : selectedColors) { - QString colorString = color; - filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, - CardFilter::Attr::AttrColor)); // OR for selected colors - } - for (const auto &color : excludedColors) { - QString colorString = color; - filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeAndNot, - CardFilter::Attr::AttrColor)); // AND NOT for excluded colors - } + filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrColor)); break; } +} - filterModel->blockSignals(false); - filterModel->filterTree()->blockSignals(false); +void VisualDatabaseDisplayColorFilterWidget::removeFilter(QChar color) +{ + QString colorString = color; - emit filterModel->filterTree()->changed(); - emit filterModel->layoutChanged(); + // Remove inclusion filters + QList allColorFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrColor); + QList matchingFilters; - blockSync = false; + for (const CardFilter *filter : allColorFilters) { + if (filter->term() == color) { + matchingFilters.append(filter); + } + } + + for (const CardFilter *filter : matchingFilters) { + filterModel->removeFilter(filter); + } + + // Add exclusion filters if the mode demands it + switch (currentMode) { + case FilterMode::ExactMatch: + filterModel->addFilter( + new CardFilter(colorString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor)); + break; + + case FilterMode::IncludeExclude: + filterModel->addFilter( + new CardFilter(colorString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor)); + break; + + case FilterMode::Includes: + // No exclusion in Includes mode + break; + } } void VisualDatabaseDisplayColorFilterWidget::updateFilterMode() { - blockSync = true; - switch (currentMode) { case FilterMode::ExactMatch: currentMode = FilterMode::Includes; // Switch to Includes @@ -170,59 +158,100 @@ void VisualDatabaseDisplayColorFilterWidget::updateFilterMode() break; } - retranslateUi(); // Update button text based on the mode - emit filterModeChanged(currentMode); // Signal mode change - updateColorFilter(); // Reapply the filter based on the new mode + filterModel->blockSignals(true); + filterModel->filterTree()->blockSignals(true); - blockSync = false; -} - -void VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel() -{ - blockSync = true; - QSet currentFilters; - - // Get current filters of type color - for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrColor)) { - if (filter->type() == CardFilter::Type::TypeAnd || filter->type() == CardFilter::Type::TypeOr) { - currentFilters.insert(filter->term()); - } - } - - QSet activeFilterList; - - // Iterate over the activeColors map and collect the active colors as strings - for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) { - if (it.value()) { // Only add active colors - activeFilterList.insert(QString(it.key())); - } - } - - // Check if the filters in the model match the active filters - if (currentFilters == activeFilterList) { - return; - } - - // Remove filters that are in the UI but not in the model - for (const auto &color : activeFilterList) { - if (!currentFilters.contains(color)) { - activeColors[color[0]] = false; // Disable the color - } - } - - // Add filters that are in the model but not in the UI - for (const auto &color : currentFilters) { - if (!activeFilterList.contains(color)) { - activeColors[color[0]] = true; // Enable the color - } - } + filterModel->clearFiltersOfType(CardFilter::Attr::AttrColor); QList manaSymbolWidgets = findChildren(); for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) { - manaSymbolWidget->setColorActive(activeColors[manaSymbolWidget->getSymbolChar()]); + handleColorToggled(manaSymbolWidget->getSymbolChar(), manaSymbolWidget->isColorActive()); } - updateColorFilter(); - blockSync = false; -} \ No newline at end of file + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); + + retranslateUi(); // Update button text based on the mode + emit filterModeChanged(currentMode); // Signal mode change +} + +void VisualDatabaseDisplayColorFilterWidget::setManaSymbolActive(QChar color, bool active) +{ + QList manaSymbolWidgets = findChildren(); + + for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) { + if (manaSymbolWidget->getSymbolChar() == color) { + manaSymbolWidget->setColorActive(active); + } + } +} + +QList VisualDatabaseDisplayColorFilterWidget::getActiveColors() +{ + QList activeColors; + QList manaSymbolWidgets = findChildren(); + + for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) { + if (manaSymbolWidget->isColorActive()) { + activeColors.append(manaSymbolWidget->getSymbolChar()); + } + } + + return activeColors; +} + +void VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel() +{ + QList allColorFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrColor); + + QList manaSymbolWidgets = findChildren(); + + for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) { + bool found = false; + for (const CardFilter *filter : allColorFilters) { + if (manaSymbolWidget->getSymbolChar() == filter->term()) { + switch (currentMode) { + case FilterMode::ExactMatch: + switch (filter->type()) { + case CardFilter::Type::TypeAnd: + setManaSymbolActive(filter->term().at(0), true); + break; + default: + setManaSymbolActive(filter->term().at(0), false); + break; + } + break; + case FilterMode::Includes: + switch (filter->type()) { + case CardFilter::Type::TypeOr: + setManaSymbolActive(filter->term().at(0), true); + break; + default: + setManaSymbolActive(filter->term().at(0), false); + break; + } + break; + case FilterMode::IncludeExclude: + switch (filter->type()) { + case CardFilter::Type::TypeOr: + setManaSymbolActive(filter->term().at(0), true); + break; + default: + setManaSymbolActive(filter->term().at(0), false); + break; + } + break; + } + found = true; + } + } + + if (!found) { + setManaSymbolActive(manaSymbolWidget->getSymbolChar(), false); + } + } +} diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.h index 191d00eee..3936c8843 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.h @@ -43,21 +43,21 @@ public: signals: void filterModeChanged(FilterMode filterMode); - void activeColorsChanged(); private slots: void handleColorToggled(QChar color, bool active); - void updateColorFilter(); + void addFilter(QChar color); + void removeFilter(QChar color); void updateFilterMode(); + void setManaSymbolActive(QChar color, bool active); + QList getActiveColors(); void syncWithFilterModel(); private: FilterTreeModel *filterModel; QHBoxLayout *layout; QPushButton *toggleButton; - QMap activeColors; FilterMode currentMode = FilterMode::Includes; // Default mode - bool blockSync = false; }; #endif // VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp index 1a099501e..21ec205fd 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp @@ -39,6 +39,7 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi void VisualDatabaseDisplayFilterSaveLoadWidget::retranslateUi() { saveButton->setText(tr("Save Filter")); + saveButton->setToolTip(tr("Save all currently applied filters to a file")); filenameInput->setPlaceholderText(tr("Enter filename...")); } diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp index 688f8f4d2..8cf9c6605 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp @@ -47,6 +47,14 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi createMainTypeButtons(); // Populate buttons initially updateFilterMode(false); // Initialize toggle button text + + retranslateUi(); +} + +void VisualDatabaseDisplayMainTypeFilterWidget::retranslateUi() +{ + spinBox->setToolTip(tr("Do not display card main-types with less than this amount of cards in the database")); + toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)")); } void VisualDatabaseDisplayMainTypeFilterWidget::createMainTypeButtons() diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h index b13071825..07086bbe3 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h @@ -16,6 +16,7 @@ class VisualDatabaseDisplayMainTypeFilterWidget : public QWidget Q_OBJECT public: explicit VisualDatabaseDisplayMainTypeFilterWidget(QWidget *parent, FilterTreeModel *filterModel); + void retranslateUi(); void createMainTypeButtons(); void updateMainTypeButtonsVisibility(); int getMaxMainTypeCount() const; diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index 69e782b66..e0ededd50 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -45,6 +45,7 @@ void VisualDatabaseDisplayNameFilterWidget::retranslateUi() { searchBox->setPlaceholderText(tr("Filter by name...")); loadFromDeckButton->setText(tr("Load from Deck")); + loadFromDeckButton->setToolTip(tr("Apply all card names in currently loaded deck as exact match name filters")); } void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp index 795c83d68..a79ef9ff6 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp @@ -3,12 +3,44 @@ #include "../../../../game/cards/card_database_manager.h" #include "../../../../game/filters/filter_tree.h" #include "../../../../game/filters/filter_tree_model.h" +#include "../../../../settings/cache_settings.h" #include #include #include #include +VisualDatabaseDisplayRecentSetFilterSettingsWidget::VisualDatabaseDisplayRecentSetFilterSettingsWidget(QWidget *parent) + : QWidget(parent) +{ + layout = new QHBoxLayout(this); + setLayout(layout); + + filterToMostRecentSetsCheckBox = new QCheckBox(this); + filterToMostRecentSetsCheckBox->setChecked( + SettingsCache::instance().getVisualDatabaseDisplayFilterToMostRecentSetsEnabled()); + connect(filterToMostRecentSetsCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsEnabled); + + filterToMostRecentSetsAmount = new QSpinBox(this); + filterToMostRecentSetsAmount->setMinimum(1); + filterToMostRecentSetsAmount->setMaximum(100); + filterToMostRecentSetsAmount->setValue( + SettingsCache::instance().getVisualDatabaseDisplayFilterToMostRecentSetsAmount()); + connect(filterToMostRecentSetsAmount, QOverload::of(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount); + + layout->addWidget(filterToMostRecentSetsCheckBox); + layout->addWidget(filterToMostRecentSetsAmount); + + retranslateUi(); +} + +void VisualDatabaseDisplayRecentSetFilterSettingsWidget::retranslateUi() +{ + filterToMostRecentSetsCheckBox->setText(tr("Filter to most recent sets")); +} + VisualDatabaseDisplaySetFilterWidget::VisualDatabaseDisplaySetFilterWidget(QWidget *parent, FilterTreeModel *_filterModel) : QWidget(parent), filterModel(_filterModel) @@ -19,6 +51,14 @@ VisualDatabaseDisplaySetFilterWidget::VisualDatabaseDisplaySetFilterWidget(QWidg layout = new QVBoxLayout(this); setLayout(layout); + recentSetsSettingsWidget = new VisualDatabaseDisplayRecentSetFilterSettingsWidget(this); + layout->addWidget(recentSetsSettingsWidget); + + connect(&SettingsCache::instance(), &SettingsCache::visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged, this, + &VisualDatabaseDisplaySetFilterWidget::filterToRecentSets); + connect(&SettingsCache::instance(), &SettingsCache::visualDatabaseDisplayFilterToMostRecentSetsAmountChanged, this, + &VisualDatabaseDisplaySetFilterWidget::filterToRecentSets); + searchBox = new QLineEdit(this); searchBox->setPlaceholderText(tr("Search sets...")); layout->addWidget(searchBox); @@ -36,24 +76,26 @@ VisualDatabaseDisplaySetFilterWidget::VisualDatabaseDisplaySetFilterWidget(QWidg connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel); }); - createSetButtons(); // Populate buttons initially - updateFilterMode(false); // Initialize toggle button text + createSetButtons(); // Populate buttons initially + retranslateUi(); +} + +void VisualDatabaseDisplaySetFilterWidget::retranslateUi() +{ + toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); } void VisualDatabaseDisplaySetFilterWidget::createSetButtons() { - SetList shared_pointerses = CardDatabaseManager::getInstance()->getSetList(); + SetList allSets = CardDatabaseManager::getInstance()->getSetList(); // Sort by release date - std::sort(shared_pointerses.begin(), shared_pointerses.end(), + std::sort(allSets.begin(), allSets.end(), [](const auto &a, const auto &b) { return a->getReleaseDate() > b->getReleaseDate(); }); - int setsToPreactivate = 10; - int setsActivated = 0; - - for (const auto &shared_pointer : shared_pointerses) { - QString shortName = shared_pointer->getShortName(); - QString longName = shared_pointer->getLongName(); + for (const auto &set : allSets) { + QString shortName = set->getShortName(); + QString longName = set->getLongName(); auto *button = new QPushButton(longName + " (" + shortName + ")", flowWidget); button->setCheckable(true); @@ -66,14 +108,47 @@ void VisualDatabaseDisplaySetFilterWidget::createSetButtons() // Connect toggle signal connect(button, &QPushButton::toggled, this, [this, shortName](bool checked) { handleSetToggled(shortName, checked); }); - if (setsActivated < setsToPreactivate) { - setsActivated++; - activeSets[shortName] = true; - button->setChecked(true); - } } - updateSetFilter(); - updateSetButtonsVisibility(); // Ensure visibility is updated initially + + filterToRecentSets(); +} + +void VisualDatabaseDisplaySetFilterWidget::filterToRecentSets() +{ + if (SettingsCache::instance().getVisualDatabaseDisplayFilterToMostRecentSetsEnabled()) { + for (auto set : activeSets.keys()) { + activeSets[set] = false; + } + + SetList allSets = CardDatabaseManager::getInstance()->getSetList(); + + // Sort by release date + std::sort(allSets.begin(), allSets.end(), + [](const auto &a, const auto &b) { return a->getReleaseDate() > b->getReleaseDate(); }); + + int setsToPreactivate = SettingsCache::instance().getVisualDatabaseDisplayFilterToMostRecentSetsAmount(); + int setsActivated = 0; + + for (const auto &set : allSets) { + QString shortName = set->getShortName(); + QString longName = set->getLongName(); + + auto button = setButtons[shortName]; + + if (setsActivated < setsToPreactivate) { + setsActivated++; + activeSets[shortName] = true; + button->blockSignals(true); + button->setChecked(true); + button->blockSignals(false); + } else { + break; + } + } + + updateSetFilter(); + updateSetButtonsVisibility(); + } } void VisualDatabaseDisplaySetFilterWidget::updateSetButtonsVisibility() @@ -183,13 +258,15 @@ void VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel() for (const auto &key : setButtons.keys()) { bool active = selectedSets.contains(key); activeSets[key] = active; + setButtons[key]->blockSignals(true); setButtons[key]->setChecked(active); + setButtons[key]->blockSignals(false); } } void VisualDatabaseDisplaySetFilterWidget::updateFilterMode(bool checked) { exactMatchMode = checked; - toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); updateSetFilter(); -} \ No newline at end of file + retranslateUi(); +} diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.h index 41da57ef8..9079850ca 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.h @@ -4,19 +4,36 @@ #include "../../../../game/filters/filter_tree_model.h" #include "../general/layout_containers/flow_widget.h" +#include #include #include #include +#include #include #include #include +class VisualDatabaseDisplayRecentSetFilterSettingsWidget : public QWidget +{ + Q_OBJECT +public: + VisualDatabaseDisplayRecentSetFilterSettingsWidget(QWidget *parent); + void retranslateUi(); + +private: + QHBoxLayout *layout; + QCheckBox *filterToMostRecentSetsCheckBox; + QSpinBox *filterToMostRecentSetsAmount; +}; + class VisualDatabaseDisplaySetFilterWidget : public QWidget { Q_OBJECT public: explicit VisualDatabaseDisplaySetFilterWidget(QWidget *parent, FilterTreeModel *filterModel); + void retranslateUi(); void createSetButtons(); + void filterToRecentSets(); void updateSetButtonsVisibility(); void handleSetToggled(const QString &setShortName, bool active); @@ -28,6 +45,7 @@ private: FilterTreeModel *filterModel; QMap allMainCardTypesWithCount; QVBoxLayout *layout; + VisualDatabaseDisplayRecentSetFilterSettingsWidget *recentSetsSettingsWidget; QLineEdit *searchBox; FlowWidget *flowWidget; QPushButton *toggleButton; // Mode switch button diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp index 4b8fc21d8..c6ea811a9 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp @@ -51,6 +51,14 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg createSubTypeButtons(); // Populate buttons initially updateFilterMode(false); // Initialize the toggle button text + + retranslateUi(); +} + +void VisualDatabaseDisplaySubTypeFilterWidget::retranslateUi() +{ + spinBox->setToolTip(tr("Do not display card sub-types with less than this amount of cards in the database")); + toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)")); } void VisualDatabaseDisplaySubTypeFilterWidget::createSubTypeButtons() diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h index 72a0a25bb..64c9c19cd 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h @@ -16,6 +16,7 @@ class VisualDatabaseDisplaySubTypeFilterWidget : public QWidget Q_OBJECT public: explicit VisualDatabaseDisplaySubTypeFilterWidget(QWidget *parent, FilterTreeModel *filterModel); + void retranslateUi(); void createSubTypeButtons(); void updateSubTypeButtonsVisibility(); int getMaxSubTypeCount() const; diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp index d1e4b2db5..f394f508f 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp @@ -62,9 +62,6 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, filterModel = new FilterTreeModel(); filterModel->setObjectName("filterModel"); - databaseDisplayModel->setFilterTree(filterModel->filterTree()); - - connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::searchModelChanged); searchKeySignals.setObjectName("searchKeySignals"); connect(searchEdit, &QLineEdit::textChanged, this, &VisualDatabaseDisplayWidget::updateSearch); @@ -144,11 +141,27 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::searchModelChanged); + databaseDisplayModel->setFilterTree(filterModel->filterTree()); + + connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::searchModelChanged); + loadCardsTimer = new QTimer(this); loadCardsTimer->setSingleShot(true); // Ensure it only fires once after the timeout connect(loadCardsTimer, &QTimer::timeout, this, [this]() { loadCurrentPage(); }); loadCardsTimer->start(5000); + + retranslateUi(); +} + +void VisualDatabaseDisplayWidget::retranslateUi() +{ + clearFilterWidget->setToolTip(tr("Clear all filters")); + + quickFilterSaveLoadWidget->setToolTip(tr("Save and load filters")); + quickFilterNameWidget->setToolTip(tr("Filter by exact card name")); + quickFilterSubTypeWidget->setToolTip(tr("Filter by card sub-type")); + quickFilterSetWidget->setToolTip(tr("Filter by set")); } void VisualDatabaseDisplayWidget::resizeEvent(QResizeEvent *event) @@ -195,13 +208,30 @@ void VisualDatabaseDisplayWidget::populateCards() databaseDisplayModel->fetchMore(QModelIndex()); } + QList setFilters = filterModel->getFiltersOfType(CardFilter::AttrSet); + const CardFilter *setFilter = nullptr; + if (setFilters.length() == 1) { + setFilter = setFilters.at(0); + } + for (int row = start; row < end; ++row) { qCDebug(VisualDatabaseDisplayLog) << "Adding " << row; QModelIndex index = databaseDisplayModel->index(row, CardDatabaseModel::NameColumn); QVariant name = databaseDisplayModel->data(index, Qt::DisplayRole); qCDebug(VisualDatabaseDisplayLog) << name.toString(); + if (CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(name.toString())) { - addCard(info); + if (setFilter) { + CardInfoPerSetMap setMap = info->getSets(); + if (setMap.contains(setFilter->term())) { + for (CardInfoPerSet cardSetInstance : setMap[setFilter->term()]) { + addCard(CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + name.toString(), cardSetInstance.getProperty("uuid"))); + } + } + } else { + addCard(info); + } } else { qCDebug(VisualDatabaseDisplayLog) << "Card not found in database!"; } diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.h index 6d75698dc..8f0cdba97 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.h @@ -38,6 +38,7 @@ public: AbstractTabDeckEditor *deckEditor, CardDatabaseModel *database_model, CardDatabaseDisplayModel *database_display_model); + void retranslateUi(); void adjustCardsPerPage(); void populateCards(); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index f6b73b0d5..8530a8233 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -2,6 +2,7 @@ #include "../../../../deck/deck_loader.h" #include "../../../../game/cards/card_database_manager.h" +#include "../../../../settings/cache_settings.h" #include "../cards/card_info_picture_widget.h" #include @@ -12,23 +13,45 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare layout = new QVBoxLayout(this); setLayout(layout); + resetAndHandSizeContainerWidget = new QWidget(this); + resetAndHandSizeLayout = new QHBoxLayout(resetAndHandSizeContainerWidget); + resetAndHandSizeContainerWidget->setLayout(resetAndHandSizeLayout); + resetButton = new QPushButton(this); connect(resetButton, SIGNAL(clicked()), this, SLOT(updateDisplay())); - layout->addWidget(resetButton); + resetAndHandSizeLayout->addWidget(resetButton); - flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + handSizeSpinBox = new QSpinBox(this); + handSizeSpinBox->setValue(SettingsCache::instance().getVisualDeckEditorSampleHandSize()); + handSizeSpinBox->setMinimum(1); + connect(handSizeSpinBox, QOverload::of(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setVisualDeckEditorSampleHandSize); + connect(handSizeSpinBox, QOverload::of(&QSpinBox::valueChanged), this, + &VisualDeckEditorSampleHandWidget::updateDisplay); + resetAndHandSizeLayout->addWidget(handSizeSpinBox); + + layout->addWidget(resetAndHandSizeContainerWidget); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); layout->addWidget(flowWidget); - for (CardInfoPtr card : getRandomCards(7)) { + cardSizeWidget = new CardSizeWidget(this, flowWidget); + layout->addWidget(cardSizeWidget); + + for (CardInfoPtr card : getRandomCards(handSizeSpinBox->value())) { auto displayWidget = new CardInfoPictureWidget(this); displayWidget->setCard(card); + displayWidget->setScaleFactor(cardSizeWidget->getSlider()->value()); flowWidget->addWidget(displayWidget); } + + retranslateUi(); } void VisualDeckEditorSampleHandWidget::retranslateUi() { - resetButton->setText(tr("Reset")); + resetButton->setText(tr("Draw a new sample hand")); + handSizeSpinBox->setToolTip(tr("Sample hand size")); } void VisualDeckEditorSampleHandWidget::setDeckModel(DeckListModel *deckModel) @@ -41,9 +64,12 @@ void VisualDeckEditorSampleHandWidget::setDeckModel(DeckListModel *deckModel) void VisualDeckEditorSampleHandWidget::updateDisplay() { flowWidget->clearLayout(); - for (CardInfoPtr card : getRandomCards(7)) { + for (CardInfoPtr card : getRandomCards(handSizeSpinBox->value())) { auto displayWidget = new CardInfoPictureWidget(this); displayWidget->setCard(card); + displayWidget->setScaleFactor(cardSizeWidget->getSlider()->value()); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, displayWidget, + &CardInfoPictureWidget::setScaleFactor); flowWidget->addWidget(displayWidget); } } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h index 44dc04cab..ba09a978e 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h @@ -1,10 +1,12 @@ #ifndef VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H #define VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H +#include "../../../../client/ui/widgets/cards/card_size_widget.h" #include "../../../../deck/deck_list_model.h" #include "../general/layout_containers/flow_widget.h" #include +#include #include class VisualDeckEditorSampleHandWidget : public QWidget @@ -22,8 +24,12 @@ public slots: private: DeckListModel *deckListModel; QVBoxLayout *layout; + QWidget *resetAndHandSizeContainerWidget; + QHBoxLayout *resetAndHandSizeLayout; QPushButton *resetButton; + QSpinBox *handSizeSpinBox; FlowWidget *flowWidget; + CardSizeWidget *cardSizeWidget; }; #endif // VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp index cf9c95071..1ebf59e36 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp @@ -29,7 +29,6 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_ connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorWidget::decklistDataChanged); // The Main Widget and Main Layout, which contain a single Widget: The Scroll Area - setMinimumSize(0, 0); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); mainLayout = new QVBoxLayout(this); setLayout(mainLayout); @@ -162,7 +161,7 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_ groupAndSortLayout->addWidget(sortCriteriaButton); groupAndSortLayout->addWidget(displayTypeButton); - scrollArea = new QScrollArea(); + scrollArea = new QScrollArea(this); scrollArea->setWidgetResizable(true); scrollArea->setMinimumSize(0, 0); @@ -191,7 +190,12 @@ void VisualDeckEditorWidget::retranslateUi() { sortLabel->setText(tr("Click and drag to change the sort order within the groups")); searchPushButton->setText(tr("Quick search and add card")); + searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add " + "preferred printing to the deck on pressing enter")); + sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups")); displayTypeButton->setText(tr("Flat Layout")); + displayTypeButton->setToolTip( + tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } void VisualDeckEditorWidget::updateZoneWidgets() @@ -227,6 +231,7 @@ void VisualDeckEditorWidget::addZoneIfDoesNotExist() for (DeckCardZoneDisplayWidget *displayWidget : cardZoneDisplayWidgets) { if (displayWidget->zoneName == zone) { found = true; + displayWidget->displayCards(); break; } } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp index b048b2372..ef59e90ff 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp @@ -47,6 +47,7 @@ void DeckPreviewColorIdentityFilterWidget::retranslateUi() { // Set the toggle button text based on the current mode toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); + toggleButton->setToolTip(tr("Color identity filter mode (AND/OR/NOT conjunctions of filters)")); } void DeckPreviewColorIdentityFilterWidget::handleColorToggled(QChar color, bool active) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp index 815545aab..4d844f6f8 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp @@ -11,7 +11,7 @@ #include DeckPreviewTagDialog::DeckPreviewTagDialog(const QStringList &knownTags, const QStringList &activeTags, QWidget *parent) - : QDialog(parent), activeTags_(activeTags) + : QDialog(parent), activeTags(activeTags) { resize(400, 500); @@ -100,13 +100,13 @@ DeckPreviewTagDialog::DeckPreviewTagDialog(const QStringList &knownTags, const Q combinedTags.removeDuplicates(); // Main layout - auto *mainLayout = new QVBoxLayout(this); + mainLayout = new QVBoxLayout(this); // Filter bar - filterInput_ = new QLineEdit(this); - mainLayout->addWidget(filterInput_); + filterInput = new QLineEdit(this); + mainLayout->addWidget(filterInput); - connect(filterInput_, &QLineEdit::textChanged, this, &DeckPreviewTagDialog::filterTags); + connect(filterInput, &QLineEdit::textChanged, this, &DeckPreviewTagDialog::filterTags); // Instruction label instructionLabel = new QLabel(this); @@ -114,31 +114,34 @@ DeckPreviewTagDialog::DeckPreviewTagDialog(const QStringList &knownTags, const Q mainLayout->addWidget(instructionLabel); // Tag list view - tagListView_ = new QListWidget(this); - mainLayout->addWidget(tagListView_); + tagListView = new QListWidget(this); + mainLayout->addWidget(tagListView); // Populate combined tags for (const auto &tag : combinedTags) { - auto *item = new QListWidgetItem(tagListView_); + auto *item = new QListWidgetItem(tagListView); auto *tagWidget = new DeckPreviewTagItemWidget(tag, activeTags.contains(tag), this); - tagListView_->addItem(item); - tagListView_->setItemWidget(item, tagWidget); + tagListView->addItem(item); + tagListView->setItemWidget(item, tagWidget); connect(tagWidget->checkBox(), &QCheckBox::toggled, this, &DeckPreviewTagDialog::onCheckboxStateChanged); } // Add tag input layout - auto *addTagLayout = new QHBoxLayout(); - newTagInput_ = new QLineEdit(this); - addTagButton_ = new QPushButton(this); - addTagLayout->addWidget(newTagInput_); - addTagLayout->addWidget(addTagButton_); + addTagLayout = new QHBoxLayout(this); + newTagInput = new QLineEdit(this); + addTagButton = new QPushButton(this); + addTagButton->setEnabled(false); + addTagLayout->addWidget(newTagInput); + addTagLayout->addWidget(addTagButton); mainLayout->addLayout(addTagLayout); - connect(addTagButton_, &QPushButton::clicked, this, &DeckPreviewTagDialog::addTag); + connect(addTagButton, &QPushButton::clicked, this, &DeckPreviewTagDialog::addTag); + connect(newTagInput, &QLineEdit::textChanged, this, + [this](const QString &text) { addTagButton->setEnabled(!text.trimmed().isEmpty()); }); // OK and Cancel buttons - auto *buttonLayout = new QHBoxLayout(); + buttonLayout = new QHBoxLayout(this); okButton = new QPushButton(this); cancelButton = new QPushButton(this); buttonLayout->addStretch(); @@ -155,30 +158,30 @@ void DeckPreviewTagDialog::retranslateUi() { setWindowTitle(tr("Deck Tags Manager")); instructionLabel->setText(tr("Manage your deck tags. Check or uncheck tags as needed, or add new ones:")); - newTagInput_->setPlaceholderText(tr("Add a new tag (e.g., Aggro️)")); - addTagButton_->setText(tr("Add Tag")); - filterInput_->setPlaceholderText(tr("Filter tags...")); + newTagInput->setPlaceholderText(tr("Add a new tag (e.g., Aggro️)")); + addTagButton->setText(tr("Add Tag")); + filterInput->setPlaceholderText(tr("Filter tags...")); okButton->setText(tr("OK")); cancelButton->setText(tr("Cancel")); } QStringList DeckPreviewTagDialog::getActiveTags() const { - return activeTags_; + return activeTags; } void DeckPreviewTagDialog::addTag() { - QString newTag = newTagInput_->text().trimmed(); + QString newTag = newTagInput->text().trimmed(); if (newTag.isEmpty()) { QMessageBox::warning(this, tr("Invalid Input"), tr("Tag name cannot be empty!")); return; } // Prevent duplicate tags - for (int i = 0; i < tagListView_->count(); ++i) { - auto *item = tagListView_->item(i); - auto *tagWidget = qobject_cast(tagListView_->itemWidget(item)); + for (int i = 0; i < tagListView->count(); ++i) { + auto *item = tagListView->item(i); + auto *tagWidget = qobject_cast(tagListView->itemWidget(item)); if (tagWidget && tagWidget->checkBox()->text() == newTag) { QMessageBox::warning(this, tr("Duplicate Tag"), tr("This tag already exists.")); return; @@ -186,23 +189,23 @@ void DeckPreviewTagDialog::addTag() } // Add the new tag - auto *item = new QListWidgetItem(tagListView_); + auto *item = new QListWidgetItem(tagListView); auto *tagWidget = new DeckPreviewTagItemWidget(newTag, true, this); - tagListView_->addItem(item); - tagListView_->setItemWidget(item, tagWidget); - activeTags_.append(newTag); + tagListView->addItem(item); + tagListView->setItemWidget(item, tagWidget); + activeTags.append(newTag); connect(tagWidget->checkBox(), &QCheckBox::toggled, this, &DeckPreviewTagDialog::onCheckboxStateChanged); // Clear the input field - newTagInput_->clear(); + newTagInput->clear(); } void DeckPreviewTagDialog::filterTags(const QString &text) { - for (int i = 0; i < tagListView_->count(); ++i) { - auto *item = tagListView_->item(i); - auto *tagWidget = qobject_cast(tagListView_->itemWidget(item)); + for (int i = 0; i < tagListView->count(); ++i) { + auto *item = tagListView->item(i); + auto *tagWidget = qobject_cast(tagListView->itemWidget(item)); if (tagWidget) { bool matches = tagWidget->checkBox()->text().contains(text, Qt::CaseInsensitive); item->setHidden(!matches); @@ -212,12 +215,12 @@ void DeckPreviewTagDialog::filterTags(const QString &text) void DeckPreviewTagDialog::onCheckboxStateChanged() { - activeTags_.clear(); - for (int i = 0; i < tagListView_->count(); ++i) { - auto *item = tagListView_->item(i); - auto *tagWidget = qobject_cast(tagListView_->itemWidget(item)); + activeTags.clear(); + for (int i = 0; i < tagListView->count(); ++i) { + auto *item = tagListView->item(i); + auto *tagWidget = qobject_cast(tagListView->itemWidget(item)); if (tagWidget && tagWidget->checkBox()->isChecked()) { - activeTags_.append(tagWidget->checkBox()->text()); + activeTags.append(tagWidget->checkBox()->text()); } } } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h index 8b528d445..9d6b5792b 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h @@ -8,6 +8,7 @@ #include #include #include +#include class DeckPreviewTagDialog : public QDialog { @@ -26,14 +27,17 @@ private slots: void retranslateUi(); private: + QVBoxLayout *mainLayout; QLabel *instructionLabel; - QListWidget *tagListView_; - QLineEdit *filterInput_; - QLineEdit *newTagInput_; - QPushButton *addTagButton_; + QListWidget *tagListView; + QLineEdit *filterInput; + QHBoxLayout *addTagLayout; + QLineEdit *newTagInput; + QPushButton *addTagButton; + QHBoxLayout *buttonLayout; QPushButton *okButton; QPushButton *cancelButton; - QStringList activeTags_; + QStringList activeTags; }; #endif // DECK_PREVIEW_TAG_DIALOG_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp index 324cc7288..4cfd9447d 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp @@ -6,7 +6,7 @@ #include DeckPreviewTagDisplayWidget::DeckPreviewTagDisplayWidget(QWidget *parent, const QString &_tagName) - : QWidget(parent), tagName(_tagName), isSelected(false) + : QWidget(parent), tagName(_tagName), state(TagState::NotSelected) { // Create layout auto *layout = new QHBoxLayout(this); @@ -48,36 +48,58 @@ QSize DeckPreviewTagDisplayWidget::sizeHint() const void DeckPreviewTagDisplayWidget::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - setSelected(!isSelected); - emit tagClicked(); + switch (event->button()) { + case Qt::LeftButton: + setState(TagState::Selected); + break; + case Qt::RightButton: + setState(TagState::Excluded); + break; + case Qt::MiddleButton: + setState(TagState::NotSelected); + break; + default: + break; } - QWidget::mousePressEvent(event); -} -void DeckPreviewTagDisplayWidget::setSelected(bool selected) -{ - isSelected = selected; - update(); // Trigger a repaint + emit tagClicked(); + QWidget::mousePressEvent(event); } void DeckPreviewTagDisplayWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); - // Set background color - QColor backgroundColor = isSelected ? QColor(173, 216, 230) : Qt::white; + QColor backgroundColor; + QColor borderColor; + int borderWidth; + + switch (state) { + case TagState::Selected: + backgroundColor = QColor(173, 216, 230); // Light blue + borderColor = Qt::blue; + borderWidth = 2; + break; + case TagState::Excluded: + backgroundColor = QColor(255, 182, 193); // Light red/pink + borderColor = Qt::red; + borderWidth = 2; + break; + case TagState::NotSelected: + default: + backgroundColor = Qt::white; + borderColor = Qt::gray; + borderWidth = 1; + break; + } + painter.setBrush(backgroundColor); painter.setPen(Qt::NoPen); - - // Draw background painter.drawRect(rect()); - // Draw border - QColor borderColor = isSelected ? Qt::blue : Qt::gray; - QPen borderPen(borderColor, isSelected ? 2 : 1); + QPen borderPen(borderColor, borderWidth); painter.setPen(borderPen); - painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width + painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Calculate font size based on widget height QFont font = painter.font(); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h index 10cc39baf..ff649dd32 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h @@ -6,6 +6,13 @@ #include #include +enum class TagState +{ + NotSelected, + Selected, + Excluded +}; + class DeckPreviewTagDisplayWidget : public QWidget { Q_OBJECT @@ -22,16 +29,16 @@ public: { return tagName; } - bool getSelected() const + TagState getState() const { - return isSelected; + return state; } - /** - * @brief Sets the selected state of the tag. - * @param selected True if the tag is selected, false otherwise. - */ - void setSelected(bool selected); + void setState(const TagState newState) + { + state = newState; + update(); + }; signals: /** @@ -61,7 +68,7 @@ private: QLabel *tagLabel; ///< Label for displaying the tag name. QPushButton *closeButton; ///< Button to close/remove the tag. QString tagName; ///< The name of the tag. - bool isSelected; ///< Indicates whether the tag is selected. + TagState state; ///< Indicates whether the tag is unselected, selected, or excluded. }; #endif // DECK_PREVIEW_TAG_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index f3bebbb90..556968d2b 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent, @@ -27,6 +28,8 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent, deckLoader = new DeckLoader(); deckLoader->setParent(this); connect(deckLoader, &DeckLoader::loadFinished, this, &DeckPreviewWidget::initializeUi); + /* TODO: We shouldn't update the tags on *every* deck load, since it's kinda expensive. We should instead count how + 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); @@ -194,6 +197,7 @@ void DeckPreviewWidget::updateBannerCardComboBox() // Block signals temporarily bool wasBlocked = bannerCardComboBox->blockSignals(true); + bannerCardComboBox->setUpdatesEnabled(false); // Clear the existing items in the combo box bannerCardComboBox->clear(); @@ -209,12 +213,7 @@ void DeckPreviewWidget::updateBannerCardComboBox() continue; for (int k = 0; k < currentCard->getNumber(); ++k) { - CardInfoPtr info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId( - currentCard->getName(), currentCard->getCardProviderId()); - if (info) { - bannerCardSet.insert( - QPair(currentCard->getName(), currentCard->getCardProviderId())); - } + bannerCardSet.insert(QPair(currentCard->getName(), currentCard->getCardProviderId())); } } } @@ -226,14 +225,19 @@ void DeckPreviewWidget::updateBannerCardComboBox() return a.first.toLower() < b.first.toLower(); }); - for (const auto &pair : pairList) { - QVariantMap dataMap; - dataMap["name"] = pair.first; - dataMap["uuid"] = pair.second; + // This is *slightly* more performant than using addItem in a loop. - bannerCardComboBox->addItem(pair.first, dataMap); + QStandardItemModel *model = new QStandardItemModel(pairList.size(), 1, bannerCardComboBox); + + int row = 0; + for (const auto &pair : pairList) { + QStandardItem *item = new QStandardItem(pair.first); + item->setData(QVariant::fromValue(pair), Qt::UserRole); + model->setItem(row++, 0, item); } + bannerCardComboBox->setModel(model); + // Try to restore the previous selection by finding the currentText int restoredIndex = bannerCardComboBox->findText(currentText); if (restoredIndex != -1) { @@ -251,15 +255,16 @@ void DeckPreviewWidget::updateBannerCardComboBox() // Restore the previous signal blocking state bannerCardComboBox->blockSignals(wasBlocked); + bannerCardComboBox->setUpdatesEnabled(true); } void DeckPreviewWidget::setBannerCard(int /* changedIndex */) { - QVariantMap itemData = bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()).toMap(); - deckLoader->setBannerCard(QPair(itemData["name"].toString(), itemData["uuid"].toString())); + QVariant itemData = bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()); + deckLoader->setBannerCard(QPair(bannerCardComboBox->currentText(), itemData.toString())); deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath)); bannerCardDisplayWidget->setCard(CardDatabaseManager::getInstance()->getCardByNameAndProviderId( - itemData["name"].toString(), itemData["uuid"].toString())); + bannerCardComboBox->currentText(), itemData.toString())); } void DeckPreviewWidget::imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index a9df635e5..8b594a9db 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -7,6 +7,8 @@ #include "../visual_deck_storage_widget.h" #include "deck_preview_deck_tags_display_widget.h" +#include +#include #include #include #include @@ -74,7 +76,22 @@ protected: bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::Wheel) { - return true; // Blocks the event + if (auto *combo = qobject_cast(obj)) { + // If popup is not open, forward event to parent scroll area + if (!combo->view()->isVisible()) { + // Try to find a scrollable parent and manually send the event + QWidget *parent = combo->parentWidget(); + while (parent) { + if (auto *scroll = qobject_cast(parent)) { + QApplication::sendEvent(scroll->viewport(), event); + return true; // Mark event as handled + } + parent = parent->parentWidget(); + } + // If no scrollable parent found, just block + return true; + } + } } return QObject::eventFilter(obj, event); } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp index 319244f85..319393775 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp @@ -90,9 +90,9 @@ void VisualDeckStorageFolderDisplayWidget::createWidgetsForFiles() &VisualDeckStorageWidget::deckLoadRequested); connect(display, &DeckPreviewWidget::openDeckEditor, visualDeckStorageWidget, &VisualDeckStorageWidget::openDeckEditor); - connect(visualDeckStorageWidget->cardSizeWidget->getSlider(), &QSlider::valueChanged, + connect(visualDeckStorageWidget->settings(), &VisualDeckStorageQuickSettingsWidget::cardSizeChanged, display->bannerCardDisplayWidget, &CardInfoPictureWidget::setScaleFactor); - display->bannerCardDisplayWidget->setScaleFactor(visualDeckStorageWidget->cardSizeWidget->getSlider()->value()); + display->bannerCardDisplayWidget->setScaleFactor(visualDeckStorageWidget->settings()->getCardSize()); allDecks.append(display); } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp new file mode 100644 index 000000000..3b9063a9b --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp @@ -0,0 +1,155 @@ +#include "visual_deck_storage_quick_settings_widget.h" + +#include "../../../../settings/cache_settings.h" +#include "visual_deck_storage_widget.h" + +#include +#include + +VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidget *parent) + : SettingsButtonWidget(parent) +{ + // show folders checkbox + showFoldersCheckBox = new QCheckBox(this); + showFoldersCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowFolders()); + connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &VisualDeckStorageQuickSettingsWidget::showFoldersChanged); + connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageShowFolders); + + // show tag filter widget checkbox + showTagFilterCheckBox = new QCheckBox(this); + showTagFilterCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowTagFilter()); + connect(showTagFilterCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &VisualDeckStorageQuickSettingsWidget::showTagFilterChanged); + connect(showTagFilterCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageShowTagFilter); + + // show tags on DeckPreviewWidget checkbox + showTagsOnDeckPreviewsCheckBox = new QCheckBox(this); + showTagsOnDeckPreviewsCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowTagsOnDeckPreviews()); + connect(showTagsOnDeckPreviewsCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &VisualDeckStorageQuickSettingsWidget::showTagsOnDeckPreviewsChanged); + connect(showTagsOnDeckPreviewsCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageShowTagsOnDeckPreviews); + + // show banner card selector checkbox + showBannerCardComboBoxCheckBox = new QCheckBox(this); + showBannerCardComboBoxCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowBannerCardComboBox()); + connect(showBannerCardComboBoxCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &VisualDeckStorageQuickSettingsWidget::showBannerCardComboBoxChanged); + connect(showBannerCardComboBoxCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageShowBannerCardComboBox); + + // search folder names checkbox + searchFolderNamesCheckBox = new QCheckBox(this); + searchFolderNamesCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageSearchFolderNames()); + connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &VisualDeckStorageQuickSettingsWidget::searchFolderNamesChanged); + connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageSearchFolderNames); + + // draw unused color identities checkbox + drawUnusedColorIdentitiesCheckBox = new QCheckBox(this); + drawUnusedColorIdentitiesCheckBox->setChecked( + SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities()); + connect(drawUnusedColorIdentitiesCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &VisualDeckStorageQuickSettingsWidget::drawUnusedColorIdentitiesChanged); + connect(drawUnusedColorIdentitiesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities); + + // color identity opacity selector + auto unusedColorIdentityOpacityWidget = new QWidget(this); + + unusedColorIdentitiesOpacityLabel = new QLabel(unusedColorIdentityOpacityWidget); + unusedColorIdentitiesOpacitySpinBox = new QSpinBox(unusedColorIdentityOpacityWidget); + + unusedColorIdentitiesOpacitySpinBox->setMinimum(0); + unusedColorIdentitiesOpacitySpinBox->setMaximum(100); + unusedColorIdentitiesOpacitySpinBox->setValue( + SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity()); + connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), this, + &VisualDeckStorageQuickSettingsWidget::unusedColorIdentitiesOpacityChanged); + connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), + &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity); + + unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox); + + auto unusedColorIdentityOpacityLayout = new QHBoxLayout(unusedColorIdentityOpacityWidget); + unusedColorIdentityOpacityLayout->setContentsMargins(11, 0, 11, 0); + unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacityLabel); + unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacitySpinBox); + + // card size slider + cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckStorageCardSize()); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, this, + &VisualDeckStorageQuickSettingsWidget::cardSizeChanged); + connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageCardSize); + + // putting everything together + this->addSettingsWidget(showFoldersCheckBox); + this->addSettingsWidget(showTagFilterCheckBox); + this->addSettingsWidget(showTagsOnDeckPreviewsCheckBox); + this->addSettingsWidget(showBannerCardComboBoxCheckBox); + this->addSettingsWidget(searchFolderNamesCheckBox); + this->addSettingsWidget(drawUnusedColorIdentitiesCheckBox); + this->addSettingsWidget(unusedColorIdentityOpacityWidget); + this->addSettingsWidget(cardSizeWidget); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, + &VisualDeckStorageQuickSettingsWidget::retranslateUi); + retranslateUi(); +} + +void VisualDeckStorageQuickSettingsWidget::retranslateUi() +{ + showFoldersCheckBox->setText(tr("Show Folders")); + showTagFilterCheckBox->setText(tr("Show Tag Filter")); + showTagsOnDeckPreviewsCheckBox->setText(tr("Show Tags On Deck Previews")); + showBannerCardComboBoxCheckBox->setText(tr("Show Banner Card Selection Option")); + searchFolderNamesCheckBox->setText(tr("Include Folder Names in Search")); + drawUnusedColorIdentitiesCheckBox->setText(tr("Draw unused Color Identities")); + unusedColorIdentitiesOpacityLabel->setText(tr("Unused Color Identities Opacity")); + unusedColorIdentitiesOpacitySpinBox->setSuffix("%"); +} + +bool VisualDeckStorageQuickSettingsWidget::getShowFolders() const +{ + return showFoldersCheckBox->isChecked(); +} + +bool VisualDeckStorageQuickSettingsWidget::getDrawUnusedColorIdentities() const +{ + return drawUnusedColorIdentitiesCheckBox->isChecked(); +} + +bool VisualDeckStorageQuickSettingsWidget::getShowBannerCardComboBox() const +{ + return showBannerCardComboBoxCheckBox->isChecked(); +} + +bool VisualDeckStorageQuickSettingsWidget::getShowTagFilter() const +{ + return showTagFilterCheckBox->isChecked(); +} + +bool VisualDeckStorageQuickSettingsWidget::getShowTagsOnDeckPreviews() const +{ + return showTagsOnDeckPreviewsCheckBox->isChecked(); +} + +bool VisualDeckStorageQuickSettingsWidget::getSearchFolderNames() const +{ + return searchFolderNamesCheckBox->isChecked(); +} + +int VisualDeckStorageQuickSettingsWidget::getUnusedColorIdentitiesOpacity() const +{ + return unusedColorIdentitiesOpacitySpinBox->value(); +} + +int VisualDeckStorageQuickSettingsWidget::getCardSize() const +{ + return cardSizeWidget->getSlider()->value(); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h new file mode 100644 index 000000000..da82b177b --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h @@ -0,0 +1,55 @@ +#ifndef VISUAL_DECK_STORAGE_QUICK_SETTINGS_WIDGET_H +#define VISUAL_DECK_STORAGE_QUICK_SETTINGS_WIDGET_H + +#include "../quick_settings/settings_button_widget.h" + +class CardSizeWidget; +class QLabel; +class QSpinBox; +class QCheckBox; + +/** + * The VDS's quick settings menu. + * Manages the widgets in the quick settings menu dropdown, as well as syncing their values with SettingsCache. + * The current values of the settings are exposed through getters and signals. + */ +class VisualDeckStorageQuickSettingsWidget : public SettingsButtonWidget +{ + Q_OBJECT + + QCheckBox *showFoldersCheckBox; + QCheckBox *drawUnusedColorIdentitiesCheckBox; + QCheckBox *showBannerCardComboBoxCheckBox; + QCheckBox *showTagFilterCheckBox; + QCheckBox *showTagsOnDeckPreviewsCheckBox; + QCheckBox *searchFolderNamesCheckBox; + QLabel *unusedColorIdentitiesOpacityLabel; + QSpinBox *unusedColorIdentitiesOpacitySpinBox; + CardSizeWidget *cardSizeWidget; + +public: + explicit VisualDeckStorageQuickSettingsWidget(QWidget *parent = nullptr); + + void retranslateUi(); + + bool getShowFolders() const; + bool getDrawUnusedColorIdentities() const; + bool getShowBannerCardComboBox() const; + bool getShowTagFilter() const; + bool getShowTagsOnDeckPreviews() const; + bool getSearchFolderNames() const; + int getUnusedColorIdentitiesOpacity() const; + int getCardSize() const; + +signals: + void showFoldersChanged(bool enabled); + void drawUnusedColorIdentitiesChanged(bool enabled); + void showBannerCardComboBoxChanged(bool enabled); + void showTagFilterChanged(bool enabled); + void showTagsOnDeckPreviewsChanged(bool enabled); + void searchFolderNamesChanged(bool enabled); + void unusedColorIdentitiesOpacityChanged(int opacity); + void cardSizeChanged(int scale); +}; + +#endif // VISUAL_DECK_STORAGE_QUICK_SETTINGS_WIDGET_H \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp index 983ab13bd..47ed6ebee 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp @@ -33,57 +33,69 @@ void VisualDeckStorageTagFilterWidget::showEvent(QShowEvent *event) void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList &deckPreviews) const { - // Collect selected tags from DeckPreviewTagDisplayWidget QStringList selectedTags; + QStringList excludedTags; + + // Collect selected and excluded tags for (DeckPreviewTagDisplayWidget *tagWidget : findChildren()) { - if (tagWidget->getSelected()) { - selectedTags.append(tagWidget->getTagName()); + switch (tagWidget->getState()) { + case TagState::Selected: + selectedTags.append(tagWidget->getTagName()); + break; + case TagState::Excluded: + excludedTags.append(tagWidget->getTagName()); + break; + default: + break; } } - // If no tags are selected, set all decks as visible - if (selectedTags.isEmpty()) { + // If no tags are selected or excluded, show all + if (selectedTags.isEmpty() && excludedTags.isEmpty()) { for (DeckPreviewWidget *deckPreview : deckPreviews) { deckPreview->filteredByTags = false; } return; } - // Filter DeckPreviewWidgets that contain all of the selected tags - QList filteredDecks; for (DeckPreviewWidget *deckPreview : deckPreviews) { QStringList deckTags = deckPreview->deckLoader->getTags(); - // Check if all selectedTags are in deckTags - bool allTagsPresent = std::all_of(selectedTags.begin(), selectedTags.end(), + bool hasAllSelected = std::all_of(selectedTags.begin(), selectedTags.end(), [&deckTags](const QString &tag) { return deckTags.contains(tag); }); - deckPreview->filteredByTags = !allTagsPresent; + bool hasAnyExcluded = std::any_of(excludedTags.begin(), excludedTags.end(), + [&deckTags](const QString &tag) { return deckTags.contains(tag); }); + + // Filter out if any excluded tag is present or if any selected tag is missing + deckPreview->filteredByTags = !(hasAllSelected && !hasAnyExcluded); } } void VisualDeckStorageTagFilterWidget::refreshTags() { - QStringList allTags = gatherAllTags(); - removeTagsNotInList(gatherAllTags()); - addTagsIfNotPresent(gatherAllTags()); + QSet allTags = gatherAllTags(); + removeTagsNotInList(allTags); + addTagsIfNotPresent(allTags); sortTags(); } -void VisualDeckStorageTagFilterWidget::removeTagsNotInList(const QStringList &tags) +void VisualDeckStorageTagFilterWidget::removeTagsNotInList(const QSet &tags) { - // Iterate through all DeckPreviewTagDisplayWidgets + auto *flowWidget = findChild(); + for (DeckPreviewTagDisplayWidget *tagWidget : findChildren()) { - // If the tag is not in the provided tags list, remove the widget - if (!tags.contains(tagWidget->getTagName())) { - auto *flowWidget = findChild(); + const QString &tagName = tagWidget->getTagName(); + + // Keep the tag widget if it is either selected or excluded + if (!tags.contains(tagName) && tagWidget->getState() == TagState::NotSelected) { flowWidget->removeWidget(tagWidget); - tagWidget->deleteLater(); // Safely delete the widget + tagWidget->deleteLater(); } } } -void VisualDeckStorageTagFilterWidget::addTagsIfNotPresent(const QStringList &tags) +void VisualDeckStorageTagFilterWidget::addTagsIfNotPresent(const QSet &tags) { for (const QString &tag : tags) { addTagIfNotPresent(tag); @@ -136,20 +148,22 @@ void VisualDeckStorageTagFilterWidget::sortTags() } } -QStringList VisualDeckStorageTagFilterWidget::gatherAllTags() +QSet VisualDeckStorageTagFilterWidget::gatherAllTags() const { - QStringList allTags; + QSet allTags; QList deckWidgets = parent->findChildren(); for (DeckPreviewWidget *widget : deckWidgets) { if (widget->checkVisibility()) { - allTags << widget->deckLoader->getTags(); + for (const QString &tag : widget->deckLoader->getTags()) { + allTags.insert(tag); + } } } return allTags; } -QStringList VisualDeckStorageTagFilterWidget::getAllKnownTags() +QStringList VisualDeckStorageTagFilterWidget::getAllKnownTags() const { QStringList allTags; diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h index a8726790e..3198e9769 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h @@ -10,16 +10,18 @@ class VisualDeckStorageTagFilterWidget : public QWidget { Q_OBJECT -public: - explicit VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent); - QStringList gatherAllTags(); - void filterDecksBySelectedTags(const QList &deckPreviews) const; - void removeTagsNotInList(const QStringList &tags); - void addTagsIfNotPresent(const QStringList &tags); + VisualDeckStorageWidget *parent; + + QSet gatherAllTags() const; + void removeTagsNotInList(const QSet &tags); + void addTagsIfNotPresent(const QSet &tags); void addTagIfNotPresent(const QString &tag); void sortTags(); - QStringList getAllKnownTags(); - VisualDeckStorageWidget *parent; + +public: + explicit VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent); + QStringList getAllKnownTags() const; + void filterDecksBySelectedTags(const QList &deckPreviews) const; public slots: void refreshTags(); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp index 4e879bd65..e82f0c8d6 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -41,79 +41,13 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare refreshButton->setFixedSize(32, 32); connect(refreshButton, &QPushButton::clicked, this, &VisualDeckStorageWidget::refreshIfPossible); - // quick settings menu - showFoldersCheckBox = new QCheckBox(this); - showFoldersCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowFolders()); - connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &VisualDeckStorageWidget::updateShowFolders); - connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageShowFolders); - - tagFilterVisibilityCheckBox = new QCheckBox(this); - tagFilterVisibilityCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowTagFilter()); - connect(tagFilterVisibilityCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + quickSettingsWidget = new VisualDeckStorageQuickSettingsWidget(this); + connect(quickSettingsWidget, &VisualDeckStorageQuickSettingsWidget::showFoldersChanged, this, + &VisualDeckStorageWidget::updateShowFolders); + connect(quickSettingsWidget, &VisualDeckStorageQuickSettingsWidget::showTagFilterChanged, this, &VisualDeckStorageWidget::updateTagsVisibility); - connect(tagFilterVisibilityCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageShowTagFilter); - - tagsOnWidgetsVisibilityCheckBox = new QCheckBox(this); - tagsOnWidgetsVisibilityCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowTagsOnDeckPreviews()); - connect(tagsOnWidgetsVisibilityCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageShowTagsOnDeckPreviews); - - drawUnusedColorIdentitiesCheckBox = new QCheckBox(this); - drawUnusedColorIdentitiesCheckBox->setChecked( - SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities()); - connect(drawUnusedColorIdentitiesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities); - - bannerCardComboBoxVisibilityCheckBox = new QCheckBox(this); - bannerCardComboBoxVisibilityCheckBox->setChecked( - SettingsCache::instance().getVisualDeckStorageShowBannerCardComboBox()); - connect(bannerCardComboBoxVisibilityCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageShowBannerCardComboBox); - - searchFolderNamesCheckBox = new QCheckBox(this); - searchFolderNamesCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageSearchFolderNames()); - connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + connect(quickSettingsWidget, &VisualDeckStorageQuickSettingsWidget::searchFolderNamesChanged, this, &VisualDeckStorageWidget::updateSearchFilter); - connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageSearchFolderNames); - - // color identity opacity selector - auto unusedColorIdentityOpacityWidget = new QWidget(this); - - unusedColorIdentitiesOpacityLabel = new QLabel(unusedColorIdentityOpacityWidget); - unusedColorIdentitiesOpacitySpinBox = new QSpinBox(unusedColorIdentityOpacityWidget); - - unusedColorIdentitiesOpacitySpinBox->setMinimum(0); - unusedColorIdentitiesOpacitySpinBox->setMaximum(100); - unusedColorIdentitiesOpacitySpinBox->setValue( - SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity()); - connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), - &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity); - - unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox); - - unusedColorIdentitiesOpacitySpinBox->setValue( - SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity()); - - auto unusedColorIdentityOpacityLayout = new QHBoxLayout(unusedColorIdentityOpacityWidget); - unusedColorIdentityOpacityLayout->setContentsMargins(11, 0, 11, 0); - unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacityLabel); - unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacitySpinBox); - - // card size slider - cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckStorageCardSize()); - - quickSettingsWidget = new SettingsButtonWidget(this); - quickSettingsWidget->addSettingsWidget(showFoldersCheckBox); - quickSettingsWidget->addSettingsWidget(tagFilterVisibilityCheckBox); - quickSettingsWidget->addSettingsWidget(tagsOnWidgetsVisibilityCheckBox); - quickSettingsWidget->addSettingsWidget(bannerCardComboBoxVisibilityCheckBox); - quickSettingsWidget->addSettingsWidget(searchFolderNamesCheckBox); - quickSettingsWidget->addSettingsWidget(drawUnusedColorIdentitiesCheckBox); - quickSettingsWidget->addSettingsWidget(unusedColorIdentityOpacityWidget); - quickSettingsWidget->addSettingsWidget(cardSizeWidget); searchAndSortLayout->addWidget(deckPreviewColorIdentityFilterWidget); searchAndSortLayout->addWidget(sortWidget); @@ -186,14 +120,16 @@ void VisualDeckStorageWidget::retranslateUi() { databaseLoadIndicator->setText(tr("Loading database ...")); - showFoldersCheckBox->setText(tr("Show Folders")); - tagFilterVisibilityCheckBox->setText(tr("Show Tag Filter")); - tagsOnWidgetsVisibilityCheckBox->setText(tr("Show Tags On Deck Previews")); - bannerCardComboBoxVisibilityCheckBox->setText(tr("Show Banner Card Selection Option")); - searchFolderNamesCheckBox->setText(tr("Include Folder Names in Search")); - drawUnusedColorIdentitiesCheckBox->setText(tr("Draw unused Color Identities")); - unusedColorIdentitiesOpacityLabel->setText(tr("Unused Color Identities Opacity")); - unusedColorIdentitiesOpacitySpinBox->setSuffix("%"); + refreshButton->setToolTip(tr("Refresh loaded files")); + quickSettingsWidget->setToolTip(tr("Visual Deck Storage Settings")); +} + +/** + * Gets a const pointer to the quick settings so that the values can be accessed. + */ +const VisualDeckStorageQuickSettingsWidget *VisualDeckStorageWidget::settings() const +{ + return quickSettingsWidget; } /** @@ -210,7 +146,7 @@ void VisualDeckStorageWidget::reapplySortAndFilters() void VisualDeckStorageWidget::createRootFolderWidget() { folderWidget = new VisualDeckStorageFolderDisplayWidget(this, this, SettingsCache::instance().getDeckPath(), false, - showFoldersCheckBox->isChecked()); + quickSettingsWidget->getShowFolders()); scrollArea->setWidget(folderWidget); // this automatically destroys the old folderWidget scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width()); @@ -261,7 +197,7 @@ void VisualDeckStorageWidget::updateSearchFilter() { if (folderWidget) { searchWidget->filterWidgets(folderWidget->findChildren(), searchWidget->getSearchText(), - searchFolderNamesCheckBox->isChecked()); + quickSettingsWidget->getSearchFolderNames()); folderWidget->updateVisibility(); } } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h index b52086a4f..755243fdf 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -8,6 +8,7 @@ #include "deck_preview/deck_preview_color_identity_filter_widget.h" #include "deck_preview/deck_preview_widget.h" #include "visual_deck_storage_folder_display_widget.h" +#include "visual_deck_storage_quick_settings_widget.h" #include "visual_deck_storage_search_widget.h" #include "visual_deck_storage_sort_widget.h" #include "visual_deck_storage_tag_filter_widget.h" @@ -29,10 +30,11 @@ public: void refreshIfPossible(); void retranslateUi(); - CardSizeWidget *cardSizeWidget; VisualDeckStorageTagFilterWidget *tagFilterWidget; bool deckPreviewSelectionAnimationEnabled; + const VisualDeckStorageQuickSettingsWidget *settings() const; + public slots: void createRootFolderWidget(); // Refresh the display of cards based on the current sorting option void updateShowFolders(bool enabled); @@ -60,15 +62,7 @@ private: VisualDeckStorageSearchWidget *searchWidget; DeckPreviewColorIdentityFilterWidget *deckPreviewColorIdentityFilterWidget; QToolButton *refreshButton; - SettingsButtonWidget *quickSettingsWidget; - QCheckBox *showFoldersCheckBox; - QCheckBox *drawUnusedColorIdentitiesCheckBox; - QCheckBox *bannerCardComboBoxVisibilityCheckBox; - QCheckBox *tagFilterVisibilityCheckBox; - QCheckBox *tagsOnWidgetsVisibilityCheckBox; - QCheckBox *searchFolderNamesCheckBox; - QLabel *unusedColorIdentitiesOpacityLabel; - QSpinBox *unusedColorIdentitiesOpacitySpinBox; + VisualDeckStorageQuickSettingsWidget *quickSettingsWidget; QScrollArea *scrollArea; VisualDeckStorageFolderDisplayWidget *folderWidget; diff --git a/cockatrice/src/deck/deck_loader.cpp b/cockatrice/src/deck/deck_loader.cpp index 1c7136bf5..d0c0f5239 100644 --- a/cockatrice/src/deck/deck_loader.cpp +++ b/cockatrice/src/deck/deck_loader.cpp @@ -239,54 +239,33 @@ static QString getDomainForWebsite(DeckLoader::DecklistWebsite website) } } -// This struct is here to support the forEachCard function call, defined in decklist. It -// requires a function to be called for each card, and passes an inner node and a card for -// each card in the decklist. -struct FormatDeckListForExport +/** + * Converts the card to the String that represents it in the decklist export + */ +static QString toDecklistExportString(const DecklistCardNode *card) { - // Create refrences for the strings that will be passed in. - QString &mainBoardCards; - QString &sideBoardCards; - // create main operator for struct, allowing the foreachcard to work. - FormatDeckListForExport(QString &_mainBoardCards, QString &_sideBoardCards) - : mainBoardCards(_mainBoardCards), sideBoardCards(_sideBoardCards){}; + QString cardString; + // Get the number of cards and add the card name + cardString += QString::number(card->getNumber()); + // Add a space between card num and name + cardString += "%20"; + // Add card name + cardString += card->getName(); - void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) const - { - // Get the card name - CardInfoPtr dbCard = CardDatabaseManager::getInstance()->getCard(card->getName()); - if (!dbCard || dbCard->getIsToken()) { - // If it's a token, we don't care about the card. - return; - } - - // Check if it's a sideboard card. - if (node->getName() == DECK_ZONE_SIDE) { - // Get the number of cards and add the card name - sideBoardCards += QString::number(card->getNumber()); - // Add a space between card num and name - sideBoardCards += "%20"; - // Add card name - sideBoardCards += card->getName(); - // Add a return at the end of the card - sideBoardCards += "%0A"; - } else // If it's a mainboard card, do the same thing, but for the mainboard card string - { - mainBoardCards += QString::number(card->getNumber()); - mainBoardCards += "%20"; - mainBoardCards += card->getName(); - if (!card->getCardSetShortName().isNull()) { - mainBoardCards += "%20"; - mainBoardCards += "(" + card->getCardSetShortName() + ")"; - } - if (!card->getCardCollectorNumber().isNull()) { - mainBoardCards += "%20"; - mainBoardCards += card->getCardCollectorNumber(); - } - mainBoardCards += "%0A"; - } + if (!card->getCardSetShortName().isNull()) { + cardString += "%20"; + cardString += "(" + card->getCardSetShortName() + ")"; } -}; + if (!card->getCardCollectorNumber().isNull()) { + cardString += "%20"; + cardString += card->getCardCollectorNumber(); + } + + // Add a return at the end of the card + cardString += "%0A"; + + return cardString; +} /** * Export deck to decklist function, called to format the deck in a way to be sent to a server @@ -298,8 +277,25 @@ QString DeckLoader::exportDeckToDecklist(DecklistWebsite website) QString deckString = "https://" + getDomainForWebsite(website) + "/?"; // Create two strings to pass to function QString mainBoardCards, sideBoardCards; - // Set up the struct to call. - FormatDeckListForExport formatDeckListForExport(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::getInstance()->getCard(card->getName()); + if (!dbCard || dbCard->getIsToken()) { + // If it's a token, we don't care about the card. + return; + } + + // 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 forEachCard(formatDeckListForExport); // Remove the extra return at the end of the last cards @@ -316,17 +312,12 @@ QString DeckLoader::exportDeckToDecklist(DecklistWebsite website) 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. -struct SetProviderId +/** + * Sets the providerId on each card in the decklist based on its set name and collector number. + */ +void DeckLoader::resolveSetNameAndNumberToProviderID() { - // Main operator for struct, allowing the foreachcard to work. - SetProviderId() - { - } - - void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const - { + auto setProviderId = [](const auto node, const auto card) { Q_UNUSED(node); // Retrieve the providerId based on setName and collectorNumber QString providerId = @@ -336,50 +327,23 @@ struct SetProviderId // Set the providerId on the card card->setCardProviderId(providerId); - } -}; + }; -/** - * This function iterates through each card in the decklist and sets the providerId - * on each card based on its set name and collector number. - */ -void DeckLoader::resolveSetNameAndNumberToProviderID() -{ - // Set up the struct to call. - SetProviderId setProviderId; - - // Call the forEachCard method for each card in the deck 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 ClearSetNameAndNumber -{ - // Main operator for struct, allowing the foreachcard to work. - ClearSetNameAndNumber() - { - } - - void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const - { - Q_UNUSED(node); - // Set the providerId on the card - card->setCardSetShortName(nullptr); - card->setCardCollectorNumber(nullptr); - } -}; - /** - * This function iterates through each card in the decklist and sets the providerId - * on each card based on its set name and collector number. + * Clears the set name and numbers on each card in the decklist. */ void DeckLoader::clearSetNamesAndNumbers() { - // Set up the struct to call. - ClearSetNameAndNumber clearSetNameAndNumber; + auto clearSetNameAndNumber = [](const auto node, auto card) { + Q_UNUSED(node) + // Set the providerId on the card + card->setCardSetShortName(nullptr); + card->setCardCollectorNumber(nullptr); + }; - // Call the forEachCard method for each card in the deck forEachCard(clearSetNameAndNumber); } diff --git a/cockatrice/src/deck/deck_stats_interface.cpp b/cockatrice/src/deck/deck_stats_interface.cpp index dc2ab14df..7cfd69d40 100644 --- a/cockatrice/src/deck/deck_stats_interface.cpp +++ b/cockatrice/src/deck/deck_stats_interface.cpp @@ -67,26 +67,15 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck) manager->post(request, data); } -struct CopyIfNotAToken +void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination) { - CardDatabase &cardDatabase; - DeckList &destination; - - CopyIfNotAToken(CardDatabase &_cardDatabase, DeckList &_destination) - : cardDatabase(_cardDatabase), destination(_destination){}; - - void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) const - { + auto copyIfNotAToken = [this, &destination](const auto node, const auto card) { CardInfoPtr dbCard = cardDatabase.getCard(card->getName()); if (dbCard && !dbCard->getIsToken()) { DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName()); addedCard->setNumber(card->getNumber()); } - } -}; + }; -void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination) -{ - CopyIfNotAToken copyIfNotAToken(cardDatabase, destination); source.forEachCard(copyIfNotAToken); } diff --git a/cockatrice/src/dialogs/dlg_create_token.cpp b/cockatrice/src/dialogs/dlg_create_token.cpp index 7b3db2773..14adaa48a 100644 --- a/cockatrice/src/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/dialogs/dlg_create_token.cpp @@ -63,6 +63,9 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa destroyCheckBox = new QCheckBox(tr("&Destroy token when it leaves the table")); destroyCheckBox->setChecked(true); + faceDownCheckBox = new QCheckBox(tr("Create face-down (Only hides name)")); + connect(faceDownCheckBox, &QCheckBox::toggled, this, &DlgCreateToken::faceDownCheckBoxToggled); + QGridLayout *grid = new QGridLayout; grid->addWidget(nameLabel, 0, 0); grid->addWidget(nameEdit, 0, 1); @@ -73,6 +76,7 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa grid->addWidget(annotationLabel, 3, 0); grid->addWidget(annotationEdit, 3, 1); grid->addWidget(destroyCheckBox, 4, 0, 1, 2); + grid->addWidget(faceDownCheckBox, 5, 0, 1, 2); QGroupBox *tokenDataGroupBox = new QGroupBox(tr("Token data")); tokenDataGroupBox->setLayout(grid); @@ -155,6 +159,21 @@ void DlgCreateToken::closeEvent(QCloseEvent *event) SettingsCache::instance().setTokenDialogGeometry(saveGeometry()); } +void DlgCreateToken::faceDownCheckBoxToggled(bool checked) +{ + if (checked) { + colorEdit->setCurrentIndex(6); + colorEdit->setEnabled(false); + ptEdit->clear(); + ptEdit->clearFocus(); + ptEdit->setEnabled(false); + } else { + colorEdit->setEnabled(true); + ptEdit->setEnabled(true); + annotationEdit->setEnabled(true); + } +} + void DlgCreateToken::tokenSelectionChanged(const QModelIndex ¤t, const QModelIndex & /*previous*/) { const QModelIndex realIndex = cardDatabaseDisplayModel->mapToSource(current); @@ -230,5 +249,6 @@ TokenInfo DlgCreateToken::getTokenInfo() const .color = colorEdit->itemData(colorEdit->currentIndex()).toString(), .pt = ptEdit->text(), .annotation = annotationEdit->text(), - .destroy = destroyCheckBox->isChecked()}; + .destroy = destroyCheckBox->isChecked(), + .faceDown = faceDownCheckBox->isChecked()}; } diff --git a/cockatrice/src/dialogs/dlg_create_token.h b/cockatrice/src/dialogs/dlg_create_token.h index 373e094f1..cd1b48c81 100644 --- a/cockatrice/src/dialogs/dlg_create_token.h +++ b/cockatrice/src/dialogs/dlg_create_token.h @@ -24,6 +24,7 @@ struct TokenInfo QString pt; QString annotation; bool destroy = true; + bool faceDown = false; }; class DlgCreateToken : public QDialog @@ -36,6 +37,7 @@ public: protected: void closeEvent(QCloseEvent *event) override; private slots: + void faceDownCheckBoxToggled(bool checked); void tokenSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); void updateSearch(const QString &search); void actChooseTokenFromAll(bool checked); @@ -51,6 +53,7 @@ private: QComboBox *colorEdit; QLineEdit *nameEdit, *ptEdit, *annotationEdit; QCheckBox *destroyCheckBox; + QCheckBox *faceDownCheckBox; QRadioButton *chooseTokenFromAllRadioButton, *chooseTokenFromDeckRadioButton; CardInfoPictureWidget *pic; QTreeView *chooseTokenView; diff --git a/cockatrice/src/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/dialogs/dlg_edit_tokens.cpp index 1248ee403..e51bbf369 100644 --- a/cockatrice/src/dialogs/dlg_edit_tokens.cpp +++ b/cockatrice/src/dialogs/dlg_edit_tokens.cpp @@ -161,7 +161,7 @@ void DlgEditTokens::actAddToken() } } - QString setName = CardDatabase::TOKENS_SETNAME; + QString setName = CardSet::TOKENS_SETNAME; CardInfoPerSetMap sets; sets[setName].append(CardInfoPerSet(databaseModel->getDatabase()->getSet(setName))); CardInfoPtr card = CardInfo::newInstance(name, "", true, QVariantHash(), QList(), diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index 502662c14..63901325f 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -4,6 +4,7 @@ #include "../client/network/release_channel.h" #include "../client/network/spoiler_background_updater.h" #include "../client/sound_engine.h" +#include "../client/tabs/tab_supervisor.h" #include "../client/ui/picture_loader/picture_loader.h" #include "../client/ui/theme_manager.h" #include "../deck/custom_line_edit.h" @@ -629,6 +630,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&closeEmptyCardViewCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setCloseEmptyCardView); + focusCardViewSearchBarCheckBox.setChecked(SettingsCache::instance().getFocusCardViewSearchBar()); + connect(&focusCardViewSearchBarCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setFocusCardViewSearchBar); + annotateTokensCheckBox.setChecked(SettingsCache::instance().getAnnotateTokens()); connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setAnnotateTokens); @@ -642,8 +647,9 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0); generalGrid->addWidget(&playToStackCheckBox, 2, 0); generalGrid->addWidget(&closeEmptyCardViewCheckBox, 3, 0); - generalGrid->addWidget(&annotateTokensCheckBox, 4, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 5, 0); + generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 4, 0); + generalGrid->addWidget(&annotateTokensCheckBox, 5, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 6, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -699,12 +705,20 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() index == visualDeckStoragePromptForConversionIndexAlways); }); + defaultDeckEditorTypeSelector.addItem(""); // these will be set in retranslateUI + defaultDeckEditorTypeSelector.addItem(""); + defaultDeckEditorTypeSelector.setCurrentIndex(SettingsCache::instance().getDefaultDeckEditorType()); + connect(&defaultDeckEditorTypeSelector, QOverload::of(&QComboBox::currentIndexChanged), + &SettingsCache::instance(), &SettingsCache::setDefaultDeckEditorType); + auto *deckEditorGrid = new QGridLayout; deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0); deckEditorGrid->addWidget(&visualDeckStorageInGameCheckBox, 1, 0); deckEditorGrid->addWidget(&visualDeckStorageSelectionAnimationCheckBox, 2, 0); deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionLabel, 3, 0); deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionSelector, 3, 1); + deckEditorGrid->addWidget(&defaultDeckEditorTypeLabel, 4, 0); + deckEditorGrid->addWidget(&defaultDeckEditorTypeSelector, 4, 1); deckEditorGroupBox = new QGroupBox; deckEditorGroupBox->setLayout(deckEditorGrid); @@ -754,6 +768,7 @@ void UserInterfaceSettingsPage::retranslateUi() clickPlaysAllSelectedCheckBox.setText(tr("&Clicking plays all selected cards (instead of just the clicked card)")); playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default")); closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); + focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); notificationsGroupBox->setTitle(tr("Notifications settings")); @@ -774,6 +789,9 @@ void UserInterfaceSettingsPage::retranslateUi() tr("ask to convert to .cod")); visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexAlways, tr("always convert to .cod")); + defaultDeckEditorTypeLabel.setText(tr("Default deck editor type")); + defaultDeckEditorTypeSelector.setItemText(TabSupervisor::ClassicDeckEditor, tr("Classic Deck Editor")); + defaultDeckEditorTypeSelector.setItemText(TabSupervisor::VisualDeckEditor, tr("Visual Deck Editor")); replayGroupBox->setTitle(tr("Replay settings")); rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:")); rewindBufferingMsBox.setSuffix(" ms"); diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index 6c9b8b062..54b2cbcd8 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -147,6 +147,7 @@ private: QCheckBox clickPlaysAllSelectedCheckBox; QCheckBox playToStackCheckBox; QCheckBox closeEmptyCardViewCheckBox; + QCheckBox focusCardViewSearchBarCheckBox; QCheckBox annotateTokensCheckBox; QCheckBox useTearOffMenusCheckBox; QCheckBox tapAnimationCheckBox; @@ -155,6 +156,8 @@ private: QComboBox visualDeckStoragePromptForConversionSelector; QCheckBox visualDeckStorageInGameCheckBox; QCheckBox visualDeckStorageSelectionAnimationCheckBox; + QLabel defaultDeckEditorTypeLabel; + QComboBox defaultDeckEditorTypeSelector; QLabel rewindBufferingMsLabel; QSpinBox rewindBufferingMsBox; QGroupBox *generalGroupBox; diff --git a/cockatrice/src/dialogs/dlg_view_log.cpp b/cockatrice/src/dialogs/dlg_view_log.cpp index dc569c92c..b60a9767b 100644 --- a/cockatrice/src/dialogs/dlg_view_log.cpp +++ b/cockatrice/src/dialogs/dlg_view_log.cpp @@ -3,23 +3,40 @@ #include "../settings/cache_settings.h" #include "../utility/logger.h" +#include #include +#include +#include #include DlgViewLog::DlgViewLog(QWidget *parent) : QDialog(parent) { - logArea = new QPlainTextEdit; logArea->setReadOnly(true); auto *mainLayout = new QVBoxLayout; + mainLayout->setSpacing(3); + mainLayout->setContentsMargins(20, 20, 20, 6); + mainLayout->addWidget(logArea); + auto *bottomLayout = new QHBoxLayout; + coClearLog = new QCheckBox; coClearLog->setText(tr("Clear log when closing")); coClearLog->setChecked(SettingsCache::instance().servers().getClearDebugLogStatus(false)); connect(coClearLog, &QCheckBox::toggled, this, &DlgViewLog::actCheckBoxChanged); - mainLayout->addWidget(coClearLog); + + copyToClipboardButton = new QPushButton; + copyToClipboardButton->setText(tr("Copy to clipboard")); + copyToClipboardButton->setAutoDefault(false); + connect(copyToClipboardButton, &QPushButton::clicked, this, &DlgViewLog::actCopyToClipboard); + + bottomLayout->addWidget(coClearLog); + bottomLayout->addStretch(); + bottomLayout->addWidget(copyToClipboardButton); + + mainLayout->addLayout(bottomLayout); setLayout(mainLayout); @@ -27,7 +44,7 @@ DlgViewLog::DlgViewLog(QWidget *parent) : QDialog(parent) resize(800, 500); loadInitialLogBuffer(); - connect(&Logger::getInstance(), &Logger::logEntryAdded, this, &DlgViewLog::logEntryAdded); + connect(&Logger::getInstance(), &Logger::logEntryAdded, this, &DlgViewLog::appendLogEntry); } void DlgViewLog::actCheckBoxChanged(bool abNewValue) @@ -35,16 +52,26 @@ void DlgViewLog::actCheckBoxChanged(bool abNewValue) SettingsCache::instance().servers().setClearDebugLogStatus(abNewValue); } +void DlgViewLog::actCopyToClipboard() +{ + QApplication::clipboard()->setText(logArea->toPlainText()); +} + void DlgViewLog::loadInitialLogBuffer() { QList logBuffer = Logger::getInstance().getLogBuffer(); for (const QString &message : logBuffer) - logEntryAdded(message); + appendLogEntry(message); } -void DlgViewLog::logEntryAdded(QString message) +void DlgViewLog::appendLogEntry(const QString &message) { - logArea->appendPlainText(message); + static auto colorEscapeCodePattern = QRegularExpression("\033\\[\\d+m"); + + QString sanitizedMessage = message; + sanitizedMessage.replace(colorEscapeCodePattern, ""); + + logArea->appendPlainText(sanitizedMessage); } void DlgViewLog::closeEvent(QCloseEvent * /* event */) diff --git a/cockatrice/src/dialogs/dlg_view_log.h b/cockatrice/src/dialogs/dlg_view_log.h index 3845f0062..26d6ad5e1 100644 --- a/cockatrice/src/dialogs/dlg_view_log.h +++ b/cockatrice/src/dialogs/dlg_view_log.h @@ -19,11 +19,13 @@ protected: private: QPlainTextEdit *logArea; QCheckBox *coClearLog; + QPushButton *copyToClipboardButton; void loadInitialLogBuffer(); private slots: - void logEntryAdded(QString message); + void appendLogEntry(const QString &message); void actCheckBoxChanged(bool abNewValue); + void actCopyToClipboard(); }; #endif \ No newline at end of file diff --git a/cockatrice/src/game/cards/abstract_card_drag_item.cpp b/cockatrice/src/game/board/abstract_card_drag_item.cpp similarity index 96% rename from cockatrice/src/game/cards/abstract_card_drag_item.cpp rename to cockatrice/src/game/board/abstract_card_drag_item.cpp index 0c7acd917..a7234c3d4 100644 --- a/cockatrice/src/game/cards/abstract_card_drag_item.cpp +++ b/cockatrice/src/game/board/abstract_card_drag_item.cpp @@ -39,6 +39,8 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, prepareGeometryChange(); update(); }); + + connect(item, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater); } AbstractCardDragItem::~AbstractCardDragItem() diff --git a/cockatrice/src/game/cards/abstract_card_drag_item.h b/cockatrice/src/game/board/abstract_card_drag_item.h similarity index 100% rename from cockatrice/src/game/cards/abstract_card_drag_item.h rename to cockatrice/src/game/board/abstract_card_drag_item.h diff --git a/cockatrice/src/game/cards/abstract_card_item.cpp b/cockatrice/src/game/board/abstract_card_item.cpp similarity index 99% rename from cockatrice/src/game/cards/abstract_card_item.cpp rename to cockatrice/src/game/board/abstract_card_item.cpp index dc00aae66..0623afb07 100644 --- a/cockatrice/src/game/cards/abstract_card_item.cpp +++ b/cockatrice/src/game/board/abstract_card_item.cpp @@ -2,9 +2,9 @@ #include "../../client/ui/picture_loader/picture_loader.h" #include "../../settings/cache_settings.h" +#include "../cards/card_database.h" +#include "../cards/card_database_manager.h" #include "../game_scene.h" -#include "card_database.h" -#include "card_database_manager.h" #include #include diff --git a/cockatrice/src/game/cards/abstract_card_item.h b/cockatrice/src/game/board/abstract_card_item.h similarity index 98% rename from cockatrice/src/game/cards/abstract_card_item.h rename to cockatrice/src/game/board/abstract_card_item.h index 48f890701..94de7596a 100644 --- a/cockatrice/src/game/cards/abstract_card_item.h +++ b/cockatrice/src/game/board/abstract_card_item.h @@ -1,8 +1,8 @@ #ifndef ABSTRACTCARDITEM_H #define ABSTRACTCARDITEM_H -#include "../board/arrow_target.h" -#include "card_info.h" +#include "../cards/card_info.h" +#include "arrow_target.h" class Player; diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index 2be8fbdd8..4caebdda0 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -3,10 +3,10 @@ #include "../../settings/cache_settings.h" #include "../cards/card_info.h" -#include "../cards/card_item.h" #include "../player/player.h" #include "../player/player_target.h" #include "../zones/card_zone.h" +#include "card_item.h" #include "color.h" #include "pb/command_attach_card.pb.h" #include "pb/command_create_arrow.pb.h" diff --git a/cockatrice/src/game/cards/card_drag_item.cpp b/cockatrice/src/game/board/card_drag_item.cpp similarity index 88% rename from cockatrice/src/game/cards/card_drag_item.cpp rename to cockatrice/src/game/board/card_drag_item.cpp index d21e9168b..ab4431958 100644 --- a/cockatrice/src/game/cards/card_drag_item.cpp +++ b/cockatrice/src/game/board/card_drag_item.cpp @@ -47,10 +47,25 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos) cursorZone = zoneViewZone; else if (cardZone) cursorZone = cardZone; - if (!cursorZone) - return; + + // Always update the current zone, even if its null, to cancel the drag + // instead of dropping cards into an non-intuitive location. currentZone = cursorZone; + if (!cursorZone) { + // Avoid the cards getting stuck visually when not over + // any zone. + QPointF newPos = cursorScenePos - hotSpot; + + if (newPos != pos()) { + for (int i = 0; i < childDrags.size(); i++) + childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); + setPos(newPos); + } + + return; + } + QPointF zonePos = currentZone->scenePos(); QPointF cursorPosInZone = cursorScenePos - zonePos; diff --git a/cockatrice/src/game/cards/card_drag_item.h b/cockatrice/src/game/board/card_drag_item.h similarity index 100% rename from cockatrice/src/game/cards/card_drag_item.h rename to cockatrice/src/game/board/card_drag_item.h diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/board/card_item.cpp similarity index 97% rename from cockatrice/src/game/cards/card_item.cpp rename to cockatrice/src/game/board/card_item.cpp index 40fa8d119..55df7a477 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -2,14 +2,14 @@ #include "../../client/tabs/tab_game.h" #include "../../settings/cache_settings.h" -#include "../board/arrow_item.h" +#include "../cards/card_info.h" #include "../game_scene.h" #include "../player/player.h" #include "../zones/card_zone.h" #include "../zones/table_zone.h" #include "../zones/view_zone.h" +#include "arrow_item.h" #include "card_drag_item.h" -#include "card_info.h" #include "pb/serverinfo_card.pb.h" #include @@ -336,8 +336,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < 2 * QApplication::startDragDistance()) return; - if (zone->getIsView()) { - const ZoneViewZone *view = static_cast(zone); + if (const ZoneViewZone *view = qobject_cast(zone)) { if (view->getRevealZone() && !view->getWriteableRevealZone()) return; } else if (!owner->getLocalOrJudge()) @@ -394,10 +393,8 @@ void CardItem::playCard(bool faceDown) */ static bool isUnwritableRevealZone(CardZone *zone) { - if (zone && zone->getIsView()) { - if (auto *view = static_cast(zone)) { - return view->getRevealZone() && !view->getWriteableRevealZone(); - } + if (auto *view = qobject_cast(zone)) { + return view->getRevealZone() && !view->getWriteableRevealZone(); } return false; } diff --git a/cockatrice/src/game/cards/card_item.h b/cockatrice/src/game/board/card_item.h similarity index 100% rename from cockatrice/src/game/cards/card_item.h rename to cockatrice/src/game/board/card_item.h diff --git a/cockatrice/src/game/cards/card_list.cpp b/cockatrice/src/game/board/card_list.cpp similarity index 99% rename from cockatrice/src/game/cards/card_list.cpp rename to cockatrice/src/game/board/card_list.cpp index 0eda14626..0355bf6bd 100644 --- a/cockatrice/src/game/cards/card_list.cpp +++ b/cockatrice/src/game/board/card_list.cpp @@ -1,6 +1,6 @@ #include "card_list.h" -#include "card_info.h" +#include "../cards/card_info.h" #include "card_item.h" #include diff --git a/cockatrice/src/game/cards/card_list.h b/cockatrice/src/game/board/card_list.h similarity index 100% rename from cockatrice/src/game/cards/card_list.h rename to cockatrice/src/game/board/card_list.h diff --git a/cockatrice/src/game/cards/card_database.cpp b/cockatrice/src/game/cards/card_database.cpp index 7181ffd06..396a32a10 100644 --- a/cockatrice/src/game/cards/card_database.cpp +++ b/cockatrice/src/game/cards/card_database.cpp @@ -16,8 +16,6 @@ #include #include -const char *CardDatabase::TOKENS_SETNAME = "TK"; - CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded) { qRegisterMetaType("CardInfoPtr"); @@ -386,9 +384,10 @@ CardInfoPerSet CardDatabase::getSpecificSetForCard(const QString &cardName, for (const auto &cardInfoPerSetList : setMap) { for (auto &cardInfoForSet : cardInfoPerSetList) { - if (cardInfoForSet.getPtr()->getShortName() == setShortName && - cardInfoForSet.getProperty("num") == collectorNumber) { - return cardInfoForSet; + if (cardInfoForSet.getPtr()->getShortName() == setShortName) { + if (cardInfoForSet.getProperty("num") == collectorNumber || collectorNumber.isEmpty()) { + return cardInfoForSet; + } } } } @@ -559,16 +558,15 @@ void CardDatabase::notifyEnabledSetsChanged() bool CardDatabase::saveCustomTokensToFile() { - QString fileName = - SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardDatabase::TOKENS_SETNAME + ".xml"; + QString fileName = SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardSet::TOKENS_SETNAME + ".xml"; SetNameMap tmpSets; - CardSetPtr customTokensSet = getSet(CardDatabase::TOKENS_SETNAME); - tmpSets.insert(CardDatabase::TOKENS_SETNAME, customTokensSet); + CardSetPtr customTokensSet = getSet(CardSet::TOKENS_SETNAME); + tmpSets.insert(CardSet::TOKENS_SETNAME, customTokensSet); CardNameMap tmpCards; for (const CardInfoPtr &card : cards) { - if (card->getSets().contains(CardDatabase::TOKENS_SETNAME)) { + if (card->getSets().contains(CardSet::TOKENS_SETNAME)) { tmpCards.insert(card->getName(), card); } } diff --git a/cockatrice/src/game/cards/card_database.h b/cockatrice/src/game/cards/card_database.h index 2de5999ee..7e4a1f2c7 100644 --- a/cockatrice/src/game/cards/card_database.h +++ b/cockatrice/src/game/cards/card_database.h @@ -61,8 +61,6 @@ private: *removeCardMutex = new QBasicMutex(); public: - static const char *TOKENS_SETNAME; - explicit CardDatabase(QObject *parent = nullptr); ~CardDatabase() override; void clear(); diff --git a/cockatrice/src/game/cards/card_database_model.cpp b/cockatrice/src/game/cards/card_database_model.cpp index 31ff98263..93bb7f02a 100644 --- a/cockatrice/src/game/cards/card_database_model.cpp +++ b/cockatrice/src/game/cards/card_database_model.cpp @@ -387,7 +387,7 @@ TokenEditModel::TokenEditModel(QObject *parent) : CardDatabaseDisplayModel(paren bool TokenEditModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const { CardInfoPtr info = static_cast(sourceModel())->getCard(sourceRow); - return info->getIsToken() && info->getSets().contains(CardDatabase::TOKENS_SETNAME) && rowMatchesCardName(info); + return info->getIsToken() && info->getSets().contains(CardSet::TOKENS_SETNAME) && rowMatchesCardName(info); } int TokenEditModel::rowCount(const QModelIndex &parent) const diff --git a/cockatrice/src/game/cards/card_info.cpp b/cockatrice/src/game/cards/card_info.cpp index ae55ad7af..4bd2c0c9c 100644 --- a/cockatrice/src/game/cards/card_info.cpp +++ b/cockatrice/src/game/cards/card_info.cpp @@ -11,6 +11,8 @@ #include #include +const char *CardSet::TOKENS_SETNAME = "TK"; + CardSet::CardSet(const QString &_shortName, const QString &_longName, const QString &_setType, @@ -289,8 +291,12 @@ QString CardInfo::getCorrectedName() const void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info) { - _set->append(smartThis); - sets[_set->getShortName()].append(_info); + if (!_set->contains(smartThis)) { + _set->append(smartThis); + } + if (!sets[_set->getShortName()].contains(_info)) { + sets[_set->getShortName()].append(_info); + } refreshCachedSetNames(); } diff --git a/cockatrice/src/game/cards/card_info.h b/cockatrice/src/game/cards/card_info.h index 76397588f..bdbebcd72 100644 --- a/cockatrice/src/game/cards/card_info.h +++ b/cockatrice/src/game/cards/card_info.h @@ -43,6 +43,8 @@ public: PriorityLowest = 100, }; + static const char *TOKENS_SETNAME; + private: QString shortName, longName; unsigned int sortKey; diff --git a/cockatrice/src/game/deckview/deck_view.h b/cockatrice/src/game/deckview/deck_view.h index 777298714..6fa47766f 100644 --- a/cockatrice/src/game/deckview/deck_view.h +++ b/cockatrice/src/game/deckview/deck_view.h @@ -1,7 +1,7 @@ #ifndef DECKVIEW_H #define DECKVIEW_H -#include "../../game/cards/abstract_card_drag_item.h" +#include "../board/abstract_card_drag_item.h" #include "pb/move_card_to_zone.pb.h" #include diff --git a/cockatrice/src/game/filters/filter_string.cpp b/cockatrice/src/game/filters/filter_string.cpp index 7edfb0d41..01a7b6be9 100644 --- a/cockatrice/src/game/filters/filter_string.cpp +++ b/cockatrice/src/game/filters/filter_string.cpp @@ -10,15 +10,15 @@ peg::parser search(R"( Start <- QueryPartList ~ws <- [ ]+ -QueryPartList <- ComplexQueryPart ( ws ("and" ws)? ComplexQueryPart)* ws* +QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws* -ComplexQueryPart <- SomewhatComplexQueryPart ws $or<[oO][rR]> ws ComplexQueryPart / SomewhatComplexQueryPart +ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart QueryPart <- NotQuery / SetQuery / RarityQuery / CMCQuery / FormatQuery / PowerQuery / ToughnessQuery / ColorQuery / TypeQuery / OracleQuery / FieldQuery / GenericQuery -NotQuery <- ('not' ws/'-') SomewhatComplexQueryPart +NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart SetQuery <- ('e'/'set') [:] FlexStringValue OracleQuery <- 'o' [:] RegexString diff --git a/cockatrice/src/game/filters/filter_tree.cpp b/cockatrice/src/game/filters/filter_tree.cpp index 170fb35d8..4e65fc06e 100644 --- a/cockatrice/src/game/filters/filter_tree.cpp +++ b/cockatrice/src/game/filters/filter_tree.cpp @@ -531,6 +531,31 @@ void FilterTree::removeFiltersByAttr(CardFilter::Attr filterType) } } +void FilterTree::removeFilter(const CardFilter *toRemove) +{ + for (int i = childNodes.size() - 1; i >= 0; --i) { + auto *logicMap = dynamic_cast(childNodes.at(i)); + if (!logicMap || logicMap->attr != toRemove->attr()) + continue; + + FilterItemList *typeList = logicMap->typeList(toRemove->type()); + if (!typeList) + continue; + + int termIdx = typeList->termIndex(toRemove->term()); + if (termIdx != -1) { + typeList->deleteAt(termIdx); + emit typeList->nodeChanged(); + if (typeList->childCount() == 0) { + int logicIndex = logicMap->childIndex(typeList); + if (logicIndex != -1) { + logicMap->deleteAt(logicIndex); + } + } + } + } +} + void FilterTree::clear() { while (childCount() > 0) { diff --git a/cockatrice/src/game/filters/filter_tree.h b/cockatrice/src/game/filters/filter_tree.h index 895e1c1f7..394e98e7b 100644 --- a/cockatrice/src/game/filters/filter_tree.h +++ b/cockatrice/src/game/filters/filter_tree.h @@ -270,6 +270,7 @@ public: bool acceptsCard(CardInfoPtr info) const; void removeFiltersByAttr(CardFilter::Attr filterType); + void removeFilter(const CardFilter *toRemove); void clear(); }; diff --git a/cockatrice/src/game/filters/filter_tree_model.cpp b/cockatrice/src/game/filters/filter_tree_model.cpp index ec98f7c98..c4ce3aa35 100644 --- a/cockatrice/src/game/filters/filter_tree_model.cpp +++ b/cockatrice/src/game/filters/filter_tree_model.cpp @@ -78,6 +78,13 @@ void FilterTreeModel::addFilter(const CardFilter *f) emit layoutChanged(); } +void FilterTreeModel::removeFilter(const CardFilter *f) +{ + emit layoutAboutToBeChanged(); + fTree->removeFilter(f); + emit layoutChanged(); +} + void FilterTreeModel::clearFiltersOfType(CardFilter::Attr filterType) { emit layoutAboutToBeChanged(); diff --git a/cockatrice/src/game/filters/filter_tree_model.h b/cockatrice/src/game/filters/filter_tree_model.h index 4e98aa02a..89547e7a2 100644 --- a/cockatrice/src/game/filters/filter_tree_model.h +++ b/cockatrice/src/game/filters/filter_tree_model.h @@ -17,6 +17,7 @@ private: public slots: void addFilter(const CardFilter *f); + void removeFilter(const CardFilter *f); void clearFiltersOfType(CardFilter::Attr filterType); QList getFiltersOfType(CardFilter::Attr filterType) const; QList allFilters() const; diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index afd6cc815..ba54d5b0c 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -2,7 +2,7 @@ #include "../client/ui/phases_toolbar.h" #include "../settings/cache_settings.h" -#include "cards/card_item.h" +#include "board/card_item.h" #include "player/player.h" #include "zones/view_zone.h" #include "zones/view_zone_widget.h" diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 26f34e977..9fff6b3df 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -9,11 +9,11 @@ #include "../../main.h" #include "../../settings/cache_settings.h" #include "../board/arrow_item.h" +#include "../board/card_item.h" +#include "../board/card_list.h" #include "../board/counter_general.h" #include "../cards/card_database.h" #include "../cards/card_database_manager.h" -#include "../cards/card_item.h" -#include "../cards/card_list.h" #include "../game_scene.h" #include "../hand_counter.h" #include "../zones/card_zone.h" @@ -124,7 +124,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T qreal avatarMargin = (counterAreaWidth + CARD_HEIGHT + 15 - playerTarget->boundingRect().width()) / 2.0; playerTarget->setPos(QPointF(avatarMargin, avatarMargin)); - auto *_deck = new PileZone(this, "deck", true, false, playerArea); + auto *_deck = addZone(new PileZone(this, "deck", true, false, playerArea)); QPointF base = QPointF(counterAreaWidth + (CARD_HEIGHT - CARD_WIDTH + 15) / 2.0, 10 + playerTarget->boundingRect().height() + 5 - (CARD_HEIGHT - CARD_WIDTH) / 2.0); _deck->setPos(base); @@ -135,22 +135,23 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T handCounter->setPos(base + QPointF(0, h + 10)); qreal h2 = handCounter->boundingRect().height(); - PileZone *grave = new PileZone(this, "grave", false, true, playerArea); + PileZone *grave = addZone(new PileZone(this, "grave", false, true, playerArea)); grave->setPos(base + QPointF(0, h + h2 + 10)); - PileZone *rfg = new PileZone(this, "rfg", false, true, playerArea); + PileZone *rfg = addZone(new PileZone(this, "rfg", false, true, playerArea)); rfg->setPos(base + QPointF(0, 2 * h + h2 + 10)); - PileZone *sb = new PileZone(this, "sb", false, false, playerArea); + PileZone *sb = addZone(new PileZone(this, "sb", false, false, playerArea)); sb->setVisible(false); - table = new TableZone(this, this); + table = addZone(new TableZone(this, "table", this)); connect(table, &TableZone::sizeChanged, this, &Player::updateBoundingRect); - stack = new StackZone(this, (int)table->boundingRect().height(), this); + stack = addZone(new StackZone(this, (int)table->boundingRect().height(), this)); - hand = new HandZone(this, _local || _judge || (_parent->getSpectator() && _parent->getSpectatorsSeeEverything()), - (int)table->boundingRect().height(), this); + hand = addZone(new HandZone(this, + _local || _judge || (_parent->getSpectator() && _parent->getSpectatorsSeeEverything()), + (int)table->boundingRect().height(), this)); connect(hand, &HandZone::cardCountChanged, handCounter, &HandCounter::updateNumber); connect(handCounter, &HandCounter::showContextMenu, hand, &HandZone::showContextMenu); @@ -399,6 +400,9 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T sbMenu->addAction(aViewSideboard); sb->setMenu(sbMenu, aViewSideboard); + mCustomZones = playerMenu->addMenu(QString()); + mCustomZones->menuAction()->setVisible(false); + aUntapAll = new QAction(this); connect(aUntapAll, &QAction::triggered, this, &Player::actUntapAll); @@ -454,6 +458,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T if (!local && !judge) { countersMenu = nullptr; sbMenu = nullptr; + mCustomZones = nullptr; aCreateAnotherToken = nullptr; createPredefinedTokenMenu = nullptr; } @@ -828,6 +833,11 @@ void Player::retranslateUi() sbMenu->setTitle(tr("&Sideboard")); libraryMenu->setTitle(tr("&Library")); countersMenu->setTitle(tr("&Counters")); + mCustomZones->setTitle(tr("C&ustom Zones")); + + for (auto aViewZone : mCustomZones->actions()) { + aViewZone->setText(tr("View custom zone '%1'").arg(aViewZone->data().toString())); + } aUntapAll->setText(tr("&Untap all permanents")); aRollDie->setText(tr("R&oll die...")); @@ -1854,6 +1864,7 @@ void Player::actCreateAnotherToken() cmd.set_pt(lastTokenInfo.pt.toStdString()); cmd.set_annotation(lastTokenInfo.annotation.toStdString()); cmd.set_destroy_on_zone_change(lastTokenInfo.destroy); + cmd.set_face_down(lastTokenInfo.faceDown); cmd.set_x(-1); cmd.set_y(lastTokenTableRow); @@ -2065,6 +2076,8 @@ void Player::createCard(const CardItem *sourceCard, break; case CardRelation::TransformInto: + // allow cards to directly transform on stack + cmd.set_zone(sourceCard->getZone()->getName() == "stack" ? "stack" : "table"); // Transform card zone changes are handled server-side cmd.set_target_zone(sourceCard->getZone()->getName().toStdString()); cmd.set_target_card_id(sourceCard->getId()); @@ -2230,10 +2243,10 @@ void Player::eventCreateToken(const Event_CreateToken &event) CardItem *card = new CardItem(this, nullptr, QString::fromStdString(event.card_name()), QString::fromStdString(event.card_provider_id()), event.card_id()); - // use db PT if not provided in event + // use db PT if not provided in event and not face-down if (!QString::fromStdString(event.pt()).isEmpty()) { card->setPT(QString::fromStdString(event.pt())); - } else { + } else if (!event.face_down()) { CardInfoPtr dbCard = card->getInfo(); if (dbCard) { card->setPT(dbCard->getPowTough()); @@ -2242,8 +2255,9 @@ void Player::eventCreateToken(const Event_CreateToken &event) card->setColor(QString::fromStdString(event.color())); card->setAnnotation(QString::fromStdString(event.annotation())); card->setDestroyOnZoneChange(event.destroy_on_zone_change()); + card->setFaceDown(event.face_down()); - emit logCreateToken(this, card->getName(), card->getPT()); + emit logCreateToken(this, card->getName(), card->getPT(), card->getFaceDown()); zone->addCard(card, true, event.x(), event.y()); } @@ -2710,19 +2724,79 @@ void Player::paint(QPainter * /*painter*/, const QStyleOptionGraphicsItem * /*op void Player::processPlayerInfo(const ServerInfo_Player &info) { + static QSet builtinZones{/* PileZones */ + "deck", "grave", "rfg", "sb", + /* TableZone */ + "table", + /* StackZone */ + "stack", + /* HandZone */ + "hand"}; clearCounters(); clearArrows(); - QMapIterator zoneIt(zones); + QMutableMapIterator zoneIt(zones); while (zoneIt.hasNext()) { zoneIt.next().value()->clearContents(); + + if (!builtinZones.contains(zoneIt.key())) { + zoneIt.remove(); + } + } + + // Can be null if we are not the local player! + if (mCustomZones) { + mCustomZones->clear(); + mCustomZones->menuAction()->setVisible(false); } const int zoneListSize = info.zone_list_size(); for (int i = 0; i < zoneListSize; ++i) { const ServerInfo_Zone &zoneInfo = info.zone_list(i); - CardZone *zone = zones.value(QString::fromStdString(zoneInfo.name()), 0); + + QString zoneName = QString::fromStdString(zoneInfo.name()); + CardZone *zone = zones.value(zoneName, 0); if (!zone) { + // Create a new CardZone if it doesn't exist + + if (zoneInfo.with_coords()) { + // Visibility not currently supported for TableZone + zone = addZone(new TableZone(this, zoneName, this)); + } else { + // Zones without coordinats are always treated as non-shufflable + // PileZones, although supporting alternate hand or stack zones + // might make sense in some scenarios. + bool contentsKnown; + + switch (zoneInfo.type()) { + case ServerInfo_Zone::PrivateZone: + contentsKnown = local || judge || (game->getSpectator() && game->getSpectatorsSeeEverything()); + break; + + case ServerInfo_Zone::PublicZone: + contentsKnown = true; + break; + + case ServerInfo_Zone::HiddenZone: + contentsKnown = false; + break; + } + + zone = addZone(new PileZone(this, zoneName, /* isShufflable */ false, contentsKnown, this)); + } + + // Non-builtin zones are hidden by default and can't be interacted + // with, except through menus. + zone->setVisible(false); + + if (mCustomZones) { + mCustomZones->menuAction()->setVisible(true); + QAction *aViewZone = mCustomZones->addAction(tr("View custom zone '%1'").arg(zoneName)); + aViewZone->setData(zoneName); + connect(aViewZone, &QAction::triggered, this, + [zoneName, this]() { static_cast(scene())->toggleZoneView(this, zoneName, -1); }); + } + continue; } @@ -2889,11 +2963,6 @@ void Player::deleteCard(CardItem *card) } } -void Player::addZone(CardZone *zone) -{ - zones.insert(zone->getName(), zone); -} - AbstractCounter *Player::addCounter(const ServerInfo_Counter &counter) { return addCounter(counter.id(), QString::fromStdString(counter.name()), @@ -3692,9 +3761,8 @@ void Player::actCardCounterTrigger() */ static bool isUnwritableRevealZone(CardZone *zone) { - if (zone && zone->getIsView()) { - auto *view = static_cast(zone); - return view && view->getRevealZone() && !view->getWriteableRevealZone(); + if (auto *view = qobject_cast(zone)) { + return view->getRevealZone() && !view->getWriteableRevealZone(); } return false; } @@ -3784,8 +3852,7 @@ void Player::updateCardMenu(const CardItem *card) bool revealedCard = false; bool writeableCard = getLocalOrJudge(); - if (card->getZone() && card->getZone()->getIsView()) { - auto *view = dynamic_cast(card->getZone()); + if (auto *view = qobject_cast(card->getZone())) { if (view->getRevealZone()) { if (view->getWriteableRevealZone()) { writeableCard = true; @@ -3955,7 +4022,7 @@ void Player::updateCardMenu(const CardItem *card) cardMenu->addSeparator(); cardMenu->addAction(aSelectAll); - if (card->getZone()->getIsView()) { + if (qobject_cast(card->getZone())) { cardMenu->addAction(aSelectColumn); } diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 76365d50d..d802ef796 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -128,7 +128,7 @@ signals: Player *targetPlayer, QString targetCard, bool _playerTarget); - void logCreateToken(Player *player, QString cardName, QString pt); + void logCreateToken(Player *player, QString cardName, QString pt, bool faceDown); void logDrawCards(Player *player, int number, bool deckIsEmpty); void logUndoDraw(Player *player, QString cardName); void logMoveCard(Player *player, CardItem *card, CardZone *startZone, int oldX, CardZone *targetZone, int newX); @@ -253,7 +253,7 @@ public: private: TabGame *game; QMenu *sbMenu, *countersMenu, *sayMenu, *createPredefinedTokenMenu, *mRevealLibrary, *mLendLibrary, *mRevealTopCard, - *mRevealHand, *mRevealRandomHandCard, *mRevealRandomGraveyardCard; + *mRevealHand, *mRevealRandomHandCard, *mRevealRandomGraveyardCard, *mCustomZones; TearOffMenu *moveGraveMenu, *moveRfgMenu, *graveMenu, *moveHandMenu, *handMenu, *libraryMenu, *topLibraryMenu, *bottomLibraryMenu, *rfgMenu, *playerMenu; QList playerLists; @@ -409,7 +409,12 @@ public: void playCardToTable(const CardItem *c, bool faceDown); void addCard(CardItem *c); void deleteCard(CardItem *c); - void addZone(CardZone *z); + + template T *addZone(T *zone) + { + zones.insert(zone->getName(), zone); + return zone; + } AbstractCounter *addCounter(const ServerInfo_Counter &counter); AbstractCounter *addCounter(int counterId, const QString &name, QColor color, int radius, int value); diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game/zones/card_zone.cpp index 7516923b0..089ed0a2d 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game/zones/card_zone.cpp @@ -1,7 +1,7 @@ #include "card_zone.h" +#include "../board/card_item.h" #include "../cards/card_database_manager.h" -#include "../cards/card_item.h" #include "../player/player.h" #include "pb/command_move_card.pb.h" #include "pb/serverinfo_user.pb.h" @@ -19,21 +19,16 @@ * @param _isShufflable whether it makes sense to shuffle this zone by default after viewing it * @param _contentsKnown whether the cards in the zone are known to the client * @param parent the parent graphics object. - * @param _isView whether this zone is a view of another zone. Modifications to a view should modify the original */ CardZone::CardZone(Player *_p, const QString &_name, bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, - QGraphicsItem *parent, - bool _isView) + QGraphicsItem *parent) : AbstractGraphicsItem(parent), player(_p), name(_name), cards(_contentsKnown), views{}, menu(nullptr), - doubleClickAction(0), hasCardAttr(_hasCardAttr), isShufflable(_isShufflable), isView(_isView) + doubleClickAction(0), hasCardAttr(_hasCardAttr), isShufflable(_isShufflable) { - if (!isView) - player->addZone(this); - // If we join a game before the card db finishes loading, the cards might have the wrong printings. // Force refresh all cards in the zone when db finishes loading to fix that. connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this, @@ -97,6 +92,10 @@ QString CardZone::getTranslatedName(bool theirOwn, GrammaticalCase gc) const default: break; } + else { + return (theirOwn ? tr("their custom zone '%1'", "nominative").arg(name) + : tr("%1's custom zone '%2'", "nominative").arg(ownerName).arg(name)); + } return QString(); } @@ -236,4 +235,4 @@ void CardZone::moveAllToZone() QPointF CardZone::closestGridPoint(const QPointF &point) { return point; -} \ No newline at end of file +} diff --git a/cockatrice/src/game/zones/card_zone.h b/cockatrice/src/game/zones/card_zone.h index 91cad179c..8ce31e885 100644 --- a/cockatrice/src/game/zones/card_zone.h +++ b/cockatrice/src/game/zones/card_zone.h @@ -3,7 +3,7 @@ #include "../../client/translation.h" #include "../board/abstract_graphics_item.h" -#include "../cards/card_list.h" +#include "../board/card_list.h" #include #include @@ -35,7 +35,6 @@ protected: QAction *doubleClickAction; bool hasCardAttr; bool isShufflable; - bool isView; bool alwaysRevealTopCard; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; @@ -65,8 +64,7 @@ public: bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, - QGraphicsItem *parent = nullptr, - bool _isView = false); + QGraphicsItem *parent = nullptr); void retranslateUi(); void clearContents(); bool getHasCardAttr() const @@ -115,10 +113,6 @@ public: } virtual void reorganizeCards() = 0; virtual QPointF closestGridPoint(const QPointF &point); - bool getIsView() const - { - return isView; - } bool getAlwaysRevealTopCard() const { return alwaysRevealTopCard; diff --git a/cockatrice/src/game/zones/hand_zone.cpp b/cockatrice/src/game/zones/hand_zone.cpp index 31f0515d4..1bc029432 100644 --- a/cockatrice/src/game/zones/hand_zone.cpp +++ b/cockatrice/src/game/zones/hand_zone.cpp @@ -2,8 +2,8 @@ #include "../../client/ui/theme_manager.h" #include "../../settings/cache_settings.h" -#include "../cards/card_drag_item.h" -#include "../cards/card_item.h" +#include "../board/card_drag_item.h" +#include "../board/card_item.h" #include "../player/player.h" #include "pb/command_move_card.pb.h" diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index ba23560c9..a26499781 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -1,7 +1,7 @@ #include "pile_zone.h" -#include "../cards/card_drag_item.h" -#include "../cards/card_item.h" +#include "../board/card_drag_item.h" +#include "../board/card_item.h" #include "../player/player.h" #include "pb/command_move_card.pb.h" #include "view_zone.h" diff --git a/cockatrice/src/game/zones/select_zone.cpp b/cockatrice/src/game/zones/select_zone.cpp index 949b3fba2..b26aa23ff 100644 --- a/cockatrice/src/game/zones/select_zone.cpp +++ b/cockatrice/src/game/zones/select_zone.cpp @@ -1,7 +1,7 @@ #include "select_zone.h" #include "../../settings/cache_settings.h" -#include "../cards/card_item.h" +#include "../board/card_item.h" #include "../game_scene.h" #include @@ -37,9 +37,8 @@ SelectZone::SelectZone(Player *_player, bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, - QGraphicsItem *parent, - bool isView) - : CardZone(_player, _name, _hasCardAttr, _isShufflable, _contentsKnown, parent, isView) + QGraphicsItem *parent) + : CardZone(_player, _name, _hasCardAttr, _isShufflable, _contentsKnown, parent) { } diff --git a/cockatrice/src/game/zones/select_zone.h b/cockatrice/src/game/zones/select_zone.h index c8c88b450..6aa56a1c6 100644 --- a/cockatrice/src/game/zones/select_zone.h +++ b/cockatrice/src/game/zones/select_zone.h @@ -26,8 +26,7 @@ public: bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, - QGraphicsItem *parent = nullptr, - bool isView = false); + QGraphicsItem *parent = nullptr); }; qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse = false); diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp index b597a9e81..324ef55fb 100644 --- a/cockatrice/src/game/zones/stack_zone.cpp +++ b/cockatrice/src/game/zones/stack_zone.cpp @@ -3,8 +3,8 @@ #include "../../client/ui/theme_manager.h" #include "../../settings/cache_settings.h" #include "../board/arrow_item.h" -#include "../cards/card_drag_item.h" -#include "../cards/card_item.h" +#include "../board/card_drag_item.h" +#include "../board/card_item.h" #include "../player/player.h" #include "pb/command_move_card.pb.h" diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index 02a06ed2e..cee782560 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -3,9 +3,9 @@ #include "../../client/ui/theme_manager.h" #include "../../settings/cache_settings.h" #include "../board/arrow_item.h" -#include "../cards/card_drag_item.h" +#include "../board/card_drag_item.h" +#include "../board/card_item.h" #include "../cards/card_info.h" -#include "../cards/card_item.h" #include "../player/player.h" #include "pb/command_move_card.pb.h" #include "pb/command_set_card_attr.pb.h" @@ -19,8 +19,8 @@ const QColor TableZone::FADE_MASK = QColor(0, 0, 0, 80); const QColor TableZone::GRADIENT_COLOR = QColor(255, 255, 255, 150); const QColor TableZone::GRADIENT_COLORLESS = QColor(255, 255, 255, 0); -TableZone::TableZone(Player *_p, QGraphicsItem *parent) - : SelectZone(_p, "table", true, false, true, parent), active(false) +TableZone::TableZone(Player *_p, const QString &name, QGraphicsItem *parent) + : SelectZone(_p, name, true, false, true, parent), active(false) { connect(themeManager, &ThemeManager::themeChanged, this, &TableZone::updateBg); connect(&SettingsCache::instance(), &SettingsCache::invertVerticalCoordinateChanged, this, diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index 271276967..3d464e6f3 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -1,7 +1,7 @@ #ifndef TABLEZONE_H #define TABLEZONE_H -#include "../cards/abstract_card_item.h" +#include "../board/abstract_card_item.h" #include "select_zone.h" /* @@ -98,7 +98,7 @@ public: @param _p the Player @param parent defaults to null */ - explicit TableZone(Player *_p, QGraphicsItem *parent = nullptr); + explicit TableZone(Player *_p, const QString &name, QGraphicsItem *parent = nullptr); /** @return a QRectF of the TableZone bounding box. diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index 9aec01db3..fd9387064 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -1,9 +1,9 @@ #include "view_zone.h" #include "../../server/pending_command.h" -#include "../cards/card_drag_item.h" +#include "../board/card_drag_item.h" +#include "../board/card_item.h" #include "../cards/card_info.h" -#include "../cards/card_item.h" #include "../player/player.h" #include "pb/command_dump_zone.pb.h" #include "pb/command_move_card.pb.h" @@ -30,7 +30,7 @@ ZoneViewZone::ZoneViewZone(Player *_p, bool _writeableRevealZone, QGraphicsItem *parent, bool _isReversed) - : SelectZone(_p, _origZone->getName(), false, false, true, parent, true), bRect(QRectF()), minRows(0), + : SelectZone(_p, _origZone->getName(), false, false, true, parent), bRect(QRectF()), minRows(0), numberCards(_numberCards), origZone(_origZone), revealZone(_revealZone), writeableRevealZone(_writeableRevealZone), groupBy(CardList::NoSort), sortBy(CardList::NoSort), isReversed(_isReversed) diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 85a91afdf..f0430f549 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -2,7 +2,7 @@ #include "../../client/ui/pixel_map_generator.h" #include "../../settings/cache_settings.h" -#include "../cards/card_item.h" +#include "../board/card_item.h" #include "../filters/syntax_help.h" #include "../game_scene.h" #include "../player/player.h" @@ -56,6 +56,11 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(&searchEdit); }); + if (SettingsCache::instance().getFocusCardViewSearchBar()) { + this->setActive(true); + searchEdit.setFocus(); + } + QGraphicsProxyWidget *searchEditProxy = new QGraphicsProxyWidget; searchEditProxy->setWidget(&searchEdit); searchEditProxy->setZValue(2000000007); diff --git a/cockatrice/src/server/message_log_widget.cpp b/cockatrice/src/server/message_log_widget.cpp index 2c0174673..a61a7ba2c 100644 --- a/cockatrice/src/server/message_log_widget.cpp +++ b/cockatrice/src/server/message_log_widget.cpp @@ -2,7 +2,7 @@ #include "../client/sound_engine.h" #include "../client/translate_counter_name.h" -#include "../game/cards/card_item.h" +#include "../game/board/card_item.h" #include "../game/phase.h" #include "../game/player/player.h" #include "../game/zones/card_zone.h" @@ -12,74 +12,39 @@ #include -const QString &MessageLogWidget::tableConstant() const -{ - static const QString constant("table"); - return constant; -} +static const QString TABLE_ZONE_NAME = "table"; +static const QString GRAVE_ZONE_NAME = "grave"; +static const QString EXILE_ZONE_NAME = "rfg"; +static const QString HAND_ZONE_NAME = "hand"; +static const QString DECK_ZONE_NAME = "deck"; +static const QString SIDEBOARD_ZONE_NAME = "sb"; +static const QString STACK_ZONE_NAME = "stack"; -const QString &MessageLogWidget::graveyardConstant() const -{ - static const QString constant("grave"); - return constant; -} - -const QString &MessageLogWidget::exileConstant() const -{ - static const QString constant("rfg"); - return constant; -} - -const QString &MessageLogWidget::handConstant() const -{ - static const QString constant("hand"); - return constant; -} - -const QString &MessageLogWidget::deckConstant() const -{ - static const QString constant("deck"); - return constant; -} - -const QString &MessageLogWidget::sideboardConstant() const -{ - static const QString constant("sb"); - return constant; -} - -const QString &MessageLogWidget::stackConstant() const -{ - static const QString constant("stack"); - return constant; -} - -QString MessageLogWidget::sanitizeHtml(QString dirty) const +static QString sanitizeHtml(QString dirty) { return dirty.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """); } -QString MessageLogWidget::cardLink(const QString cardName) const +static QString cardLink(const QString &cardName) { return QString("%2").arg(cardName).arg(cardName); } -QPair -MessageLogWidget::getFromStr(CardZone *zone, QString cardName, int position, bool ownerChange) const +QPair MessageLogWidget::getFromStr(CardZone *zone, QString cardName, int position, bool ownerChange) { bool cardNameContainsStartZone = false; QString fromStr; QString zoneName = zone->getName(); - if (zoneName == tableConstant()) { + if (zoneName == TABLE_ZONE_NAME) { fromStr = tr(" from play"); - } else if (zoneName == graveyardConstant()) { + } else if (zoneName == GRAVE_ZONE_NAME) { fromStr = tr(" from their graveyard"); - } else if (zoneName == exileConstant()) { + } else if (zoneName == EXILE_ZONE_NAME) { fromStr = tr(" from exile"); - } else if (zoneName == handConstant()) { + } else if (zoneName == HAND_ZONE_NAME) { fromStr = tr(" from their hand"); - } else if (zoneName == deckConstant()) { + } else if (zoneName == DECK_ZONE_NAME) { if (position == 0) { if (cardName.isEmpty()) { if (ownerChange) { @@ -117,16 +82,18 @@ MessageLogWidget::getFromStr(CardZone *zone, QString cardName, int position, boo fromStr = tr(" from their library"); } } - } else if (zoneName == sideboardConstant()) { + } else if (zoneName == SIDEBOARD_ZONE_NAME) { fromStr = tr(" from sideboard"); - } else if (zoneName == stackConstant()) { + } else if (zoneName == STACK_ZONE_NAME) { fromStr = tr(" from the stack"); + } else { + fromStr = tr(" from custom zone '%1'").arg(zoneName); } if (!cardNameContainsStartZone) { cardName.clear(); } - return QPair(cardName, fromStr); + return {cardName, fromStr}; } void MessageLogWidget::containerProcessingDone() @@ -249,12 +216,16 @@ void MessageLogWidget::logCreateArrow(Player *player, } } -void MessageLogWidget::logCreateToken(Player *player, QString cardName, QString pt) +void MessageLogWidget::logCreateToken(Player *player, QString cardName, QString pt, bool faceDown) { - appendHtmlServerMessage(tr("%1 creates token: %2%3.") - .arg(sanitizeHtml(player->getName())) - .arg(cardLink(std::move(cardName))) - .arg(pt.isEmpty() ? QString() : QString(" (%1)").arg(sanitizeHtml(pt)))); + if (faceDown) { + appendHtmlServerMessage(tr("%1 creates a face down token.").arg(sanitizeHtml(player->getName()))); + } else { + appendHtmlServerMessage(tr("%1 creates token: %2%3.") + .arg(sanitizeHtml(player->getName())) + .arg(cardLink(std::move(cardName))) + .arg(pt.isEmpty() ? QString() : QString(" (%1)").arg(sanitizeHtml(pt)))); + } } void MessageLogWidget::logDeckSelect(Player *player, QString deckHash, int sideboardSize) @@ -291,9 +262,9 @@ void MessageLogWidget::logMoveCard(Player *player, bool ownerChanged = startZone->getPlayer() != targetZone->getPlayer(); // do not log if moved within the same zone - if ((startZoneName == tableConstant() && targetZoneName == tableConstant() && !ownerChanged) || - (startZoneName == handConstant() && targetZoneName == handConstant()) || - (startZoneName == exileConstant() && targetZoneName == exileConstant())) { + if ((startZoneName == TABLE_ZONE_NAME && targetZoneName == TABLE_ZONE_NAME && !ownerChanged) || + (startZoneName == HAND_ZONE_NAME && targetZoneName == HAND_ZONE_NAME) || + (startZoneName == EXILE_ZONE_NAME && targetZoneName == EXILE_ZONE_NAME)) { return; } @@ -322,20 +293,20 @@ void MessageLogWidget::logMoveCard(Player *player, QString finalStr; bool usesNewX = false; - if (targetZoneName == tableConstant()) { + if (targetZoneName == TABLE_ZONE_NAME) { soundEngine->playSound("play_card"); if (card->getFaceDown()) { finalStr = tr("%1 puts %2 into play%3 face down."); } else { finalStr = tr("%1 puts %2 into play%3."); } - } else if (targetZoneName == graveyardConstant()) { + } else if (targetZoneName == GRAVE_ZONE_NAME) { finalStr = tr("%1 puts %2%3 into their graveyard."); - } else if (targetZoneName == exileConstant()) { + } else if (targetZoneName == EXILE_ZONE_NAME) { finalStr = tr("%1 exiles %2%3."); - } else if (targetZoneName == handConstant()) { + } else if (targetZoneName == HAND_ZONE_NAME) { finalStr = tr("%1 moves %2%3 to their hand."); - } else if (targetZoneName == deckConstant()) { + } else if (targetZoneName == DECK_ZONE_NAME) { if (newX == -1) { finalStr = tr("%1 puts %2%3 into their library."); } else if (newX >= targetZone->getCards().size()) { @@ -347,18 +318,21 @@ void MessageLogWidget::logMoveCard(Player *player, usesNewX = true; finalStr = tr("%1 puts %2%3 into their library %4 cards from the top."); } - } else if (targetZoneName == sideboardConstant()) { + } else if (targetZoneName == SIDEBOARD_ZONE_NAME) { finalStr = tr("%1 moves %2%3 to sideboard."); - } else if (targetZoneName == stackConstant()) { + } else if (targetZoneName == STACK_ZONE_NAME) { soundEngine->playSound("play_card"); finalStr = tr("%1 plays %2%3."); + } else { + finalStr = tr("%1 moves %2%3 to custom zone '%4'."); } if (usesNewX) { appendHtmlServerMessage( finalStr.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(nameFrom.second).arg(newX)); } else { - appendHtmlServerMessage(finalStr.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(nameFrom.second)); + appendHtmlServerMessage( + finalStr.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(nameFrom.second).arg(targetZoneName)); } } @@ -845,6 +819,6 @@ void MessageLogWidget::connectToPlayer(Player *player) } MessageLogWidget::MessageLogWidget(TabSupervisor *_tabSupervisor, TabGame *_game, QWidget *parent) - : ChatView(_tabSupervisor, _game, true, parent), mulliganNumber(0), currentContext(MessageContext_None) + : ChatView(_tabSupervisor, _game, true, parent), currentContext(MessageContext_None) { } diff --git a/cockatrice/src/server/message_log_widget.h b/cockatrice/src/server/message_log_widget.h index ef66f21e7..21b03dee9 100644 --- a/cockatrice/src/server/message_log_widget.h +++ b/cockatrice/src/server/message_log_widget.h @@ -21,22 +21,10 @@ private: MessageContext_Mulligan }; - int mulliganNumber; - Player *mulliganPlayer; MessageContext currentContext; QString messagePrefix, messageSuffix; - const QString &tableConstant() const; - const QString &graveyardConstant() const; - const QString &exileConstant() const; - const QString &handConstant() const; - const QString &deckConstant() const; - const QString &sideboardConstant() const; - const QString &stackConstant() const; - - QString sanitizeHtml(QString dirty) const; - QString cardLink(QString cardName) const; - QPair getFromStr(CardZone *zone, QString cardName, int position, bool ownerChange) const; + static QPair getFromStr(CardZone *zone, QString cardName, int position, bool ownerChange); public slots: void containerProcessingDone(); @@ -53,7 +41,7 @@ public slots: Player *targetPlayer, QString targetCard, bool playerTarget); - void logCreateToken(Player *player, QString cardName, QString pt); + void logCreateToken(Player *player, QString cardName, QString pt, bool faceDown); void logDeckSelect(Player *player, QString deckHash, int sideboardSize); void logDestroyCard(Player *player, QString cardName); void logDrawCards(Player *player, int number, bool deckIsEmpty); diff --git a/cockatrice/src/server/remote/remote_client.cpp b/cockatrice/src/server/remote/remote_client.cpp index a573765af..1d8579c14 100644 --- a/cockatrice/src/server/remote/remote_client.cpp +++ b/cockatrice/src/server/remote/remote_client.cpp @@ -43,7 +43,7 @@ RemoteClient::RemoteClient(QObject *parent) connect(socket, &QTcpSocket::connected, this, &RemoteClient::slotConnected); connect(socket, &QTcpSocket::readyRead, this, &RemoteClient::readData); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) connect(socket, &QTcpSocket::errorOccurred, this, &RemoteClient::slotSocketError); #else connect(socket, qOverload(&QTcpSocket::error), this, &RemoteClient::slotSocketError); diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index 096ab23b8..ef369641b 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -251,6 +251,7 @@ SettingsCache::SettingsCache() cardViewInitialRowsMax = settings->value("interface/cardViewInitialRowsMax", 14).toInt(); cardViewExpandedRowsMax = settings->value("interface/cardViewExpandedRowsMax", 20).toInt(); closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool(); + focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool(); showShortcuts = settings->value("menu/showshortcuts", true).toBool(); displayCardNames = settings->value("cards/displaycardnames", true).toBool(); @@ -263,6 +264,9 @@ SettingsCache::SettingsCache() includeRebalancedCards = settings->value("cards/includerebalancedcards", true).toBool(); printingSelectorNavigationButtonsVisible = settings->value("cards/printingselectornavigationbuttonsvisible", true).toBool(); + deckEditorBannerCardComboBoxVisible = + settings->value("interface/deckeditorbannercardcomboboxvisible", true).toBool(); + deckEditorTagsWidgetVisible = settings->value("interface/deckeditortagswidgetvisible", true).toBool(); visualDeckStorageCardSize = settings->value("interface/visualdeckstoragecardsize", 100).toInt(); visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).toInt(); visualDeckStorageShowFolders = settings->value("interface/visualdeckstorageshowfolders", true).toBool(); @@ -282,6 +286,12 @@ SettingsCache::SettingsCache() visualDeckStorageInGame = settings->value("interface/visualdeckstorageingame", true).toBool(); visualDeckStorageSelectionAnimation = settings->value("interface/visualdeckstorageselectionanimation", true).toBool(); + defaultDeckEditorType = settings->value("interface/defaultDeckEditorType", 1).toInt(); + visualDatabaseDisplayFilterToMostRecentSetsEnabled = + settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsenabled", true).toBool(); + visualDatabaseDisplayFilterToMostRecentSetsAmount = + settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsamount", 10).toInt(); + visualDeckEditorSampleHandSize = settings->value("interface/visualdeckeditorsamplehandsize", 7).toInt(); horizontalHand = settings->value("hand/horizontal", true).toBool(); invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt(); @@ -362,6 +372,12 @@ void SettingsCache::setCloseEmptyCardView(QT_STATE_CHANGED_T value) settings->setValue("interface/closeEmptyCardView", closeEmptyCardView); } +void SettingsCache::setFocusCardViewSearchBar(QT_STATE_CHANGED_T value) +{ + focusCardViewSearchBar = value; + settings->setValue("interface/focusCardViewSearchBar", focusCardViewSearchBar); +} + void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures) { knownMissingFeatures = _knownMissingFeatures; @@ -681,6 +697,20 @@ void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED emit printingSelectorNavigationButtonsVisibleChanged(); } +void SettingsCache::setDeckEditorBannerCardComboBoxVisible(QT_STATE_CHANGED_T _deckEditorBannerCardComboBoxVisible) +{ + deckEditorBannerCardComboBoxVisible = _deckEditorBannerCardComboBoxVisible; + settings->setValue("interface/deckeditorbannercardcomboboxvisible", deckEditorBannerCardComboBoxVisible); + emit deckEditorBannerCardComboBoxVisibleChanged(deckEditorBannerCardComboBoxVisible); +} + +void SettingsCache::setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T _deckEditorTagsWidgetVisible) +{ + deckEditorTagsWidgetVisible = _deckEditorTagsWidgetVisible; + settings->setValue("interface/deckeditortagswidgetvisible", deckEditorTagsWidgetVisible); + emit deckEditorTagsWidgetVisibleChanged(deckEditorTagsWidgetVisible); +} + void SettingsCache::setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder) { visualDeckStorageSortingOrder = _visualDeckStorageSortingOrder; @@ -770,6 +800,35 @@ void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T va emit visualDeckStorageSelectionAnimationChanged(visualDeckStorageSelectionAnimation); } +void SettingsCache::setDefaultDeckEditorType(int value) +{ + defaultDeckEditorType = value; + settings->setValue("interface/defaultDeckEditorType", defaultDeckEditorType); +} + +void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T _enabled) +{ + visualDatabaseDisplayFilterToMostRecentSetsEnabled = _enabled; + settings->setValue("interface/visualdatabasedisplayfiltertomostrecentsetsenabled", + visualDatabaseDisplayFilterToMostRecentSetsEnabled); + emit visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged(visualDatabaseDisplayFilterToMostRecentSetsEnabled); +} + +void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int _amount) +{ + visualDatabaseDisplayFilterToMostRecentSetsAmount = _amount; + settings->setValue("interface/visualdatabasedisplayfiltertomostrecentsetsamount", + visualDatabaseDisplayFilterToMostRecentSetsAmount); + emit visualDatabaseDisplayFilterToMostRecentSetsAmountChanged(visualDatabaseDisplayFilterToMostRecentSetsAmount); +} + +void SettingsCache::setVisualDeckEditorSampleHandSize(int _amount) +{ + visualDeckEditorSampleHandSize = _amount; + settings->setValue("interface/visualdeckeditorsamplehandsize", visualDeckEditorSampleHandSize); + emit visualDeckEditorSampleHandSizeAmountChanged(visualDeckEditorSampleHandSize); +} + void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand) { horizontalHand = static_cast(_horizontalHand); diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index 82625d9f7..94cb2ed6a 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -61,6 +61,8 @@ signals: void printingSelectorCardSizeChanged(); void includeRebalancedCardsChanged(bool _includeRebalancedCards); void printingSelectorNavigationButtonsVisibleChanged(); + void deckEditorBannerCardComboBoxVisibleChanged(bool _visible); + void deckEditorTagsWidgetVisibleChanged(bool _visible); void visualDeckStorageShowTagFilterChanged(bool _visible); void visualDeckStorageShowBannerCardComboBoxChanged(bool _visible); void visualDeckStorageShowTagsOnDeckPreviewsChanged(bool _visible); @@ -69,6 +71,9 @@ signals: void visualDeckStorageUnusedColorIdentitiesOpacityChanged(bool value); void visualDeckStorageInGameChanged(bool enabled); void visualDeckStorageSelectionAnimationChanged(bool enabled); + void visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged(bool enabled); + void visualDatabaseDisplayFilterToMostRecentSetsAmountChanged(int amount); + void visualDeckEditorSampleHandSizeAmountChanged(int amount); void horizontalHandChanged(); void handJustificationChanged(); void invertVerticalCoordinateChanged(); @@ -134,6 +139,8 @@ private: int printingSelectorCardSize; bool includeRebalancedCards; bool printingSelectorNavigationButtonsVisible; + bool deckEditorBannerCardComboBoxVisible; + bool deckEditorTagsWidgetVisible; int visualDeckStorageSortingOrder; bool visualDeckStorageShowFolders; bool visualDeckStorageShowBannerCardComboBox; @@ -147,6 +154,10 @@ private: bool visualDeckStorageAlwaysConvert; bool visualDeckStorageInGame; bool visualDeckStorageSelectionAnimation; + int defaultDeckEditorType; + bool visualDatabaseDisplayFilterToMostRecentSetsEnabled; + int visualDatabaseDisplayFilterToMostRecentSetsAmount; + int visualDeckEditorSampleHandSize; bool horizontalHand; bool invertVerticalCoordinate; int minPlayersForMultiColumnLayout; @@ -175,6 +186,7 @@ private: int cardViewInitialRowsMax; int cardViewExpandedRowsMax; bool closeEmptyCardView; + bool focusCardViewSearchBar; int pixmapCacheSize; int networkCacheSize; int redirectCacheTtl; @@ -420,6 +432,14 @@ public: { return printingSelectorNavigationButtonsVisible; } + bool getDeckEditorBannerCardComboBoxVisible() const + { + return deckEditorBannerCardComboBoxVisible; + } + bool getDeckEditorTagsWidgetVisible() const + { + return deckEditorTagsWidgetVisible; + } int getVisualDeckStorageSortingOrder() const { return visualDeckStorageSortingOrder; @@ -472,6 +492,22 @@ public: { return visualDeckStorageSelectionAnimation; } + int getDefaultDeckEditorType() const + { + return defaultDeckEditorType; + } + bool getVisualDatabaseDisplayFilterToMostRecentSetsEnabled() const + { + return visualDatabaseDisplayFilterToMostRecentSetsEnabled; + } + int getVisualDatabaseDisplayFilterToMostRecentSetsAmount() const + { + return visualDatabaseDisplayFilterToMostRecentSetsAmount; + } + int getVisualDeckEditorSampleHandSize() const + { + return visualDeckEditorSampleHandSize; + } bool getHorizontalHand() const { return horizontalHand; @@ -670,6 +706,7 @@ public: void setCardViewInitialRowsMax(int _cardViewInitialRowsMax); void setCardViewExpandedRowsMax(int value); void setCloseEmptyCardView(QT_STATE_CHANGED_T value); + void setFocusCardViewSearchBar(QT_STATE_CHANGED_T value); QString getClientID() { return clientID; @@ -698,6 +735,10 @@ public: { return closeEmptyCardView; } + bool getFocusCardViewSearchBar() const + { + return focusCardViewSearchBar; + } ShortcutsSettings &shortcuts() const { return *shortcutsSettings; @@ -800,6 +841,8 @@ public slots: void setPrintingSelectorCardSize(int _printingSelectorCardSize); void setIncludeRebalancedCards(bool _includeRebalancedCards); void setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible); + void setDeckEditorBannerCardComboBoxVisible(QT_STATE_CHANGED_T _deckEditorBannerCardComboBoxVisible); + void setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T _deckEditorTagsWidgetVisible); void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder); void setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value); void setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags); @@ -813,6 +856,10 @@ public slots: void setVisualDeckStorageAlwaysConvert(bool _visualDeckStorageAlwaysConvert); void setVisualDeckStorageInGame(QT_STATE_CHANGED_T value); void setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T value); + void setDefaultDeckEditorType(int value); + void setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T _enabled); + void setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int _amount); + void setVisualDeckEditorSampleHandSize(int _amount); void setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand); void setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate); void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); diff --git a/cockatrice/src/settings/shortcut_treeview.cpp b/cockatrice/src/settings/shortcut_treeview.cpp index 02bc08164..6b329b23d 100644 --- a/cockatrice/src/settings/shortcut_treeview.cpp +++ b/cockatrice/src/settings/shortcut_treeview.cpp @@ -150,7 +150,7 @@ void ShortcutTreeView::currentChanged(const QModelIndex ¤t, const QModelIn */ void ShortcutTreeView::updateSearchString(const QString &searchString) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0) const auto skipEmptyParts = Qt::SkipEmptyParts; #else const auto skipEmptyParts = QString::SkipEmptyParts; diff --git a/cockatrice/src/utility/card_info_comparator.cpp b/cockatrice/src/utility/card_info_comparator.cpp index 26d00420f..821cc8675 100644 --- a/cockatrice/src/utility/card_info_comparator.cpp +++ b/cockatrice/src/utility/card_info_comparator.cpp @@ -37,17 +37,17 @@ bool CardInfoComparator::compareVariants(const QVariant &a, const QVariant &b) c // Perform type-specific comparison #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - switch (a.typeId()) { + switch (static_cast(a.typeId())) { #else - switch (a.type()) { + switch (static_cast(a.type())) { #endif - case QMetaType::Int: + case static_cast(QMetaType::Int): return a.toInt() < b.toInt(); - case QMetaType::Double: + case static_cast(QMetaType::Double): return a.toDouble() < b.toDouble(); - case QMetaType::QString: + case static_cast(QMetaType::QString): return a.toString() < b.toString(); - case QMetaType::Bool: + case static_cast(QMetaType::Bool): return a.toBool() < b.toBool(); default: // Default to comparing as strings diff --git a/cockatrice/src/utility/logger.cpp b/cockatrice/src/utility/logger.cpp index 74ed90597..0184146ae 100644 --- a/cockatrice/src/utility/logger.cpp +++ b/cockatrice/src/utility/logger.cpp @@ -81,12 +81,12 @@ void Logger::closeLogfileSession() fileHandle.close(); } -void Logger::log(QtMsgType /* type */, const QMessageLogContext & /* ctx */, const QString message) +void Logger::log(QtMsgType /* type */, const QMessageLogContext & /* ctx */, const QString &message) { QMetaObject::invokeMethod(this, "internalLog", Qt::QueuedConnection, Q_ARG(const QString &, message)); } -void Logger::internalLog(QString message) +void Logger::internalLog(const QString &message) { QMutexLocker locker(&mutex); diff --git a/cockatrice/src/utility/logger.h b/cockatrice/src/utility/logger.h index 246e9204a..b90644b36 100644 --- a/cockatrice/src/utility/logger.h +++ b/cockatrice/src/utility/logger.h @@ -28,7 +28,7 @@ public: } void logToFile(bool enabled); - void log(QtMsgType type, const QMessageLogContext &ctx, QString message); + void log(QtMsgType type, const QMessageLogContext &ctx, const QString &message); QString getClientVersion(); QString getClientOperatingSystem(); QString getSystemArchitecture(); @@ -57,10 +57,10 @@ protected: void closeLogfileSession(); protected slots: - void internalLog(QString message); + void internalLog(const QString &message); signals: - void logEntryAdded(QString message); + void logEntryAdded(const QString &message); }; #endif diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index f310b5842..7cdce9896 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -203,7 +203,7 @@ Controlla se la cartella è valida e prova ancora. Minimum overlap percentage of cards on the stack and in vertical hand - Sovrapposizione % minima delle carte in pila e nella mano verticale + Sovrapposizione % minima delle carte in pila e nella mano verticale: @@ -2770,13 +2770,13 @@ Per favore, visitate la pagina di download per aggiornare manualmente. Released - Rilasciato + Data di rilascio Changelog - Cambiamenti della versione + Changelog @@ -2789,13 +2789,14 @@ Per favore, visitate la pagina di download per aggiornare manualmente. Unfortunately there are no download packages available for your operating system. You may have to build from source yourself. - + Purtroppo l'aggiornamento automatico non è riuscito a trovare una versione compatibile. +Dovrai scaricare la nuova versione manualmente. Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Please check the download page manually and visit the wiki for instructions on compiling. - + Per favore, controlla la <a href="%1">pagina delle release</a> sul nostro Github e scarica la versione giusta per il tuo sistema. @@ -3668,7 +3669,7 @@ La tua versione è la %1, la versione online è la %2. Start &local game... - Inizia &partita in locale... + Inizia partita in &locale... @@ -3688,7 +3689,7 @@ La tua versione è la %1, la versione online è la %2. &Restore password... - &Recupera password... + Recupera &password... @@ -3713,7 +3714,7 @@ La tua versione è la %1, la versione online è la %2. C&ard Database - &Database delle carte + &Database carte @@ -3916,7 +3917,7 @@ Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti n An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + Si è verificato un errore nel cercare di scrivere al processo. Ad esempio, il processo potrebbe non essere in esecuzione, o potrebbe aver chiuso il suo canale di input. @@ -5850,7 +5851,7 @@ Il database delle carte verrà ricaricato. Time started - Inizio tempo + Inizio @@ -7122,7 +7123,7 @@ Più informazioni inserisci, più specifici saranno i risultati. Deck Editor - Editor dei mazzi + &Editor dei mazzi @@ -7142,12 +7143,12 @@ Più informazioni inserisci, più specifici saranno i risultati. Deck Storage - Archivio mazzi + &Archivio mazzi Game Replays - Replay partite + &Replay partite @@ -8299,12 +8300,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Analyze Deck (deckstats.net) Analyze Deck - Analizza Mazzo (deckstats.net) + Analizza mazzo (deckstats.net) Analyze Deck (tappedout.net) - Analizza Mazzo (tappedout.net) + Analizza mazzo (tappedout.net) @@ -8496,7 +8497,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Set Red Counters... - Imposta Contatore Rosso... + Imposta Segnalino Rosso... diff --git a/common/decklist.cpp b/common/decklist.cpp index 789f57910..6f27d2895 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -262,21 +262,6 @@ bool AbstractDecklistCardNode::compareName(AbstractDecklistNode *other) const } } -class InnerDecklistNode::compareFunctor -{ -private: - Qt::SortOrder order; - -public: - explicit compareFunctor(Qt::SortOrder _order) : order(_order) - { - } - inline bool operator()(QPair a, QPair b) const - { - return (order == Qt::AscendingOrder) ? (b.second->compare(a.second)) : (a.second->compare(b.second)); - } -}; - bool InnerDecklistNode::readElement(QXmlStreamReader *xml) { while (!xml->atEnd()) { @@ -347,7 +332,10 @@ QVector> InnerDecklistNode::sort(Qt::SortOrder order) } // Sort temporary list - compareFunctor cmp(order); + auto cmp = [order](const auto &a, const auto &b) { + return (order == Qt::AscendingOrder) ? (b.second->compare(a.second)) : (a.second->compare(b.second)); + }; + std::sort(tempList.begin(), tempList.end(), cmp); // Map old indexes to new indexes and @@ -762,20 +750,9 @@ bool DeckList::loadFromFile_Plain(QIODevice *device) return loadFromStream_Plain(in, false); } -struct WriteToStream +bool DeckList::saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards) { - QTextStream &stream; - bool prefixSideboardCards; - bool slashTappedOutSplitCards; - - WriteToStream(QTextStream &_stream, bool _prefixSideboardCards, bool _slashTappedOutSplitCards) - : stream(_stream), prefixSideboardCards(_prefixSideboardCards), - slashTappedOutSplitCards(_slashTappedOutSplitCards) - { - } - - void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) - { + auto writeToStream = [&stream, prefixSideboardCards, slashTappedOutSplitCards](const auto node, const auto card) { if (prefixSideboardCards && node->getName() == DECK_ZONE_SIDE) { stream << "SB: "; } @@ -784,12 +761,8 @@ struct WriteToStream } else { stream << QString("%1 %2\n").arg(card->getNumber()).arg(card->getName().replace("//", "/")); } - } -}; + }; -bool DeckList::saveToStream_Plain(QTextStream &out, bool prefixSideboardCards, bool slashTappedOutSplitCards) -{ - WriteToStream writeToStream(out, prefixSideboardCards, slashTappedOutSplitCards); forEachCard(writeToStream); return true; } @@ -994,3 +967,19 @@ void DeckList::refreshDeckHash() cachedDeckHash = QString(); emit deckHashChanged(); } + +/** + * Calls a given function on each card in the deck. + */ +void DeckList::forEachCard(const std::function &func) +{ + // Support for this is only possible if the internal structure + // doesn't get more complicated. + for (int i = 0; i < root->size(); i++) { + InnerDecklistNode *node = dynamic_cast(root->at(i)); + for (int j = 0; j < node->size(); j++) { + DecklistCardNode *card = dynamic_cast(node->at(j)); + func(node, card); + } + } +} \ No newline at end of file diff --git a/common/decklist.h b/common/decklist.h index de839ad23..989c3e7fe 100644 --- a/common/decklist.h +++ b/common/decklist.h @@ -381,23 +381,7 @@ public: QString getDeckHash() const; void refreshDeckHash(); - /** - * Calls a given function object for each card in the deck. It must - * take a InnerDecklistNode* as its first argument and a - * DecklistCardNode* as its second. - */ - template void forEachCard(Callback &callback) - { - // Support for this is only possible if the internal structure - // doesn't get more complicated. - for (int i = 0; i < root->size(); i++) { - InnerDecklistNode *node = dynamic_cast(root->at(i)); - for (int j = 0; j < node->size(); j++) { - DecklistCardNode *card = dynamic_cast(node->at(j)); - callback(node, card); - } - } - } + void forEachCard(const std::function &func); }; #endif \ No newline at end of file diff --git a/common/pb/command_create_token.proto b/common/pb/command_create_token.proto index 671ce3fe3..4b7d11098 100644 --- a/common/pb/command_create_token.proto +++ b/common/pb/command_create_token.proto @@ -27,4 +27,5 @@ message Command_CreateToken { optional TargetMode target_mode = 11; optional string card_provider_id = 12; + optional bool face_down = 13; } diff --git a/common/pb/event_create_token.proto b/common/pb/event_create_token.proto index bc88a744c..6947b6048 100644 --- a/common/pb/event_create_token.proto +++ b/common/pb/event_create_token.proto @@ -15,4 +15,5 @@ message Event_CreateToken { optional sint32 x = 8; optional sint32 y = 9; optional string card_provider_id = 10; + optional bool face_down = 11; } diff --git a/common/pb/serverinfo_zone.proto b/common/pb/serverinfo_zone.proto index 0efa2d9be..f0ad5d709 100644 --- a/common/pb/serverinfo_zone.proto +++ b/common/pb/serverinfo_zone.proto @@ -11,6 +11,10 @@ message ServerInfo_Zone { // setting beingLookedAt to true. // Cards in a zone with the type HiddenZone are referenced by their // list index, whereas cards in any other zone are referenced by their ids. + // + // WARNING: Adding new zone types will break compatibility with older + // clients. Older clients will read new zone types as PrivateZone, which + // is likely *NOT* what you want. PrivateZone = 0; PublicZone = 1; diff --git a/common/server.cpp b/common/server.cpp index a8223c599..4b0120ea1 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -51,8 +51,7 @@ Server::Server(QObject *parent) : QObject(parent), nextLocalGameId(0), tcpUserCo qRegisterMetaType("IslMessage"); qRegisterMetaType("Command_JoinGame"); - connect(this, SIGNAL(sigSendIslMessage(IslMessage, int)), this, SLOT(doSendIslMessage(IslMessage, int)), - Qt::QueuedConnection); + connect(this, &Server::sigSendIslMessage, this, &Server::doSendIslMessage, Qt::QueuedConnection); } void Server::prepareDestroy() @@ -67,7 +66,7 @@ void Server::prepareDestroy() void Server::setDatabaseInterface(Server_DatabaseInterface *_databaseInterface) { - connect(this, SIGNAL(endSession(qint64)), _databaseInterface, SLOT(endSession(qint64))); + connect(this, &Server::endSession, _databaseInterface, &Server_DatabaseInterface::endSession); databaseInterfaces.insert(QThread::currentThread(), _databaseInterface); } @@ -568,8 +567,9 @@ void Server::addRoom(Server_Room *newRoom) QWriteLocker locker(&roomsLock); qDebug() << "Adding room: ID=" << newRoom->getId() << "name=" << newRoom->getName(); rooms.insert(newRoom->getId(), newRoom); - connect(newRoom, SIGNAL(roomInfoChanged(ServerInfo_Room)), this, SLOT(broadcastRoomUpdate(const ServerInfo_Room &)), - Qt::QueuedConnection); + connect( + newRoom, &Server_Room::roomInfoChanged, this, [this](auto roomInfo) { broadcastRoomUpdate(roomInfo); }, + Qt::QueuedConnection); } int Server::getUsersCount() const diff --git a/common/server_game.cpp b/common/server_game.cpp index 17ab01dda..9387ccd37 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -87,7 +87,7 @@ Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, if (room->getServer()->getGameShouldPing()) { pingClock = new QTimer(this); - connect(pingClock, SIGNAL(timeout()), this, SLOT(pingClockTimeout())); + connect(pingClock, &QTimer::timeout, this, &Server_Game::pingClockTimeout); pingClock->start(1000); } } diff --git a/common/server_player.cpp b/common/server_player.cpp index 60ebfa3c0..01f6947fa 100644 --- a/common/server_player.cpp +++ b/common/server_player.cpp @@ -386,13 +386,23 @@ void Server_Player::revealTopCardIfNeeded(Server_CardZone *zone, GameEventStorag } } -static Event_CreateToken makeCreateTokenEvent(Server_CardZone *zone, Server_Card *card, int xCoord, int yCoord) +/** + * Creates the create token event. + * By default, will set event's name and color fields to empty if the token is face-down + */ +static Event_CreateToken +makeCreateTokenEvent(Server_CardZone *zone, Server_Card *card, int xCoord, int yCoord, bool revealFacedownInfo = false) { Event_CreateToken event; event.set_zone_name(zone->getName().toStdString()); event.set_card_id(card->getId()); - event.set_card_name(card->getName().toStdString()); - event.set_card_provider_id(card->getProviderId().toStdString()); + event.set_face_down(card->getFaceDown()); + + if (!card->getFaceDown() || revealFacedownInfo) { + event.set_card_name(card->getName().toStdString()); + event.set_card_provider_id(card->getProviderId().toStdString()); + } + event.set_color(card->getColor().toStdString()); event.set_pt(card->getPT().toStdString()); event.set_annotation(card->getAnnotation().toStdString()); @@ -401,7 +411,6 @@ static Event_CreateToken makeCreateTokenEvent(Server_CardZone *zone, Server_Card event.set_y(yCoord); return event; } - static Event_AttachCard makeAttachCardEvent(Server_Card *attachedCard, Server_Card *parentCard = nullptr) { Event_AttachCard event; @@ -417,6 +426,29 @@ static Event_AttachCard makeAttachCardEvent(Server_Card *attachedCard, Server_Ca return event; } +/** + * Determines whether moving the card from startZone to targetZone should cause the card to be destroyed. + */ +static bool +shouldDestroyOnMove(const Server_Card *card, const Server_CardZone *startZone, const Server_CardZone *targetZone) +{ + if (!card->getDestroyOnZoneChange()) { + return false; + } + + if (startZone->getName() == targetZone->getName()) { + return false; + } + + // Allow tokens on the stack + if ((startZone->getName() == "table" || startZone->getName() == "stack") && + (targetZone->getName() == "table" || targetZone->getName() == "stack")) { + return false; + } + + return true; +} + Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges, Server_CardZone *startzone, const QList &_cards, @@ -523,7 +555,7 @@ Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges, } } - if (card->getDestroyOnZoneChange() && (startzone->getName() != targetzone->getName())) { + if (shouldDestroyOnMove(card, startzone, targetzone)) { Event_DestroyCard event; event.set_zone_name(startzone->getName().toStdString()); event.set_card_id(static_cast(card->getId())); @@ -1471,7 +1503,8 @@ Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer const QString cardName = nameFromStdString(cmd.card_name()); const QString cardProviderId = nameFromStdString(cmd.card_provider_id()); if (zone->hasCoords()) { - xCoord = zone->getFreeGridColumn(xCoord, yCoord, cardName, false); + bool dontStackSameName = cmd.face_down(); + xCoord = zone->getFreeGridColumn(xCoord, yCoord, cardName, dontStackSameName); } if (xCoord < 0) { xCoord = 0; @@ -1482,13 +1515,17 @@ Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer auto *card = new Server_Card(cardName, cardProviderId, newCardId(), xCoord, yCoord); card->moveToThread(thread()); - card->setPT(nameFromStdString(cmd.pt())); - card->setColor(nameFromStdString(cmd.color())); + // Client should already prevent face-down tokens from having attributes; this just an extra server-side check + if (!cmd.face_down()) { + card->setColor(nameFromStdString(cmd.color())); + card->setPT(nameFromStdString(cmd.pt())); + } card->setAnnotation(nameFromStdString(cmd.annotation())); card->setDestroyOnZoneChange(cmd.destroy_on_zone_change()); + card->setFaceDown(cmd.face_down()); zone->insertCard(card, xCoord, yCoord); - ges.enqueueGameEvent(makeCreateTokenEvent(zone, card, xCoord, yCoord), playerId); + sendCreateTokenEvents(zone, card, xCoord, yCoord, ges); // check if the token is a replacement for an existing card if (!targetCard) { @@ -1619,6 +1656,38 @@ Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer return Response::RespOk; } +/** + * Creates and sends the events required to properly communicate the given token creation. + * Primarily written to handle creating face-down tokens. + */ +void Server_Player::sendCreateTokenEvents(Server_CardZone *zone, + Server_Card *card, + int xCoord, + int yCoord, + GameEventStorage &ges) +{ + // Token is not face-down; things are easy + if (!card->getFaceDown()) { + ges.enqueueGameEvent(makeCreateTokenEvent(zone, card, xCoord, yCoord), playerId); + return; + } + + // Token is face-down. We have to send different info to each player + auto eventOthers = makeCreateTokenEvent(zone, card, xCoord, yCoord, false); + ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); + + auto eventPrivate = makeCreateTokenEvent(zone, card, xCoord, yCoord, true); + ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); + + // Event_CreateToken didn't use to have face_down field; send attribute event afterward for backwards compatibility + Event_SetCardAttr event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + event.set_attribute(AttrFaceDown); + event.set_attr_value("1"); + ges.enqueueGameEvent(event, playerId); +} + Response::ResponseCode Server_Player::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) { diff --git a/common/server_player.h b/common/server_player.h index ffa12f8b0..b9d414482 100644 --- a/common/server_player.h +++ b/common/server_player.h @@ -84,6 +84,7 @@ private: bool conceded; bool sideboardLocked; void revealTopCardIfNeeded(Server_CardZone *zone, GameEventStorage &ges); + void sendCreateTokenEvents(Server_CardZone *zone, Server_Card *card, int xCoord, int yCoord, GameEventStorage &ges); public: mutable QMutex playerMutex; diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 045b84487..075b3901e 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -36,7 +36,7 @@ Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, idleClientWarningSent(false), timeRunning(0), lastDataReceived(0), lastActionReceived(0) { - connect(server, SIGNAL(pingClockTimeout()), this, SLOT(pingClockTimeout())); + connect(server, &Server::pingClockTimeout, this, &Server_ProtocolHandler::pingClockTimeout); } Server_ProtocolHandler::~Server_ProtocolHandler() diff --git a/common/server_room.cpp b/common/server_room.cpp index 77f720cb5..654666edf 100644 --- a/common/server_room.cpp +++ b/common/server_room.cpp @@ -31,8 +31,9 @@ Server_Room::Server_Room(int _id, permissionLevel(_permissionLevel), privilegeLevel(_privilegeLevel), autoJoin(_autoJoin), joinMessage(_joinMessage), gameTypes(_gameTypes), gamesLock(QReadWriteLock::Recursive) { - connect(this, SIGNAL(gameListChanged(ServerInfo_Game)), this, SLOT(broadcastGameListUpdate(ServerInfo_Game)), - Qt::QueuedConnection); + connect( + this, &Server_Room::gameListChanged, this, [this](auto gameInfo) { broadcastGameListUpdate(gameInfo); }, + Qt::QueuedConnection); } Server_Room::~Server_Room() @@ -352,7 +353,7 @@ void Server_Room::addGame(Server_Game *game) roomInfo.set_room_id(id); gamesLock.lockForWrite(); - connect(game, SIGNAL(gameInfoChanged(ServerInfo_Game)), this, SLOT(broadcastGameListUpdate(ServerInfo_Game))); + connect(game, &Server_Game::gameInfoChanged, this, [this](auto gameInfo) { broadcastGameListUpdate(gameInfo); }); game->gameMutex.lock(); games.insert(game->getGameId(), game); diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 53260e1fd..032cdc95e 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -64,6 +64,9 @@ void SettingsCache::setCardViewExpandedRowsMax(int /* value */) void SettingsCache::setCloseEmptyCardView(const QT_STATE_CHANGED_T /* value */) { } +void SettingsCache::setFocusCardViewSearchBar(QT_STATE_CHANGED_T /* value */) +{ +} void SettingsCache::setKnownMissingFeatures(const QString & /* _knownMissingFeatures */) { } @@ -208,6 +211,13 @@ void SettingsCache::setIncludeRebalancedCards(bool /* _includeRebalancedCards */ void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */) { } +void SettingsCache::setDeckEditorBannerCardComboBoxVisible( + QT_STATE_CHANGED_T /* _deckEditorBannerCardComboBoxVisible */) +{ +} +void SettingsCache::setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T /* _deckEditorTagsWidgetVisible */) +{ +} void SettingsCache::setVisualDeckStorageSortingOrder(int /* _visualDeckStorageSortingOrder */) { } @@ -249,6 +259,18 @@ void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T /* value */) void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T /* value */) { } +void SettingsCache::setDefaultDeckEditorType(int /* value */) +{ +} +void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T /* _enabled */) +{ +} +void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int /* _amount */) +{ +} +void SettingsCache::setVisualDeckEditorSampleHandSize(int /* _amount */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { } diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index de81cee3f..7138f80af 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -12,18 +12,18 @@ SplitCardPart::SplitCardPart(const QString &_name, const QString &_text, const QVariantHash &_properties, - const CardInfoPerSet _setInfo) + const CardInfoPerSet &_setInfo) : name(_name), text(_text), properties(_properties), setInfo(_setInfo) { } const QRegularExpression OracleImporter::formatRegex = QRegularExpression("^format-"); -OracleImporter::OracleImporter(const QString &_dataDir, QObject *parent) : CardDatabase(parent), dataDir(_dataDir) +OracleImporter::OracleImporter(QObject *parent) : QObject(parent) { } -CardSet::Priority OracleImporter::getSetPriority(QString &setType, QString &shortName) +static CardSet::Priority getSetPriority(const QString &setType, const QString &shortName) { if (!setTypePriorities.contains(setType.toLower())) { qDebug() << "warning: Set type" << setType << "unrecognized for prioritization"; @@ -40,30 +40,22 @@ bool OracleImporter::readSetsFromByteArray(const QByteArray &data) QList newSetList; bool ok; - setsMap = QtJson::Json::parse(QString(data), ok).toMap().value("data").toMap(); + auto setsMap = QtJson::Json::parse(QString(data), ok).toMap().value("data").toMap(); if (!ok) { qDebug() << "error: QtJson::Json::parse()"; return false; } QListIterator it(setsMap.values()); - QVariantMap map; - - QString shortName; - QString longName; - QList setCards; - QString setType; - QDate releaseDate; - CardSet::Priority priority; while (it.hasNext()) { - map = it.next().toMap(); - shortName = map.value("code").toString().toUpper(); - longName = map.value("name").toString(); - setCards = map.value("cards").toList(); - setType = map.value("type").toString(); - releaseDate = map.value("releaseDate").toDate(); - priority = getSetPriority(setType, shortName); + QVariantMap map = it.next().toMap(); + QString shortName = map.value("code").toString().toUpper(); + QString longName = map.value("name").toString(); + QList setCards = map.value("cards").toList(); + QString setType = map.value("type").toString(); + QDate releaseDate = map.value("releaseDate").toDate(); + CardSet::Priority priority = getSetPriority(setType, shortName); // capitalize set type if (setType.length() > 0) { // basic grammar for words that aren't capitalized, like in "From the Vault" @@ -93,13 +85,16 @@ bool OracleImporter::readSetsFromByteArray(const QByteArray &data) return true; } -QString OracleImporter::getMainCardType(const QStringList &typeList) +static QString getMainCardType(const QStringList &typeList) { if (typeList.isEmpty()) { return {}; } - for (const auto &type : mainCardTypes) { + static const QStringList typePriority = {"Planeswalker", "Creature", "Land", "Sorcery", + "Instant", "Artifact", "Enchantment"}; + + for (const auto &type : typePriority) { if (typeList.contains(type)) { return type; } @@ -108,12 +103,33 @@ QString OracleImporter::getMainCardType(const QStringList &typeList) return typeList.first(); } +/** + * Sorts and deduplicates the color chars in the string by WUBRG order. + * + * @param colors The string containing the color chars. Will be modified in-place + */ +static void sortAndReduceColors(QString &colors) +{ + // sort + static const QHash colorOrder{{'W', 0}, {'U', 1}, {'B', 2}, {'R', 3}, {'G', 4}}; + std::sort(colors.begin(), colors.end(), + [](const QChar a, const QChar b) { return colorOrder.value(a, INT_MAX) < colorOrder.value(b, INT_MAX); }); + // reduce + QChar lastChar = '\0'; + for (int i = 0; i < colors.size(); ++i) { + if (colors.at(i) == lastChar) + colors.remove(i, 1); + else + lastChar = colors.at(i); + } +} + CardInfoPtr OracleImporter::addCard(QString name, - QString text, + const QString &text, bool isToken, QVariantHash properties, - QList &relatedCards, - CardInfoPerSet setInfo) + const QList &relatedCards, + const CardInfoPerSet &setInfo) { // Workaround for card name weirdness name = name.replace("Æ", "AE"); @@ -197,7 +213,7 @@ CardInfoPtr OracleImporter::addCard(QString name, return newCard; } -QString OracleImporter::getStringPropertyFromMap(const QVariantMap &card, const QString &propertyName) +static QString getStringPropertyFromMap(const QVariantMap &card, const QString &propertyName) { return card.contains(propertyName) ? card.value(propertyName).toString() : QString(""); } @@ -442,29 +458,12 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList return numCards; } -void OracleImporter::sortAndReduceColors(QString &colors) -{ - // sort - const QHash colorOrder{{'W', 0}, {'U', 1}, {'B', 2}, {'R', 3}, {'G', 4}}; - std::sort(colors.begin(), colors.end(), [&colorOrder](const QChar a, const QChar b) { - return colorOrder.value(a, INT_MAX) < colorOrder.value(b, INT_MAX); - }); - // reduce - QChar lastChar = '\0'; - for (int i = 0; i < colors.size(); ++i) { - if (colors.at(i) == lastChar) - colors.remove(i, 1); - else - lastChar = colors.at(i); - } -} - int OracleImporter::startImport() { int setCards = 0, setIndex = 0; // add an empty set for tokens - CardSetPtr tokenSet = CardSet::newInstance(TOKENS_SETNAME, tr("Dummy set containing tokens"), "Tokens"); - sets.insert(TOKENS_SETNAME, tokenSet); + CardSetPtr tokenSet = CardSet::newInstance(CardSet::TOKENS_SETNAME, tr("Dummy set containing tokens"), "Tokens"); + sets.insert(CardSet::TOKENS_SETNAME, tokenSet); for (const SetToDownload &curSetToParse : allSets) { CardSetPtr newSet = @@ -494,6 +493,7 @@ bool OracleImporter::saveToFile(const QString &fileName, const QString &sourceUr void OracleImporter::clear() { - CardDatabase::clear(); + sets.clear(); + cards.clear(); allSets.clear(); } diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index 1231d1a81..314723706 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include // many users prefer not to see these sets with non english arts @@ -92,7 +92,10 @@ public: class SplitCardPart { public: - SplitCardPart(const QString &_name, const QString &_text, const QVariantHash &_properties, CardInfoPerSet setInfo); + SplitCardPart(const QString &_name, + const QString &_text, + const QVariantHash &_properties, + const CardInfoPerSet &setInfo); inline const QString &getName() const { return name; @@ -117,48 +120,49 @@ private: CardInfoPerSet setInfo; }; -class OracleImporter : public CardDatabase +class OracleImporter : public QObject { Q_OBJECT private: - const QStringList mainCardTypes = {"Planeswalker", "Creature", "Land", "Sorcery", - "Instant", "Artifact", "Enchantment"}; static const QRegularExpression formatRegex; - QList allSets; - QVariantMap setsMap; - QString dataDir; - QString getMainCardType(const QStringList &typeList); + /** + * The cards, indexed by name. + */ + CardNameMap cards; + + /** + * The sets, indexed by short name. + */ + SetNameMap sets; + + QList allSets; + CardInfoPtr addCard(QString name, - QString text, + const QString &text, bool isToken, QVariantHash properties, - QList &relatedCards, - CardInfoPerSet setInfo); + const QList &relatedCards, + const CardInfoPerSet &setInfo); signals: void setIndexChanged(int cardsImported, int setIndex, const QString &setName); void dataReadProgress(int bytesRead, int totalBytes); public: - explicit OracleImporter(const QString &_dataDir, QObject *parent = nullptr); - CardSet::Priority getSetPriority(QString &setType, QString &shortName); + explicit OracleImporter(QObject *parent = nullptr); bool readSetsFromByteArray(const QByteArray &data); int startImport(); bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion); - int importCardsFromSet(const CardSetPtr ¤tSet, const QList &cards); + int importCardsFromSet(const CardSetPtr ¤tSet, const QList &cardsList); + const CardNameMap &getCardList() const + { + return cards; + } QList &getSets() { return allSets; } - const QString &getDataDir() const - { - return dataDir; - } void clear(); - -protected: - inline QString getStringPropertyFromMap(const QVariantMap &card, const QString &propertyName); - void sortAndReduceColors(QString &colors); }; #endif diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index c1c366948..bea87dde2 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -58,9 +58,9 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) QString dummy = QT_TRANSLATE_NOOP("i18n", "English"); settings = new QSettings(SettingsCache::instance().getSettingsPath() + "global.ini", QSettings::IniFormat, this); - connect(&SettingsCache::instance(), SIGNAL(langChanged()), this, SLOT(updateLanguage())); + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &OracleWizard::updateLanguage); - importer = new OracleImporter(SettingsCache::instance().getDataPath(), this); + importer = new OracleImporter(this); nam = new QNetworkAccessManager(this); @@ -158,7 +158,7 @@ IntroPage::IntroPage(QWidget *parent) : OracleWizardPage(parent) languageBox->setCurrentIndex(index); } - connect(languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); + connect(languageBox, qOverload(&QComboBox::currentIndexChanged), this, &IntroPage::languageBoxChanged); auto *layout = new QGridLayout(this); layout->addWidget(label, 0, 0, 1, 2); @@ -226,10 +226,10 @@ LoadSetsPage::LoadSetsPage(QWidget *parent) : OracleWizardPage(parent) urlRadioButton->setChecked(true); urlButton = new QPushButton(this); - connect(urlButton, SIGNAL(clicked()), this, SLOT(actRestoreDefaultUrl())); + connect(urlButton, &QPushButton::clicked, this, &LoadSetsPage::actRestoreDefaultUrl); fileButton = new QPushButton(this); - connect(fileButton, SIGNAL(clicked()), this, SLOT(actLoadSetsFile())); + connect(fileButton, &QPushButton::clicked, this, &LoadSetsPage::actLoadSetsFile); auto *layout = new QGridLayout(this); layout->addWidget(urlRadioButton, 0, 0); @@ -241,7 +241,7 @@ LoadSetsPage::LoadSetsPage(QWidget *parent) : OracleWizardPage(parent) layout->addWidget(progressLabel, 4, 0); layout->addWidget(progressBar, 4, 1); - connect(&watcher, SIGNAL(finished()), this, SLOT(importFinished())); + connect(&watcher, &QFutureWatcher::finished, this, &LoadSetsPage::importFinished); setLayout(layout); } @@ -387,8 +387,8 @@ void LoadSetsPage::downloadSetsFile(const QUrl &url) auto *reply = wizard()->nam->get(QNetworkRequest(url)); - connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSetsFile())); - connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(actDownloadProgressSetsFile(qint64, qint64))); + connect(reply, &QNetworkReply::finished, this, &LoadSetsPage::actDownloadFinishedSetsFile); + connect(reply, &QNetworkReply::downloadProgress, this, &LoadSetsPage::actDownloadProgressSetsFile); } void LoadSetsPage::actDownloadProgressSetsFile(qint64 received, qint64 total) @@ -598,7 +598,7 @@ SaveSetsPage::SaveSetsPage(QWidget *parent) : OracleWizardPage(parent) void SaveSetsPage::cleanupPage() { wizard()->importer->clear(); - disconnect(wizard()->importer, SIGNAL(setIndexChanged(int, int, const QString &)), nullptr, nullptr); + disconnect(wizard()->importer, &OracleImporter::setIndexChanged, nullptr, nullptr); } void SaveSetsPage::initializePage() @@ -611,8 +611,7 @@ void SaveSetsPage::initializePage() return; } messageLog->show(); - connect(wizard()->importer, SIGNAL(setIndexChanged(int, int, const QString &)), this, - SLOT(updateTotalProgress(int, int, const QString &))); + connect(wizard()->importer, &OracleImporter::setIndexChanged, this, &SaveSetsPage::updateTotalProgress); if (!wizard()->importer->startImport()) { QMessageBox::critical(this, tr("Error"), tr("No set has been imported.")); diff --git a/oracle/src/pagetemplates.cpp b/oracle/src/pagetemplates.cpp index b4d8d358b..858fd8022 100644 --- a/oracle/src/pagetemplates.cpp +++ b/oracle/src/pagetemplates.cpp @@ -23,7 +23,7 @@ SimpleDownloadFilePage::SimpleDownloadFilePage(QWidget *parent) : OracleWizardPa progressBar = new QProgressBar(this); urlButton = new QPushButton(this); - connect(urlButton, SIGNAL(clicked()), this, SLOT(actRestoreDefaultUrl())); + connect(urlButton, &QPushButton::clicked, this, &SimpleDownloadFilePage::actRestoreDefaultUrl); defaultPathCheckBox = new QCheckBox(this); @@ -91,8 +91,8 @@ void SimpleDownloadFilePage::downloadFile(QUrl url) { QNetworkReply *reply = wizard()->nam->get(QNetworkRequest(url)); - connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinished())); - connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(actDownloadProgress(qint64, qint64))); + connect(reply, &QNetworkReply::finished, this, &SimpleDownloadFilePage::actDownloadFinished); + connect(reply, &QNetworkReply::downloadProgress, this, &SimpleDownloadFilePage::actDownloadProgress); } void SimpleDownloadFilePage::actDownloadProgress(qint64 received, qint64 total) diff --git a/oracle/translations/oracle_pt_BR.ts b/oracle/translations/oracle_pt_BR.ts index 59ea83ec1..0a3439bf5 100644 --- a/oracle/translations/oracle_pt_BR.ts +++ b/oracle/translations/oracle_pt_BR.ts @@ -63,7 +63,7 @@ Sets file (%1) Sets JSON file (%1) - + Arquivo de expansões (%1) diff --git a/tests/carddatabase/carddatabase_test.cpp b/tests/carddatabase/carddatabase_test.cpp index 752143f67..4818cd22e 100644 --- a/tests/carddatabase/carddatabase_test.cpp +++ b/tests/carddatabase/carddatabase_test.cpp @@ -18,9 +18,9 @@ TEST(CardDatabaseTest, LoadXml) // load dummy cards and test result db->loadCardDatabases(); - ASSERT_EQ(6, db->getCardList().size()) << "Wrong card count after load"; - ASSERT_EQ(3, db->getSetList().size()) << "Wrong sets count after load"; - ASSERT_EQ(2, db->getAllMainCardTypes().size()) << "Wrong types count after load"; + ASSERT_EQ(8, db->getCardList().size()) << "Wrong card count after load"; + ASSERT_EQ(4, db->getSetList().size()) << "Wrong sets count after load"; + ASSERT_EQ(3, db->getAllMainCardTypes().size()) << "Wrong types count after load"; ASSERT_EQ(Ok, db->getLoadStatus()) << "Wrong status after load"; // ensure the card database is empty after clear() diff --git a/tests/carddatabase/data/cards.xml b/tests/carddatabase/data/cards.xml index 24ede9dff..f235ab4f9 100644 --- a/tests/carddatabase/data/cards.xml +++ b/tests/carddatabase/data/cards.xml @@ -31,5 +31,31 @@ 4/4 + + Not Dead + Not a Card + 0 + Dead! + + 333 + B + B + 1 + Instant + + + + Truth + Not a Card + 0 + Truth! + + 444 + U + 2U + 2 + Instant + + diff --git a/tests/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp index 4fa139514..b29660159 100644 --- a/tests/carddatabase/filter_string_test.cpp +++ b/tests/carddatabase/filter_string_test.cpp @@ -19,11 +19,14 @@ protected: void SetUp() override { cat = CardDatabaseManager::getInstance()->getCardBySimpleName("Cat"); + notDeadAfterAll = CardDatabaseManager::getInstance()->getCardBySimpleName("Not Dead"); + truth = CardDatabaseManager::getInstance()->getCardBySimpleName("Truth"); } - // void TearDown() override {} CardData cat; + CardData notDeadAfterAll; + CardData truth; }; QUERY(Empty, cat, "", true) @@ -31,14 +34,18 @@ QUERY(Typing, cat, "t", true) QUERY(NonMatchingType, cat, "t:kithkin", false) QUERY(MatchingType, cat, "t:creature", true) -QUERY(Not1, cat, "not t:kithkin", true) -QUERY(Not2, cat, "not t:creature", false) +QUERY(Not1, cat, "NOT t:kithkin", true) +QUERY(Not2, cat, "NOT t:creature", false) +QUERY(NonKeyword1, cat, "not t:kithkin", false) +QUERY(NonKeyword2, cat, "t:bat or t:creature", false) +QUERY(NonKeyword3, notDeadAfterAll, "not dead", true) +QUERY(NonKeyword4, truth, "truth or trail", false) QUERY(Case, cat, "t:cReAtUrE", true) QUERY(And, cat, "t:creature t:creature", true) QUERY(And2, cat, "t:creature t:sorcery", false) -QUERY(Or, cat, "t:bat or t:creature", true) +QUERY(Or, cat, "t:bat OR t:creature", true) QUERY(Cmc1, cat, "cmc=2", true) QUERY(Cmc2, cat, "cmc>3", false) diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index c5cf99b68..db7dd9353 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -68,6 +68,9 @@ void SettingsCache::setCardViewExpandedRowsMax(int /* value */) void SettingsCache::setCloseEmptyCardView(QT_STATE_CHANGED_T /* value */) { } +void SettingsCache::setFocusCardViewSearchBar(QT_STATE_CHANGED_T /* value */) +{ +} void SettingsCache::setKnownMissingFeatures(const QString & /* _knownMissingFeatures */) { } @@ -212,6 +215,13 @@ void SettingsCache::setIncludeRebalancedCards(bool /* _includeRebalancedCards */ void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */) { } +void SettingsCache::setDeckEditorBannerCardComboBoxVisible( + QT_STATE_CHANGED_T /* _deckEditorBannerCardComboBoxVisible */) +{ +} +void SettingsCache::setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T /* _deckEditorTagsWidgetVisible */) +{ +} void SettingsCache::setVisualDeckStorageSortingOrder(int /* _visualDeckStorageSortingOrder */) { } @@ -253,6 +263,18 @@ void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T /* value */) void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T /* value */) { } +void SettingsCache::setDefaultDeckEditorType(int /* value */) +{ +} +void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T /* _enabled */) +{ +} +void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int /* _amount */) +{ +} +void SettingsCache::setVisualDeckEditorSampleHandSize(int /* _amount */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { } diff --git a/tests/loading_from_clipboard/clipboard_testing.cpp b/tests/loading_from_clipboard/clipboard_testing.cpp index ad8db3dbf..b2021bde5 100644 --- a/tests/loading_from_clipboard/clipboard_testing.cpp +++ b/tests/loading_from_clipboard/clipboard_testing.cpp @@ -2,17 +2,6 @@ #include -void Result::operator()(const InnerDecklistNode *innerDecklistNode, const DecklistCardNode *card) -{ - if (innerDecklistNode->getName() == DECK_ZONE_MAIN) { - mainboard.append({card->getName().toStdString(), card->getNumber()}); - } else if (innerDecklistNode->getName() == DECK_ZONE_SIDE) { - sideboard.append({card->getName().toStdString(), card->getNumber()}); - } else { - FAIL(); - } -} - void testEmpty(const QString &clipboard) { QString cp(clipboard); @@ -33,9 +22,22 @@ void testDeck(const QString &clipboard, const Result &result) ASSERT_EQ(result.name, deckList.getName().toStdString()); ASSERT_EQ(result.comments, deckList.getComments().toStdString()); - Result decklistBuilder; - deckList.forEachCard(decklistBuilder); + CardRows mainboard; + CardRows sideboard; - ASSERT_EQ(result.mainboard, decklistBuilder.mainboard); - ASSERT_EQ(result.sideboard, decklistBuilder.sideboard); + auto extractCards = [&mainboard, &sideboard](const InnerDecklistNode *innerDecklistNode, + const DecklistCardNode *card) { + if (innerDecklistNode->getName() == DECK_ZONE_MAIN) { + mainboard.append({card->getName().toStdString(), card->getNumber()}); + } else if (innerDecklistNode->getName() == DECK_ZONE_SIDE) { + sideboard.append({card->getName().toStdString(), card->getNumber()}); + } else { + FAIL(); + } + }; + + deckList.forEachCard(extractCards); + + ASSERT_EQ(result.mainboard, mainboard); + ASSERT_EQ(result.sideboard, sideboard); } diff --git a/tests/loading_from_clipboard/clipboard_testing.h b/tests/loading_from_clipboard/clipboard_testing.h index 272801126..00d73b636 100644 --- a/tests/loading_from_clipboard/clipboard_testing.h +++ b/tests/loading_from_clipboard/clipboard_testing.h @@ -5,25 +5,20 @@ #include "gtest/gtest.h" +// using std types because qt types aren't understood by gtest (without this you'll get less nice errors) +using CardRows = QVector>; + struct Result { - // using std types because qt types aren't understood by gtest (without this you'll get less nice errors) - using CardRows = QVector>; std::string name; std::string comments; CardRows mainboard; CardRows sideboard; - Result() - { - } - Result(std::string _name, std::string _comments, CardRows _mainboard, CardRows _sideboard) : name(_name), comments(_comments), mainboard(_mainboard), sideboard(_sideboard) { } - - void operator()(const InnerDecklistNode *innerDecklistNode, const DecklistCardNode *card); }; void testEmpty(const QString &clipboard); diff --git a/webclient/package-lock.json b/webclient/package-lock.json index f5c2ea22e..fa93ceb06 100644 --- a/webclient/package-lock.json +++ b/webclient/package-lock.json @@ -17962,9 +17962,10 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -32825,9 +32826,9 @@ } }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "requires": { "randombytes": "^2.1.0" }