From 62f7c7f9cec795a7c7ae70fd1db7b03d54191787 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Mon, 6 Jan 2025 00:12:20 +0100 Subject: [PATCH] New visual deck storage (#5290) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add TabDeckStorageVisual * Visual Deck Storage * Add BannerCard to .cod format, use it in the visual deck storage widget. * Show filename instead of deckname if deck name is empty. * Lint. * Don't delint cmake list through hooks. * Add deck loading functionality. * Open Decks on double click, not single click. * Void event for now. * Fix build issue with overload? * Fix build issue with overload? * Include QDebug. * Turn the tab into a widget. * Move the signals down to the widget, move the connections and slots up to the parent widgets. * No banner card equals an empty CardInfoPtr. * Add an option to sort by filename or last modified. * Flip last modified comparison. * Lint. * Don't open decks twice in the storage tab. * Fix unload deck not working by showing/hiding widgets instead of adding/removing to layout. * Add a search bar. * Add a card size slider. * Lint. * Lint. * Lint. * Fix settings mocks. * No need to QDebug. * No need to QDebug. * Member variable. * Member variable. * Non-lambda. * Change set to list conversion. * Specify overload. * Include MouseEvent * Adjust font size dynamically. * Add an option to show the visual deck storage on database load. * Fix the close button not working on the tab, add an option to launch the visual deck storage tab to Cockatrice menu. * Override virtual functions. * Correct tab text. * Add a setting to remember last used sorting order for visual deck storage widget. * Update banner card combo box correctly. * Fix mocks. --------- Co-authored-by: Lukas BrĂ¼bach Co-authored-by: Zach H --- cockatrice/CMakeLists.txt | 4 + .../src/client/tabs/tab_deck_editor.cpp | 89 +++++++++++++ cockatrice/src/client/tabs/tab_deck_editor.h | 5 + cockatrice/src/client/tabs/tab_game.cpp | 52 +++++++- cockatrice/src/client/tabs/tab_game.h | 7 +- cockatrice/src/client/tabs/tab_supervisor.cpp | 10 ++ cockatrice/src/client/tabs/tab_supervisor.h | 2 + .../tab_deck_storage_visual.cpp | 104 +++++++++++++++ .../tab_deck_storage_visual.h | 56 ++++++++ ..._info_picture_with_text_overlay_widget.cpp | 57 ++++++--- ...rd_info_picture_with_text_overlay_widget.h | 2 +- .../ui/widgets/cards/card_size_widget.cpp | 9 +- .../ui/widgets/cards/card_size_widget.h | 4 +- .../deck_preview_card_picture_widget.cpp | 53 ++++++++ .../cards/deck_preview_card_picture_widget.h | 40 ++++++ .../visual_deck_storage_search_widget.cpp | 58 +++++++++ .../visual_deck_storage_search_widget.h | 28 ++++ .../visual_deck_storage_widget.cpp | 121 ++++++++++++++++++ .../visual_deck_storage_widget.h | 51 ++++++++ cockatrice/src/client/ui/window_main.cpp | 14 ++ cockatrice/src/client/ui/window_main.h | 10 +- cockatrice/src/dialogs/dlg_settings.cpp | 6 + cockatrice/src/dialogs/dlg_settings.h | 1 + .../src/game/cards/card_database_model.h | 5 +- cockatrice/src/settings/cache_settings.cpp | 22 ++++ cockatrice/src/settings/cache_settings.h | 19 +++ common/decklist.cpp | 8 +- common/decklist.h | 10 +- dbconverter/src/mocks.cpp | 9 ++ tests/carddatabase/mocks.cpp | 9 ++ 30 files changed, 834 insertions(+), 31 deletions(-) create mode 100644 cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp create mode 100644 cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h create mode 100644 cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp create mode 100644 cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h create mode 100644 cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp create mode 100644 cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h create mode 100644 cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp create mode 100644 cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 324eab6f6..c88aca46b 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -160,6 +160,10 @@ set(cockatrice_SOURCES src/client/ui/window_main.cpp src/game/zones/view_zone_widget.cpp src/game/zones/view_zone.cpp + src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp + src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp + src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp + src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp ${VERSION_STRING_CPP} ) diff --git a/cockatrice/src/client/tabs/tab_deck_editor.cpp b/cockatrice/src/client/tabs/tab_deck_editor.cpp index 9768c028f..896ccde9a 100644 --- a/cockatrice/src/client/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/tab_deck_editor.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -95,6 +97,16 @@ void TabDeckEditor::createDeckDock() commentsEdit->setObjectName("commentsEdit"); commentsLabel->setBuddy(commentsEdit); connect(commentsEdit, SIGNAL(textChanged()), this, SLOT(updateComments())); + bannerCardLabel = new QLabel(); + bannerCardLabel->setObjectName("bannerCardLabel"); + bannerCardLabel->setText(tr("Banner Card")); + bannerCardComboBox = new QComboBox(this); + connect(deckModel, &DeckListModel::dataChanged, this, [this]() { + // Delay the update to avoid race conditions + QTimer::singleShot(100, this, &TabDeckEditor::updateBannerCardComboBox); + }); + connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &TabDeckEditor::setBannerCard); aIncrement = new QAction(QString(), this); aIncrement->setIcon(QPixmap("theme:icons/increment")); @@ -128,6 +140,9 @@ void TabDeckEditor::createDeckDock() upperLayout->addWidget(commentsLabel, 1, 0); upperLayout->addWidget(commentsEdit, 1, 1); + upperLayout->addWidget(bannerCardLabel, 2, 0); + upperLayout->addWidget(bannerCardComboBox, 2, 1); + hashLabel1 = new QLabel(); hashLabel1->setObjectName("hashLabel1"); auto *hashSizePolicy = new QSizePolicy(); @@ -802,6 +817,71 @@ void TabDeckEditor::updateComments() setSaveStatus(true); } +void TabDeckEditor::updateBannerCardComboBox() +{ + // Store the current text of the combo box + QString currentText = bannerCardComboBox->currentText(); + + // Block signals temporarily + bool wasBlocked = bannerCardComboBox->blockSignals(true); + + // Clear the existing items in the combo box + bannerCardComboBox->clear(); + + // Prepare the new items with deduplication + QSet bannerCardSet; + InnerDecklistNode *listRoot = deckModel->getDeckList()->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + if (!currentCard) + continue; + + for (int k = 0; k < currentCard->getNumber(); ++k) { + CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); + if (info) { + bannerCardSet.insert(currentCard->getName()); + } + } + } + } + + // Convert the QSet to a sorted QStringList + QStringList bannerCardChoices; + for (const QString &entry : bannerCardSet) { + bannerCardChoices.append(entry); + } + bannerCardChoices.sort(Qt::CaseInsensitive); + + // Populate the combo box with new items + bannerCardComboBox->addItems(bannerCardChoices); + + // Try to restore the previous selection by finding the currentText + int restoredIndex = bannerCardComboBox->findText(currentText); + if (restoredIndex != -1) { + bannerCardComboBox->setCurrentIndex(restoredIndex); + } else { + // Add a placeholder "-" and set it as the current selection + int bannerIndex = bannerCardComboBox->findText(deckModel->getDeckList()->getBannerCard()); + if (bannerIndex != -1) { + bannerCardComboBox->setCurrentIndex(bannerIndex); + } else { + bannerCardComboBox->insertItem(0, "-"); + bannerCardComboBox->setCurrentIndex(0); + } + } + + // Restore the previous signal blocking state + bannerCardComboBox->blockSignals(wasBlocked); +} + +void TabDeckEditor::setBannerCard(int /* changedIndex */) +{ + qDebug() << "Banner card was set to: " << bannerCardComboBox->currentText(); + deckModel->getDeckList()->setBannerCard(bannerCardComboBox->currentText()); +} + void TabDeckEditor::updateCardInfo(CardInfoPtr _card) { cardInfo->setCard(_card); @@ -941,6 +1021,7 @@ void TabDeckEditor::actLoadDeck() QString fileName = dialog.selectedFiles().at(0); openDeckFromFile(fileName, deckOpenLocation); + updateBannerCardComboBox(); } void TabDeckEditor::actOpenRecent(const QString &fileName) @@ -966,6 +1047,10 @@ void TabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation d auto *l = new DeckLoader; if (l->loadFromFile(fileName, fmt)) { SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName); + updateBannerCardComboBox(); + if (!l->getBannerCard().isEmpty()) { + bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText(l->getBannerCard())); + } if (deckOpenLocation == NEW_TAB) { emit openDeckEditor(l); } else { @@ -1429,10 +1514,14 @@ void TabDeckEditor::actDecrement() void TabDeckEditor::setDeck(DeckLoader *_deck) { + qDebug() << " ORIGINAL BANNER CARD " << _deck->getBannerCard(); deckModel->setDeckList(_deck); nameEdit->setText(deckModel->getDeckList()->getName()); commentsEdit->setText(deckModel->getDeckList()->getComments()); + qDebug() << deckModel->getDeckList()->getBannerCard() << " was the banner card"; + bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard()); + updateBannerCardComboBox(); updateHash(); deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); deckView->expandAll(); diff --git a/cockatrice/src/client/tabs/tab_deck_editor.h b/cockatrice/src/client/tabs/tab_deck_editor.h index 8340f3b1b..451328f62 100644 --- a/cockatrice/src/client/tabs/tab_deck_editor.h +++ b/cockatrice/src/client/tabs/tab_deck_editor.h @@ -22,6 +22,7 @@ class DeckLoader; class Response; class FilterTreeModel; class FilterBuilder; +class QComboBox; class QGroupBox; class QMessageBox; class QHBoxLayout; @@ -35,6 +36,8 @@ class TabDeckEditor : public Tab private slots: void updateName(const QString &name); void updateComments(); + void updateBannerCardComboBox(); + void setBannerCard(int); void updateHash(); void updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex &previous); void updateCardInfoRight(const QModelIndex ¤t, const QModelIndex &previous); @@ -129,6 +132,8 @@ private: LineEditUnfocusable *nameEdit; QLabel *commentsLabel; QTextEdit *commentsEdit; + QLabel *bannerCardLabel; + QComboBox *bannerCardComboBox; QLabel *hashLabel1; LineEditUnfocusable *hashLabel; FilterTreeModel *filterModel; diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index 4b2410cce..9897b3865 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -105,6 +105,8 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) { loadLocalButton = new QPushButton; loadRemoteButton = new QPushButton; + unloadDeckButton = new QPushButton; + unloadDeckButton->setEnabled(false); readyStartButton = new ToggleButton; readyStartButton->setEnabled(false); forceStartGameButton = new QPushButton; @@ -114,6 +116,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck())); connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart())); + connect(unloadDeckButton, &QPushButton::clicked, this, &DeckViewContainer::unloadDeck); connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart); connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked())); connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText())); @@ -127,6 +130,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) auto *buttonHBox = new QHBoxLayout; buttonHBox->addWidget(loadLocalButton); buttonHBox->addWidget(loadRemoteButton); + buttonHBox->addWidget(unloadDeckButton); buttonHBox->addWidget(readyStartButton); buttonHBox->addWidget(sideboardLockButton); if (forceStartGameButton->isEnabled()) { @@ -134,13 +138,20 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) } buttonHBox->setContentsMargins(0, 0, 0, 0); buttonHBox->addStretch(); + deckView = new DeckView; connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged())); + deckView->setVisible(false); - auto *deckViewLayout = new QVBoxLayout; + visualDeckStorageWidget = new VisualDeckStorageWidget(this); + connect(visualDeckStorageWidget, &VisualDeckStorageWidget::imageDoubleClicked, this, + &DeckViewContainer::replaceDeckStorageWithDeckView); + + deckViewLayout = new QVBoxLayout; deckViewLayout->addLayout(buttonHBox); deckViewLayout->addWidget(deckView); + deckViewLayout->addWidget(visualDeckStorageWidget); deckViewLayout->setContentsMargins(0, 0, 0, 0); setLayout(deckViewLayout); @@ -153,6 +164,7 @@ void DeckViewContainer::retranslateUi() { loadLocalButton->setText(tr("Load deck...")); loadRemoteButton->setText(tr("Load remote deck...")); + unloadDeckButton->setText(tr("Unload deck...")); readyStartButton->setText(tr("Ready to start")); forceStartGameButton->setText(tr("Force start")); updateSideboardLockButtonText(); @@ -287,6 +299,44 @@ void TabGame::refreshShortcuts() } } +void DeckViewContainer::replaceDeckStorageWithDeckView(QMouseEvent *event, DeckPreviewCardPictureWidget *instance) +{ + Q_UNUSED(event); + QString fileName = instance->filePath; + DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); + QString deckString; + DeckLoader deck; + + bool error = !deck.loadFromFile(fileName, fmt); + if (!error) { + deckString = deck.writeToString_Native(); + error = deckString.length() > MAX_FILE_LENGTH; + } + if (error) { + QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded.")); + return; + } + + Command_DeckSelect cmd; + cmd.set_deck(deckString.toStdString()); + PendingCommand *pend = parentGame->prepareGameCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(deckSelectFinished(const Response &))); + parentGame->sendGameCommand(pend, playerId); + visualDeckStorageWidget->setVisible(false); + deckView->setVisible(true); + deckViewLayout->update(); + unloadDeckButton->setEnabled(true); +} + +void DeckViewContainer::unloadDeck() +{ + deckView->setVisible(false); + visualDeckStorageWidget->setVisible(true); + deckViewLayout->update(); + unloadDeckButton->setEnabled(false); +} + void DeckViewContainer::loadLocalDeck() { DlgLoadDeck dialog(this); diff --git a/cockatrice/src/client/tabs/tab_game.h b/cockatrice/src/client/tabs/tab_game.h index 6b6af48d9..427a3a746 100644 --- a/cockatrice/src/client/tabs/tab_game.h +++ b/cockatrice/src/client/tabs/tab_game.h @@ -3,6 +3,7 @@ #include "../../client/tearoff_menu.h" #include "../../game/player/player.h" +#include "../ui/widgets/visual_deck_storage/visual_deck_storage_widget.h" #include "pb/event_leave.pb.h" #include "pb/serverinfo_game.pb.h" #include "tab.h" @@ -86,14 +87,18 @@ class DeckViewContainer : public QWidget { Q_OBJECT private: - QPushButton *loadLocalButton, *loadRemoteButton, *forceStartGameButton; + QVBoxLayout *deckViewLayout; + QPushButton *loadLocalButton, *loadRemoteButton, *unloadDeckButton, *forceStartGameButton; ToggleButton *readyStartButton, *sideboardLockButton; DeckView *deckView; + VisualDeckStorageWidget *visualDeckStorageWidget; TabGame *parentGame; int playerId; private slots: + void replaceDeckStorageWithDeckView(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); void loadLocalDeck(); void loadRemoteDeck(); + void unloadDeck(); void readyStart(); void forceStart(); void deckSelectFinished(const Response &r); diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp index 9ef7ee385..c71b19a3f 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.cpp +++ b/cockatrice/src/client/tabs/tab_supervisor.cpp @@ -24,6 +24,7 @@ #include "tab_replays.h" #include "tab_room.h" #include "tab_server.h" +#include "visual_deck_storage/tab_deck_storage_visual.h" #include #include @@ -504,6 +505,15 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen) return tab; } +TabDeckStorageVisual *TabSupervisor::addVisualDeckStorageTab() +{ + TabDeckStorageVisual *tab = new TabDeckStorageVisual(this, client); + int tabIndex = myAddTab(tab); + addCloseButtonToTab(tab, tabIndex); + setCurrentWidget(tab); + return tab; +} + void TabSupervisor::deckEditorClosed(TabDeckEditor *tab) { if (tab == currentWidget()) diff --git a/cockatrice/src/client/tabs/tab_supervisor.h b/cockatrice/src/client/tabs/tab_supervisor.h index 7b43881d3..0f7dcf4ed 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.h +++ b/cockatrice/src/client/tabs/tab_supervisor.h @@ -3,6 +3,7 @@ #include "../../deck/deck_loader.h" #include "../../server/chat_view/user_list_proxy.h" +#include "visual_deck_storage/tab_deck_storage_visual.h" #include #include @@ -130,6 +131,7 @@ signals: public slots: TabDeckEditor *addDeckEditorTab(const DeckLoader *deckToOpen); + TabDeckStorageVisual *addVisualDeckStorageTab(); void openReplay(GameReplay *replay); void maximizeMainWindow(); private slots: 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 new file mode 100644 index 000000000..831e7ed97 --- /dev/null +++ b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -0,0 +1,104 @@ +#include "tab_deck_storage_visual.h" + +#include "../../../game/cards/card_database_model.h" +#include "../tab_supervisor.h" +#include "pb/command_deck_del.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class FlowLayout; +TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor, AbstractClient *_client) + : Tab(_tabSupervisor), client(_client) +{ + deck_list_model = new DeckListModel(this); + deck_list_model->setObjectName("visualDeckModel"); + + QWidget *container = new QWidget(this); + QVBoxLayout *layout = new QVBoxLayout(container); + container->setLayout(layout); + this->setCentralWidget(container); + + leftToolBar = new QToolBar; + leftToolBar->setOrientation(Qt::Horizontal); + leftToolBar->setIconSize(QSize(32, 32)); + QHBoxLayout *leftToolBarLayout = new QHBoxLayout(this); + leftToolBarLayout->addStretch(); + leftToolBarLayout->addWidget(leftToolBar); + leftToolBarLayout->addStretch(); + + aOpenLocalDeck = new QAction(this); + aOpenLocalDeck->setIcon(QPixmap("theme:icons/pencil")); + connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck())); + aDeleteLocalDeck = new QAction(this); + aDeleteLocalDeck->setIcon(QPixmap("theme:icons/remove_row")); + connect(aDeleteLocalDeck, SIGNAL(triggered()), this, SLOT(actDeleteLocalDeck())); + + connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::addDeckEditorTab); + + leftToolBar->addAction(aOpenLocalDeck); + leftToolBar->addAction(aDeleteLocalDeck); + + visualDeckStorageWidget = new VisualDeckStorageWidget(this); + connect(visualDeckStorageWidget, &VisualDeckStorageWidget::imageDoubleClicked, this, + &TabDeckStorageVisual::actOpenLocalDeck); + + // layout->addWidget(leftToolBar); + layout->addWidget(visualDeckStorageWidget); + + retranslateUi(); +} + +void TabDeckStorageVisual::closeRequest() +{ + this->close(); +} + +void TabDeckStorageVisual::retranslateUi() +{ + aOpenLocalDeck->setText(tr("Open in deck editor")); + aDeleteLocalDeck->setText(tr("Delete")); +} + +QString TabDeckStorageVisual::getTargetPath() const +{ + return {}; +} + +void TabDeckStorageVisual::actOpenLocalDeck(QMouseEvent *event, DeckPreviewCardPictureWidget *instance) +{ + (void)event; + DeckLoader deckLoader; + if (!deckLoader.loadFromFile(instance->filePath, DeckLoader::CockatriceFormat)) + return; + + emit openDeckEditor(&deckLoader); +} + +void TabDeckStorageVisual::actDeleteLocalDeck() +{ + QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); + if (localDirModel->isDir(curLeft)) + return; + + if (QMessageBox::warning(this, tr("Delete local file"), + tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + return; + + localDirModel->remove(curLeft); +} + +void TabDeckStorageVisual::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + qDebug() << "Card update process finished with exit code:" << exitCode << "and exit status:" << exitStatus; +} diff --git a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h new file mode 100644 index 000000000..4d8538826 --- /dev/null +++ b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h @@ -0,0 +1,56 @@ +#ifndef TAB_DECK_STORAGE_VISUAL_H +#define TAB_DECK_STORAGE_VISUAL_H + +#include "../../../deck/deck_list_model.h" +#include "../../../deck/deck_view.h" +#include "../../ui/widgets/cards/deck_preview_card_picture_widget.h" +#include "../../ui/widgets/visual_deck_storage/visual_deck_storage_widget.h" +#include "../tab.h" + +#include + +class AbstractClient; +class QTreeView; +class QFileSystemModel; +class QToolBar; +class QTreeWidget; +class QTreeWidgetItem; +class QGroupBox; +class CommandContainer; +class Response; +class DeckLoader; + +class TabDeckStorageVisual final : public Tab +{ + Q_OBJECT +public: + TabDeckStorageVisual(TabSupervisor *_tabSupervisor, AbstractClient *_client); + + void retranslateUi() override; + QString getTabText() const override + { + return tr("Visual Deck storage"); + } +public slots: + void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus); + void closeRequest() override; + void actOpenLocalDeck(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + void actDeleteLocalDeck(); +signals: + void openDeckEditor(const DeckLoader *deckLoader); + +private: + QWidget *container; + QHBoxLayout *layout; + AbstractClient *client; + QTreeView *localDirView; + QFileSystemModel *localDirModel; + QToolBar *leftToolBar; + QGroupBox *leftGroupBox; + VisualDeckStorageWidget *visualDeckStorageWidget; + DeckListModel *deck_list_model; + QAction *aOpenLocalDeck, *aDeleteLocalDeck; + QString getTargetPath() const; +}; + +#endif diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index f2e9442d0..255ab4738 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -96,16 +96,12 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) // Call the base class's paintEvent to draw the card image CardInfoPictureWidget::paintEvent(event); - // Now add the custom text overlay on top of the image + // If no overlay text, skip drawing the text if (overlayText.isEmpty()) { return; } - QStylePainter painter(this); - // Set text properties - QFont font = painter.font(); - font.setPointSize(fontSize); - painter.setFont(font); + QStylePainter painter(this); // Get the pixmap from the base class using the getter const QPixmap &pixmap = getResizedPixmap(); @@ -118,13 +114,34 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) const QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2}; const QRect pixmapRect(topLeft, scaledSize); - // Prepare text wrapping - const QFontMetrics fontMetrics(font); - const int lineHeight = fontMetrics.height(); - const int textWidth = pixmapRect.width(); - QString wrappedText; + // Calculate the optimal font size + QFont font = painter.font(); + int optimalFontSize = fontSize; // Start with the user-defined font size + const QFontMetrics baseMetrics(font); + int textWidth = pixmapRect.width(); - // Break the text into multiple lines to fit within the pixmap width + // Reduce the font size until the text fits within the pixmap's width + do { + font.setPointSize(optimalFontSize); + QFontMetrics fm(font); + int currentWidth = 0; + for (const QString &word : overlayText.split(' ')) { + currentWidth = std::max(currentWidth, fm.horizontalAdvance(word)); + } + + if (currentWidth <= textWidth) { + break; + } + + --optimalFontSize; + } while (optimalFontSize > 1); + + // Apply the calculated font size + painter.setFont(font); + + // Wrap the text to fit within the pixmap width + const QFontMetrics fontMetrics(font); + QString wrappedText; QString currentLine; QStringList words = overlayText.split(' '); for (const QString &word : words) { @@ -141,13 +158,22 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) wrappedText += currentLine; // Calculate total text block height - const int totalTextHeight = static_cast(wrappedText.count('\n')) * lineHeight + lineHeight; + int totalTextHeight = wrappedText.count('\n') * fontMetrics.height() + fontMetrics.height(); + + // Adjust font size if the total text height exceeds the pixmap height + while (totalTextHeight > pixmapRect.height() && optimalFontSize > 1) { + --optimalFontSize; + font.setPointSize(optimalFontSize); + painter.setFont(font); + const QFontMetrics newMetrics(font); + totalTextHeight = wrappedText.count('\n') * newMetrics.height() + newMetrics.height(); + } // Set up the text layout options QTextOption textOption; textOption.setAlignment(textAlignment); - // Create a text rectangle centered within the pixmap rect + // Create a text rectangle centered vertically within the pixmap rect auto textRect = QRect(pixmapRect.left(), pixmapRect.top(), pixmapRect.width(), totalTextHeight); textRect.moveTop((pixmapRect.height() - totalTextHeight) / 2 + pixmapRect.top()); @@ -169,7 +195,6 @@ void CardInfoPictureWithTextOverlayWidget::drawOutlinedText(QPainter &painter, const QString &text, const QTextOption &textOption) const { - // Draw the black outline (outlineColor) painter.setPen(outlineColor); for (int dx = -1; dx <= 1; ++dx) { for (int dy = -1; dy <= 1; ++dy) { @@ -180,7 +205,7 @@ void CardInfoPictureWithTextOverlayWidget::drawOutlinedText(QPainter &painter, } } - // Draw the main text (textColor) + // Draw the main text painter.setPen(textColor); painter.drawText(textRect, text, textOption); } diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h index 0290ce3eb..44107362a 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h @@ -7,7 +7,7 @@ #include #include -class CardInfoPictureWithTextOverlayWidget final : public CardInfoPictureWidget +class CardInfoPictureWithTextOverlayWidget : public CardInfoPictureWidget { Q_OBJECT 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 80c26d817..4efb1f44d 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp @@ -1,6 +1,8 @@ #include "card_size_widget.h" #include "../../../../settings/cache_settings.h" +#include "../printing_selector/printing_selector.h" +#include "../visual_deck_storage/visual_deck_storage_widget.h" /** * @class CardSizeWidget @@ -40,7 +42,12 @@ CardSizeWidget::CardSizeWidget(QWidget *parent, FlowWidget *flowWidget, int defa */ void CardSizeWidget::updateCardSizeSetting(int newValue) { - SettingsCache::instance().setPrintingSelectorCardSize(newValue); + // Check the type of the parent widget + if ((parent = qobject_cast(parentWidget()))) { + SettingsCache::instance().setPrintingSelectorCardSize(newValue); + } else if ((parent = qobject_cast(parentWidget()))) { + SettingsCache::instance().setVisualDeckStorageCardSize(newValue); + } } /** 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 aaafc2f45..4bc2afc98 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_size_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_size_widget.h @@ -15,11 +15,11 @@ class CardSizeWidget : public QWidget public: explicit CardSizeWidget(QWidget *parent, FlowWidget *flowWidget = nullptr, int defaultValue = 100); [[nodiscard]] QSlider *getSlider() const; + QWidget *parent; public slots: - static void updateCardSizeSetting(int newValue); + void updateCardSizeSetting(int newValue); private: - QWidget *parent; FlowWidget *flowWidget; QHBoxLayout *cardSizeLayout; QLabel *cardSizeLabel; diff --git a/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp new file mode 100644 index 000000000..eff64e83d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp @@ -0,0 +1,53 @@ +#include "deck_preview_card_picture_widget.h" + +#include +#include +#include +#include +#include +#include + +/** + * @brief Constructs a CardPictureWithTextOverlay widget. + * @param parent The parent widget. + * @param hoverToZoomEnabled If this widget will spawn a larger widget when hovered over. + * @param textColor The color of the overlay text. + * @param outlineColor The color of the outline around the text. + * @param fontSize The font size of the overlay text. + * @param alignment The alignment of the text within the overlay. + * + * Sets the widget's size policy and default border style. + */ +DeckPreviewCardPictureWidget::DeckPreviewCardPictureWidget(QWidget *parent, + const bool hoverToZoomEnabled, + const QColor &textColor, + const QColor &outlineColor, + const int fontSize, + const Qt::Alignment alignment) + : CardInfoPictureWithTextOverlayWidget(parent, hoverToZoomEnabled, textColor, outlineColor, fontSize, alignment) +{ + singleClickTimer = new QTimer(this); + singleClickTimer->setSingleShot(true); + connect(singleClickTimer, &QTimer::timeout, this, [this]() { emit imageClicked(lastMouseEvent, this); }); +} + +void DeckPreviewCardPictureWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + lastMouseEvent = event; + singleClickTimer->start(QApplication::doubleClickInterval()); + } +} + +void DeckPreviewCardPictureWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + singleClickTimer->stop(); // Prevent single-click logic + emit imageDoubleClicked(lastMouseEvent, this); + } +} + +void DeckPreviewCardPictureWidget::setFilePath(const QString &_filePath) +{ + filePath = _filePath; +} diff --git a/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h new file mode 100644 index 000000000..25d9b16e9 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h @@ -0,0 +1,40 @@ +#ifndef DECK_PREVIEW_CARD_PICTURE_WIDGET_H +#define DECK_PREVIEW_CARD_PICTURE_WIDGET_H + +#include "card_info_picture_with_text_overlay_widget.h" + +#include +#include +#include + +class DeckPreviewCardPictureWidget final : public CardInfoPictureWithTextOverlayWidget +{ + Q_OBJECT + +public: + explicit DeckPreviewCardPictureWidget(QWidget *parent = nullptr, + bool hoverToZoomEnabled = false, + const QColor &textColor = Qt::white, + const QColor &outlineColor = Qt::black, + int fontSize = 12, + Qt::Alignment alignment = Qt::AlignCenter); + + QString filePath; + +signals: + void imageClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + void imageDoubleClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + +public slots: + void setFilePath(const QString &filePath); + +private: + QTimer *singleClickTimer; + QMouseEvent *lastMouseEvent = nullptr; // Store the last mouse event + +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; +}; + +#endif // DECK_PREVIEW_CARD_PICTURE_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp new file mode 100644 index 000000000..f41c720f0 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp @@ -0,0 +1,58 @@ +#include "visual_deck_storage_search_widget.h" + +/** + * @brief Constructs a PrintingSelectorCardSearchWidget for searching cards by set name or set code. + * + * This widget provides a search bar that allows users to search for cards by either their set name + * or set code. It uses a debounced timer to trigger the search action after the user stops typing. + * + * @param parent The parent PrintingSelector widget that will handle the search results. + */ +VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent) : parent(parent) +{ + layout = new QHBoxLayout(this); + setLayout(layout); + + searchBar = new QLineEdit(this); + searchBar->setPlaceholderText(tr("Search by filename")); + layout->addWidget(searchBar); + + // Add a debounce timer for the search bar to limit frequent updates + searchDebounceTimer = new QTimer(this); + searchDebounceTimer->setSingleShot(true); + connect(searchBar, &QLineEdit::textChanged, this, [this]() { + searchDebounceTimer->start(300); // 300ms debounce + }); + + connect(searchDebounceTimer, &QTimer::timeout, parent, &VisualDeckStorageWidget::refreshBannerCards); +} + +/** + * @brief Retrieves the current text in the search bar. + * + * @return The text entered by the user in the search bar. + */ +QString VisualDeckStorageSearchWidget::getSearchText() +{ + return searchBar->text(); +} + +QStringList VisualDeckStorageSearchWidget::filterFiles(const QStringList &files, const QString &searchText) +{ + if (searchText.isEmpty() || searchText.isNull()) { + return files; + } + + QStringList filteredFiles; + + for (const auto &file : files) { + QFileInfo fileInfo(file); + QString fileName = fileInfo.fileName().toLower(); + + if (fileName.contains(searchText.toLower())) { + filteredFiles << file; + } + } + + return filteredFiles; +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h new file mode 100644 index 000000000..e136c5491 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h @@ -0,0 +1,28 @@ +#ifndef VISUAL_DECK_STORAGE_SEARCH_WIDGET_H +#define VISUAL_DECK_STORAGE_SEARCH_WIDGET_H + +#include "visual_deck_storage_widget.h" + +#include +#include +#include +#include + +class VisualDeckStorageWidget; +class VisualDeckStorageSearchWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent); + QString getSearchText(); + QStringList filterFiles(const QStringList &files, const QString &searchText); + +private: + QHBoxLayout *layout; + VisualDeckStorageWidget *parent; + QLineEdit *searchBar; + QTimer *searchDebounceTimer; +}; + +#endif // VISUAL_DECK_STORAGE_SEARCH_WIDGET_H 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 new file mode 100644 index 000000000..3c3ca9cbf --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -0,0 +1,121 @@ +#include "visual_deck_storage_widget.h" + +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../settings/cache_settings.h" +#include "visual_deck_storage_search_widget.h" + +#include +#include +#include +#include + +VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(parent), sortOrder(Alphabetical) +{ + deckListModel = new DeckListModel(this); + deckListModel->setObjectName("visualDeckModel"); + + layout = new QVBoxLayout(); + setLayout(layout); + + // ComboBox for sorting options + sortComboBox = new QComboBox(this); + sortComboBox->addItem("Sort Alphabetically (Filename)", Alphabetical); + sortComboBox->addItem("Sort by Last Modified", ByLastModified); + sortComboBox->setCurrentIndex(SettingsCache::instance().getVisualDeckStorageSortingOrder()); + + searchWidget = new VisualDeckStorageSearchWidget(this); + + // Add combo box to the main layout + layout->addWidget(sortComboBox); + layout->addWidget(searchWidget); + + flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + layout->addWidget(flowWidget); + + cardSizeWidget = new CardSizeWidget(this, flowWidget, SettingsCache::instance().getVisualDeckStorageCardSize()); + layout->addWidget(cardSizeWidget); + + // Connect sorting change signal to refresh the file list + connect(sortComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &VisualDeckStorageWidget::updateSortOrder); +} + +void VisualDeckStorageWidget::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + updateSortOrder(); +} + +void VisualDeckStorageWidget::updateSortOrder() +{ + sortOrder = static_cast(sortComboBox->currentData().toInt()); + SettingsCache::instance().setVisualDeckStorageSortingOrder(sortComboBox->currentData().toInt()); + refreshBannerCards(); // Refresh the banner cards with the new sort order +} + +void VisualDeckStorageWidget::imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance) +{ + emit imageClicked(event, instance); +} + +void VisualDeckStorageWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance) +{ + emit imageDoubleClicked(event, instance); +} + +void VisualDeckStorageWidget::refreshBannerCards() +{ + QStringList allFiles; + + // QDirIterator with QDir::Files and QDir::NoSymLinks ensures only files are listed (no directories or symlinks) + QDirIterator it(SettingsCache::instance().getDeckPath(), QDir::Files | QDir::NoSymLinks, + QDirIterator::Subdirectories); + + while (it.hasNext()) { + allFiles << it.next(); // Add each file path to the list + } + + // Sort files based on the current sort order + std::sort(allFiles.begin(), allFiles.end(), [this](const QString &file1, const QString &file2) { + QFileInfo info1(file1); + QFileInfo info2(file2); + + switch (sortOrder) { + case Alphabetical: + return info1.fileName().toLower() < info2.fileName().toLower(); + case ByLastModified: + return info1.lastModified() > info2.lastModified(); + } + + return false; // Default case + }); + + auto filteredFiles = searchWidget->filterFiles(allFiles, searchWidget->getSearchText()); + + flowWidget->clearLayout(); // Clear existing widgets in the flow layout + + foreach (const QString &file, filteredFiles) { + auto deckLoader = new DeckLoader(); + deckLoader->loadFromFile(file, DeckLoader::CockatriceFormat); + deckListModel->setDeckList(new DeckLoader(*deckLoader)); + + auto *display = new DeckPreviewCardPictureWidget(flowWidget, false); + auto bannerCard = deckLoader->getBannerCard().isEmpty() + ? CardInfoPtr() + : CardDatabaseManager::getInstance()->getCard(deckLoader->getBannerCard()); + display->setCard(bannerCard); + display->setOverlayText(deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName() + : deckLoader->getName()); + display->setFontSize(24); + display->setFilePath(deckLoader->getLastFileName()); + + connect(display, &DeckPreviewCardPictureWidget::imageClicked, this, + &VisualDeckStorageWidget::imageClickedEvent); + connect(display, &DeckPreviewCardPictureWidget::imageDoubleClicked, this, + &VisualDeckStorageWidget::imageDoubleClickedEvent); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, display, &CardInfoPictureWidget::setScaleFactor); + display->setScaleFactor(cardSizeWidget->getSlider()->value()); + flowWidget->addWidget(display); + } +} 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 new file mode 100644 index 000000000..9428fd663 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -0,0 +1,51 @@ +#ifndef VISUAL_DECK_STORAGE_WIDGET_H +#define VISUAL_DECK_STORAGE_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../../../../deck/deck_view.h" +#include "../../../ui/widgets/cards/deck_preview_card_picture_widget.h" +#include "../../../ui/widgets/general/layout_containers/flow_widget.h" +#include "../cards/card_size_widget.h" +#include "visual_deck_storage_search_widget.h" + +#include +#include + +class VisualDeckStorageSearchWidget; +class VisualDeckStorageWidget final : public QWidget +{ + Q_OBJECT +public: + explicit VisualDeckStorageWidget(QWidget *parent); + void retranslateUi(); + +public slots: + void imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + void imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + void refreshBannerCards(); // Refresh the display of cards based on the current sorting option + void showEvent(QShowEvent *event) override; + void updateSortOrder(); + +signals: + void imageClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + void imageDoubleClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + +private: + enum SortOrder + { + Alphabetical, + ByLastModified + }; + + QVBoxLayout *layout; + FlowWidget *flowWidget; + DeckListModel *deckListModel; + QMap cardContainers; + + SortOrder sortOrder; // Current sorting option + QComboBox *sortComboBox; + VisualDeckStorageSearchWidget *searchWidget; + CardSizeWidget *cardSizeWidget; +}; + +#endif // VISUAL_DECK_STORAGE_WIDGET_H diff --git a/cockatrice/src/client/ui/window_main.cpp b/cockatrice/src/client/ui/window_main.cpp index dcb1e76da..ff25471a1 100644 --- a/cockatrice/src/client/ui/window_main.cpp +++ b/cockatrice/src/client/ui/window_main.cpp @@ -290,6 +290,11 @@ void MainWindow::actDeckEditor() tabSupervisor->addDeckEditorTab(nullptr); } +void MainWindow::actVisualDeckStorage() +{ + tabSupervisor->addVisualDeckStorageTab(); +} + void MainWindow::actFullScreen(bool checked) { if (checked) @@ -660,6 +665,7 @@ void MainWindow::retranslateUi() aSinglePlayer->setText(tr("Start &local game...")); aWatchReplay->setText(tr("&Watch replay...")); aDeckEditor->setText(tr("&Deck editor")); + aVisualDeckStorage->setText(tr("&Visual Deck storage")); aFullScreen->setText(tr("&Full screen")); aRegister->setText(tr("&Register to server...")); aForgotPassword->setText(tr("&Restore password...")); @@ -707,6 +713,8 @@ void MainWindow::createActions() connect(aWatchReplay, SIGNAL(triggered()), this, SLOT(actWatchReplay())); aDeckEditor = new QAction(this); connect(aDeckEditor, SIGNAL(triggered()), this, SLOT(actDeckEditor())); + aVisualDeckStorage = new QAction(this); + connect(aVisualDeckStorage, SIGNAL(triggered()), this, SLOT(actVisualDeckStorage())); aFullScreen = new QAction(this); aFullScreen->setCheckable(true); connect(aFullScreen, SIGNAL(toggled(bool)), this, SLOT(actFullScreen(bool))); @@ -794,6 +802,7 @@ void MainWindow::createMenus() cockatriceMenu->addAction(aWatchReplay); cockatriceMenu->addSeparator(); cockatriceMenu->addAction(aDeckEditor); + cockatriceMenu->addAction(aVisualDeckStorage); cockatriceMenu->addSeparator(); cockatriceMenu->addAction(aFullScreen); cockatriceMenu->addSeparator(); @@ -881,6 +890,11 @@ MainWindow::MainWindow(QWidget *parent) connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); + + if (SettingsCache::instance().getVisualDeckStorageShowOnLoad()) { + connect(CardDatabaseManager::getInstance(), SIGNAL(cardDatabaseLoadingFinished()), tabSupervisor, + SLOT(addVisualDeckStorageTab())); + } connect(CardDatabaseManager::getInstance(), SIGNAL(cardDatabaseLoadingFailed()), this, SLOT(cardDatabaseLoadingFailed())); connect(CardDatabaseManager::getInstance(), SIGNAL(cardDatabaseNewSetsFound(int, QStringList)), this, diff --git a/cockatrice/src/client/ui/window_main.h b/cockatrice/src/client/ui/window_main.h index 329008d6e..4efed7fd5 100644 --- a/cockatrice/src/client/ui/window_main.h +++ b/cockatrice/src/client/ui/window_main.h @@ -74,6 +74,7 @@ private slots: void actSinglePlayer(); void actWatchReplay(); void actDeckEditor(); + void actVisualDeckStorage(); void actFullScreen(bool checked); void actRegister(); void actSettings(); @@ -131,10 +132,11 @@ private: QList tabMenus; QMenu *cockatriceMenu, *dbMenu, *helpMenu, *trayIconMenu; - QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit, - *aAbout, *aTips, *aCheckCardUpdates, *aRegister, *aForgotPassword, *aUpdate, *aViewLog, *aManageSets, - *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aReloadCardDatabase, *aShow, - *aOpenSettingsFolder; + QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aVisualDeckStorage, *aFullScreen, + *aSettings, *aExit, *aAbout, *aTips, *aCheckCardUpdates, *aRegister, *aForgotPassword, *aUpdate, *aViewLog, + *aManageSets, *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aReloadCardDatabase, + *aShow, *aOpenSettingsFolder; + TabSupervisor *tabSupervisor; WndSets *wndSets; RemoteClient *client; diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index 9eae7a6b4..537944a0d 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -346,8 +346,13 @@ AppearanceSettingsPage::AppearanceSettingsPage() showShortcutsCheckBox.setChecked(settings.getShowShortcuts()); connect(&showShortcutsCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &AppearanceSettingsPage::showShortcutsChanged); + showVisualDeckStorageOnLoadCheckBox.setChecked(settings.getVisualDeckStorageShowOnLoad()); + connect(&showVisualDeckStorageOnLoadCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setVisualDeckStorageShowOnLoad); + auto *menuGrid = new QGridLayout; menuGrid->addWidget(&showShortcutsCheckBox, 0, 0); + menuGrid->addWidget(&showVisualDeckStorageOnLoadCheckBox, 1, 0); menuGroupBox = new QGroupBox; menuGroupBox->setLayout(menuGrid); @@ -482,6 +487,7 @@ void AppearanceSettingsPage::retranslateUi() menuGroupBox->setTitle(tr("Menu settings")); showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus")); + showVisualDeckStorageOnLoadCheckBox.setText(tr("Show visual deck storage on database load")); cardsGroupBox->setTitle(tr("Card rendering")); displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture")); diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index 983312184..0fb0d694b 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -95,6 +95,7 @@ private: QLabel minPlayersForMultiColumnLayoutLabel; QLabel maxFontSizeForCardsLabel; QCheckBox showShortcutsCheckBox; + QCheckBox showVisualDeckStorageOnLoadCheckBox; QCheckBox displayCardNamesCheckBox; QCheckBox autoRotateSidewaysLayoutCardsCheckBox; QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox; diff --git a/cockatrice/src/game/cards/card_database_model.h b/cockatrice/src/game/cards/card_database_model.h index 1720b4b9a..5a6b8feec 100644 --- a/cockatrice/src/game/cards/card_database_model.h +++ b/cockatrice/src/game/cards/card_database_model.h @@ -117,14 +117,15 @@ public: } void clearFilterAll(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; static int lessThanNumerically(const QString &left, const QString &right); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool rowMatchesCardName(CardInfoPtr info) const; - bool canFetchMore(const QModelIndex &parent) const override; - void fetchMore(const QModelIndex &parent) override; + private slots: void filterTreeChanged(); /** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */ diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index e1fb59e0a..660b5935c 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -259,6 +259,9 @@ SettingsCache::SettingsCache() settings->value("cards/printingselectorcardsizeslidervisible", true).toBool(); printingSelectorNavigationButtonsVisible = settings->value("cards/printingselectornavigationbuttonsvisible", true).toBool(); + visualDeckStorageCardSize = settings->value("cards/visualdeckstoragecardsize", 100).toInt(); + visualDeckStorageShowOnLoad = settings->value("interface/visualdeckstorageshowonload", true).toBool(); + visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).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(); @@ -609,6 +612,25 @@ void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED emit printingSelectorNavigationButtonsVisibleChanged(); } +void SettingsCache::setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder) +{ + visualDeckStorageSortingOrder = _visualDeckStorageSortingOrder; + settings->setValue("interface/visualdeckstoragesortingorder", visualDeckStorageSortingOrder); +} + +void SettingsCache::setVisualDeckStorageCardSize(int _visualDeckStorageCardSize) +{ + visualDeckStorageCardSize = _visualDeckStorageCardSize; + settings->setValue("cards/visualdeckstoragecardsize", visualDeckStorageCardSize); + emit visualDeckStorageCardSizeChanged(); +} + +void SettingsCache::setVisualDeckStorageShowOnLoad(QT_STATE_CHANGED_T _visualDeckStorageShowOnLoad) +{ + visualDeckStorageShowOnLoad = _visualDeckStorageShowOnLoad; + settings->setValue("interface/visualdeckstorageshowonload", visualDeckStorageShowOnLoad); +} + 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 738c4a431..63ab91e13 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -59,6 +59,7 @@ signals: void printingSelectorSearchBarVisibleChanged(); void printingSelectorCardSizeSliderVisibleChanged(); void printingSelectorNavigationButtonsVisibleChanged(); + void visualDeckStorageCardSizeChanged(); void horizontalHandChanged(); void handJustificationChanged(); void invertVerticalCoordinateChanged(); @@ -123,6 +124,9 @@ private: bool printingSelectorSearchBarVisible; bool printingSelectorCardSizeSliderVisible; bool printingSelectorNavigationButtonsVisible; + int visualDeckStorageSortingOrder; + int visualDeckStorageCardSize; + bool visualDeckStorageShowOnLoad; bool horizontalHand; bool invertVerticalCoordinate; int minPlayersForMultiColumnLayout; @@ -369,6 +373,18 @@ public: { return printingSelectorNavigationButtonsVisible; } + int getVisualDeckStorageSortingOrder() const + { + return visualDeckStorageSortingOrder; + } + int getVisualDeckStorageCardSize() const + { + return visualDeckStorageCardSize; + } + bool getVisualDeckStorageShowOnLoad() const + { + return visualDeckStorageShowOnLoad; + } bool getHorizontalHand() const { return horizontalHand; @@ -677,6 +693,9 @@ public slots: void setPrintingSelectorSearchBarVisible(QT_STATE_CHANGED_T _searchBarVisible); void setPrintingSelectorCardSizeSliderVisible(QT_STATE_CHANGED_T _cardSizeSliderVisible); void setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible); + void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder); + void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize); + void setVisualDeckStorageShowOnLoad(QT_STATE_CHANGED_T _visualDeckStorageShowOnLoad); void setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand); void setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate); void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); diff --git a/common/decklist.cpp b/common/decklist.cpp index 08b98a730..6ffaa7725 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -368,7 +368,7 @@ DeckList::DeckList() // TODO: https://qt-project.org/doc/qt-4.8/qobject.html#no-copy-constructor-or-assignment-operator DeckList::DeckList(const DeckList &other) - : QObject(), name(other.name), comments(other.comments), deckHash(other.deckHash) + : QObject(), name(other.name), comments(other.comments), bannerCard(other.bannerCard), deckHash(other.deckHash) { root = new InnerDecklistNode(other.getRoot()); @@ -423,7 +423,10 @@ bool DeckList::readElement(QXmlStreamReader *xml) name = xml->readElementText(); else if (childName == "comments") comments = xml->readElementText(); - else if (childName == "zone") { + else if (childName == "bannerCard") { + bannerCard = xml->readElementText(); + qDebug() << "Deckloader found the banner card " << bannerCard; + } else if (childName == "zone") { InnerDecklistNode *newZone = getZoneObjFromName(xml->attributes().value("name").toString()); newZone->readElement(xml); } else if (childName == "sideboard_plan") { @@ -444,6 +447,7 @@ void DeckList::write(QXmlStreamWriter *xml) xml->writeAttribute("version", "1"); xml->writeTextElement("deckname", name); xml->writeTextElement("comments", comments); + xml->writeTextElement("bannerCard", bannerCard); for (int i = 0; i < root->size(); i++) root->at(i)->writeElement(xml); diff --git a/common/decklist.h b/common/decklist.h index 87596a884..e6e5dad15 100644 --- a/common/decklist.h +++ b/common/decklist.h @@ -250,7 +250,7 @@ class DeckList : public QObject { Q_OBJECT private: - QString name, comments; + QString name, comments, bannerCard; QString deckHash; QMap sideboardPlans; InnerDecklistNode *root; @@ -279,6 +279,10 @@ public slots: { comments = _comments; } + void setBannerCard(const QString &_bannerCard = QString()) + { + bannerCard = _bannerCard; + } public: explicit DeckList(); @@ -293,6 +297,10 @@ public: { return comments; } + QString getBannerCard() const + { + return bannerCard; + } QList getCurrentSideboardPlan(); void setCurrentSideboardPlan(const QList &plan); const QMap &getSideboardPlans() const diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 7efeb9992..7f5c16e52 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -184,6 +184,15 @@ void SettingsCache::setPrintingSelectorCardSizeSliderVisible(QT_STATE_CHANGED_T void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */) { } +void SettingsCache::setVisualDeckStorageSortingOrder(int /* _visualDeckStorageSortingOrder */) +{ +} +void SettingsCache::setVisualDeckStorageCardSize(int /* _visualDeckStorageCardSize */) +{ +} +void SettingsCache::setVisualDeckStorageShowOnLoad(QT_STATE_CHANGED_T /* _visualDeckStorageShowOnLoad */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { } diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index f47bae36b..572c28f3a 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -188,6 +188,15 @@ void SettingsCache::setPrintingSelectorCardSizeSliderVisible(QT_STATE_CHANGED_T void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */) { } +void SettingsCache::setVisualDeckStorageSortingOrder(int /* _visualDeckStorageSortingOrder */) +{ +} +void SettingsCache::setVisualDeckStorageCardSize(int /* _visualDeckStorageCardSize */) +{ +} +void SettingsCache::setVisualDeckStorageShowOnLoad(QT_STATE_CHANGED_T /* _visualDeckStorageShowOnLoad */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { }