diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 79bc1b815..7fb403549 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: @@ -132,7 +133,7 @@ jobs: CACHE: /tmp/${{matrix.distro}}${{matrix.version}}-cache # ${{runner.temp}} does not work? # Cache size over the entire repo is 10Gi: # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy - CCACHE_SIZE: 200M + CCACHE_SIZE: 500M steps: - name: Checkout @@ -386,7 +387,9 @@ jobs: tools: ${{matrix.qt_tools}} modules: ${{matrix.qt_modules}} - - name: Run vcpkg + # TODO: re-enable when https://github.com/lukka/run-vcpkg/issues/243 is fixed + - if: false + name: Run vcpkg (disabled) uses: lukka/run-vcpkg@v11 with: runVcpkgInstall: true 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 0650ae9f4..f1aa4a261 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -135,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 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/tab_edhrec_main.cpp b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp index 0bd4e843a..2eed749af 100644 --- a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec_main.cpp @@ -356,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/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/ui/widgets/cards/additional_info/mana_symbol_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp index e42e93444..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 @@ -66,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/card_info_picture_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp index 4ee3de5eb..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 @@ -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_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/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 9adf8e095..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 @@ -91,7 +91,7 @@ void DeckEditorDeckDockWidget::createDeckDock() bannerCardLabel = new QLabel(); bannerCardLabel->setObjectName("bannerCardLabel"); bannerCardLabel->setText(tr("Banner Card")); - bannerCardLabel->setHidden(SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); + bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); bannerCardComboBox = new QComboBox(this); connect(deckModel, &DeckListModel::dataChanged, this, [this]() { // Delay the update to avoid race conditions @@ -228,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() @@ -294,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); @@ -314,7 +321,7 @@ 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) @@ -362,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()); @@ -422,7 +433,7 @@ void DeckEditorDeckDockWidget::actSwapCard() deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); if (isModified) { - emit deckChanged(); + emit deckModified(); } update(); @@ -517,7 +528,7 @@ void DeckEditorDeckDockWidget::actRemoveCard() deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); if (isModified) { - emit deckChanged(); + emit deckModified(); } } @@ -535,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) @@ -563,6 +574,7 @@ 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:")); 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 0a9ebcd65..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: 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 681282fc4..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 @@ -58,6 +58,8 @@ 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) 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_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 9d82463be..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 @@ -150,6 +150,18 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, 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) 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 7868627e2..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); + + 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 0914a472e..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 @@ -190,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() 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_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.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 25cb5a1e5..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,32 +33,42 @@ 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); } } @@ -72,13 +82,15 @@ void VisualDeckStorageTagFilterWidget::refreshTags() 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(); } } } 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/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index 99f11efd6..c0195af87 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" @@ -686,6 +687,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); @@ -699,8 +704,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); @@ -756,12 +762,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); @@ -811,6 +825,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")); @@ -831,6 +846,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 32ce5a410..f0b9c62c9 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -149,6 +149,7 @@ private: QCheckBox clickPlaysAllSelectedCheckBox; QCheckBox playToStackCheckBox; QCheckBox closeEmptyCardViewCheckBox; + QCheckBox focusCardViewSearchBarCheckBox; QCheckBox annotateTokensCheckBox; QCheckBox useTearOffMenusCheckBox; QCheckBox tapAnimationCheckBox; @@ -157,6 +158,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/board/abstract_card_drag_item.cpp b/cockatrice/src/game/board/abstract_card_drag_item.cpp index 0c7acd917..d7b0b7d46 100644 --- a/cockatrice/src/game/board/abstract_card_drag_item.cpp +++ b/cockatrice/src/game/board/abstract_card_drag_item.cpp @@ -19,6 +19,7 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, if (parentDrag) { parentDrag->addChildDrag(this); setZValue(2000000007 + hotSpot.x() * 1000000 + hotSpot.y() * 1000 + 1000); + connect(parentDrag, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater); } else { hotSpot = QPointF{qBound(0.0, hotSpot.x(), static_cast(CARD_WIDTH - 1)), qBound(0.0, hotSpot.y(), static_cast(CARD_HEIGHT - 1))}; @@ -39,12 +40,8 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, prepareGeometryChange(); update(); }); -} -AbstractCardDragItem::~AbstractCardDragItem() -{ - for (int i = 0; i < childDrags.size(); i++) - delete childDrags[i]; + connect(item, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater); } QPainterPath AbstractCardDragItem::shape() const diff --git a/cockatrice/src/game/board/abstract_card_drag_item.h b/cockatrice/src/game/board/abstract_card_drag_item.h index 85ce38b15..a411a24d1 100644 --- a/cockatrice/src/game/board/abstract_card_drag_item.h +++ b/cockatrice/src/game/board/abstract_card_drag_item.h @@ -26,7 +26,6 @@ public: return Type; } AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); - ~AbstractCardDragItem() override; QRectF boundingRect() const override { return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); diff --git a/cockatrice/src/game/board/card_drag_item.cpp b/cockatrice/src/game/board/card_drag_item.cpp index d21e9168b..ab4431958 100644 --- a/cockatrice/src/game/board/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/player/player.cpp b/cockatrice/src/game/player/player.cpp index 2380454b9..8ce7b0055 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -145,7 +145,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T PileZone *sb = addZone(new PileZone(this, "sb", false, false, playerArea)); sb->setVisible(false); - table = addZone(new TableZone(this, this)); + table = addZone(new TableZone(this, "table", this)); connect(table, &TableZone::sizeChanged, this, &Player::updateBoundingRect); stack = addZone(new StackZone(this, (int)table->boundingRect().height(), this)); @@ -401,6 +401,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); @@ -458,6 +461,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; mCardCounters = nullptr; @@ -833,6 +837,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...")); @@ -2726,19 +2735,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; } diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 960a24057..9062ac5b4 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -253,7 +253,7 @@ public: private: TabGame *game; QMenu *sbMenu, *countersMenu, *sayMenu, *createPredefinedTokenMenu, *mRevealLibrary, *mLendLibrary, *mRevealTopCard, - *mRevealHand, *mRevealRandomHandCard, *mRevealRandomGraveyardCard, *mCardCounters; + *mRevealHand, *mRevealRandomHandCard, *mRevealRandomGraveyardCard, *mCustomZones, *mCardCounters; TearOffMenu *moveGraveMenu, *moveRfgMenu, *graveMenu, *moveHandMenu, *handMenu, *libraryMenu, *topLibraryMenu, *bottomLibraryMenu, *rfgMenu, *playerMenu; QList playerLists; diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game/zones/card_zone.cpp index 05b4c35b9..089ed0a2d 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game/zones/card_zone.cpp @@ -92,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(); } diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index e214b2e09..cee782560 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -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 f3fbdccb6..3d464e6f3 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_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_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 1721831ac..f0430f549 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -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 045f47242..ed25801f6 100644 --- a/cockatrice/src/server/message_log_widget.cpp +++ b/cockatrice/src/server/message_log_widget.cpp @@ -87,6 +87,8 @@ QPair MessageLogWidget::getFromStr(CardZone *zone, QString car fromStr = tr(" from sideboard"); } else if (zoneName == STACK_ZONE_NAME) { fromStr = tr(" from the stack"); + } else { + fromStr = tr(" from custom zone '%1'").arg(zoneName); } if (!cardNameContainsStartZone) { @@ -322,13 +324,16 @@ void MessageLogWidget::logMoveCard(Player *player, } 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)); } } 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 cb5025540..1635d8420 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -255,6 +255,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(); @@ -289,10 +290,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(); @@ -373,6 +376,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; @@ -795,6 +804,12 @@ 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; @@ -811,6 +826,13 @@ void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int _am 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 4555681e0..f4cc95bb3 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -74,6 +74,7 @@ signals: void visualDeckStorageSelectionAnimationChanged(bool enabled); void visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged(bool enabled); void visualDatabaseDisplayFilterToMostRecentSetsAmountChanged(int amount); + void visualDeckEditorSampleHandSizeAmountChanged(int amount); void horizontalHandChanged(); void handJustificationChanged(); void invertVerticalCoordinateChanged(); @@ -155,8 +156,10 @@ private: bool visualDeckStorageAlwaysConvert; bool visualDeckStorageInGame; bool visualDeckStorageSelectionAnimation; + int defaultDeckEditorType; bool visualDatabaseDisplayFilterToMostRecentSetsEnabled; int visualDatabaseDisplayFilterToMostRecentSetsAmount; + int visualDeckEditorSampleHandSize; bool horizontalHand; bool invertVerticalCoordinate; int minPlayersForMultiColumnLayout; @@ -185,6 +188,7 @@ private: int cardViewInitialRowsMax; int cardViewExpandedRowsMax; bool closeEmptyCardView; + bool focusCardViewSearchBar; int pixmapCacheSize; int networkCacheSize; int redirectCacheTtl; @@ -490,6 +494,10 @@ public: { return visualDeckStorageSelectionAnimation; } + int getDefaultDeckEditorType() const + { + return defaultDeckEditorType; + } bool getVisualDatabaseDisplayFilterToMostRecentSetsEnabled() const { return visualDatabaseDisplayFilterToMostRecentSetsEnabled; @@ -498,6 +506,10 @@ public: { return visualDatabaseDisplayFilterToMostRecentSetsAmount; } + int getVisualDeckEditorSampleHandSize() const + { + return visualDeckEditorSampleHandSize; + } bool getHorizontalHand() const { return horizontalHand; @@ -696,6 +708,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; @@ -724,6 +737,10 @@ public: { return closeEmptyCardView; } + bool getFocusCardViewSearchBar() const + { + return focusCardViewSearchBar; + } ShortcutsSettings &shortcuts() const { return *shortcutsSettings; @@ -843,8 +860,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/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/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 93048d3cf..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 */) { } @@ -256,12 +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/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/mocks.cpp b/tests/carddatabase/mocks.cpp index a2ca73fb1..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 */) { } @@ -260,12 +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 */) { }