From b2dd8eed3fbec6539dfbc8a61ce2d2e70664f229 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 31 Dec 2025 08:54:47 -0800 Subject: [PATCH] [TabDeckEditor] Create class to centralize deck state (#6459) * create new file * use QSharedPointer in DeckListModel * [TabDeckEditor] Create class to centralize deck state * delete method * update docs --- cockatrice/CMakeLists.txt | 1 + .../deck_editor_deck_dock_widget.cpp | 298 ++++----------- .../deck_editor_deck_dock_widget.h | 38 +- .../deck_list_history_manager_widget.cpp | 77 ++-- .../deck_list_history_manager_widget.h | 11 +- .../deck_editor/deck_state_manager.cpp | 361 ++++++++++++++++++ .../widgets/deck_editor/deck_state_manager.h | 297 ++++++++++++++ .../dialogs/dlg_select_set_for_cards.cpp | 49 +-- .../dialogs/dlg_select_set_for_cards.h | 6 +- .../all_zones_card_amount_widget.cpp | 14 +- .../all_zones_card_amount_widget.h | 4 +- .../printing_selector/card_amount_widget.cpp | 44 +-- .../printing_selector/card_amount_widget.h | 8 +- .../printing_selector/printing_selector.cpp | 16 +- .../printing_selector/printing_selector.h | 13 +- .../printing_selector_card_display_widget.cpp | 9 +- .../printing_selector_card_display_widget.h | 3 +- .../printing_selector_card_overlay_widget.cpp | 9 +- .../printing_selector_card_overlay_widget.h | 3 +- ...rinting_selector_card_selection_widget.cpp | 14 +- .../printing_selector_card_selection_widget.h | 3 +- .../printing_selector_card_sorting_widget.cpp | 2 +- .../printing_selector_card_sorting_widget.h | 2 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 135 +++---- .../widgets/tabs/abstract_tab_deck_editor.h | 36 +- ...idekt_api_response_deck_display_widget.cpp | 5 +- .../widgets/tabs/tab_deck_editor.cpp | 5 +- .../tab_deck_editor_visual.cpp | 19 +- ...al_database_display_name_filter_widget.cpp | 3 +- .../models/deck_list/deck_list_model.cpp | 15 +- .../models/deck_list/deck_list_model.h | 10 +- 31 files changed, 933 insertions(+), 577 deletions(-) create mode 100644 cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp create mode 100644 cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index b2d387391..c6a29969f 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -156,6 +156,7 @@ set(cockatrice_SOURCES src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp src/interface/widgets/deck_editor/deck_list_style_proxy.cpp + src/interface/widgets/deck_editor/deck_state_manager.cpp src/interface/widgets/general/background_sources.cpp src/interface/widgets/general/display/background_plate_widget.cpp src/interface/widgets/general/display/banner_widget.cpp diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index b8ebc1419..62195974b 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -1,8 +1,8 @@ #include "deck_editor_deck_dock_widget.h" #include "../../../client/settings/cache_settings.h" -#include "../../deck_loader/deck_loader.h" #include "deck_list_style_proxy.h" +#include "deck_state_manager.h" #include #include @@ -38,7 +38,7 @@ static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo) } DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent) - : QDockWidget(parent), deckEditor(parent) + : QDockWidget(parent), deckEditor(parent), deckStateManager(parent->deckStateManager) { setObjectName("deckDock"); @@ -52,19 +52,19 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent void DeckEditorDeckDockWidget::createDeckDock() { - deckModel = new DeckListModel(this); - deckModel->setObjectName("deckModel"); - connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); - - deckLoader = new DeckLoader(this); + connect(getModel(), &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); proxy = new DeckListStyleProxy(this); - proxy->setSourceModel(deckModel); + proxy->setSourceModel(getModel()); - historyManagerWidget = new DeckListHistoryManagerWidget(deckModel, proxy, deckEditor->getHistoryManager(), this); + historyManagerWidget = new DeckListHistoryManagerWidget(deckStateManager, proxy, this); connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this, &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); + connect(deckStateManager, &DeckStateManager::focusIndexChanged, this, &DeckEditorDeckDockWidget::setSelectedIndex); + connect(deckStateManager, &DeckStateManager::deckReplaced, this, + &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); + deckView = new QTreeView(); deckView->setObjectName("deckView"); deckView->setModel(proxy); @@ -97,7 +97,7 @@ void DeckEditorDeckDockWidget::createDeckDock() nameDebounceTimer = new QTimer(this); nameDebounceTimer->setSingleShot(true); nameDebounceTimer->setInterval(300); // debounce duration in ms - connect(nameDebounceTimer, &QTimer::timeout, this, [this]() { updateName(nameEdit->text()); }); + connect(nameDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeName); connect(nameEdit, &LineEditUnfocusable::textChanged, this, [this]() { nameDebounceTimer->start(); // restart debounce timer @@ -141,7 +141,7 @@ void DeckEditorDeckDockWidget::createDeckDock() commentsDebounceTimer = new QTimer(this); commentsDebounceTimer->setSingleShot(true); commentsDebounceTimer->setInterval(400); // longer debounce for multi-line - connect(commentsDebounceTimer, &QTimer::timeout, this, [this]() { updateComments(); }); + connect(commentsDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeComments); connect(commentsEdit, &QTextEdit::textChanged, this, [this]() { commentsDebounceTimer->start(); // restart debounce timer @@ -152,21 +152,21 @@ void DeckEditorDeckDockWidget::createDeckDock() bannerCardLabel->setText(tr("Banner Card")); bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); bannerCardComboBox = new QComboBox(this); - connect(deckModel, &DeckListModel::dataChanged, this, [this]() { + connect(getModel(), &DeckListModel::dataChanged, this, [this]() { // Delay the update to avoid race conditions QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox); }); - connect(deckModel, &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand); - connect(deckModel, &DeckListModel::modelReset, this, &DeckEditorDeckDockWidget::expandAll); + connect(getModel(), &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand); + connect(getModel(), &DeckListModel::modelReset, this, &DeckEditorDeckDockWidget::expandAll); connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, - &DeckEditorDeckDockWidget::setBannerCard); + &DeckEditorDeckDockWidget::writeBannerCard); bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, {}); deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); - connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, - &DeckEditorDeckDockWidget::setTags); + connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, deckStateManager, + &DeckStateManager::setTags); activeGroupCriteriaLabel = new QLabel(this); @@ -175,9 +175,9 @@ void DeckEditorDeckDockWidget::createDeckDock() activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST); activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR); connect(activeGroupCriteriaComboBox, QOverload::of(&QComboBox::currentIndexChanged), [this]() { - deckModel->setActiveGroupCriteria(static_cast( + getModel()->setActiveGroupCriteria(static_cast( activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt())); - deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); + getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); }); aIncrement = new QAction(QString(), this); @@ -295,9 +295,10 @@ void DeckEditorDeckDockWidget::initializeFormats() formatComboBox->addItem(formatName, formatName); // store the raw key in itemData } - if (!deckModel->getDeckList()->getGameFormat().isEmpty()) { - deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); - formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); + QString format = deckStateManager->getMetadata().gameFormat; + if (!format.isEmpty()) { + getModel()->setActiveFormat(format); + formatComboBox->setCurrentIndex(formatComboBox->findData(format)); } else { // Ensure no selection is visible initially formatComboBox->setCurrentIndex(-1); @@ -306,11 +307,10 @@ void DeckEditorDeckDockWidget::initializeFormats() connect(formatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { if (index >= 0) { QString formatKey = formatComboBox->itemData(index).toString(); - deckModel->setActiveFormat(formatKey); + deckStateManager->setFormat(formatKey); } else { - deckModel->setActiveFormat(QString()); // clear format if deselected + deckStateManager->setFormat(""); // clear format if deselected } - emit deckModified(); }); } @@ -340,43 +340,37 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard() void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const QModelIndex & /*previous*/) { if (ExactCard card = getCurrentCard()) { - emit cardChanged(card); + emit selectedCardChanged(card); } } -void DeckEditorDeckDockWidget::updateName(const QString &name) +/** + * @brief Writes the contents of the name textBox to the DeckStateManager + */ +void DeckEditorDeckDockWidget::writeName() { - emit requestDeckHistorySave( - QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeck().deckList.getName())); - deckModel->getDeckList()->setName(name); - deckEditor->setModified(name.isEmpty()); - emit nameChanged(); - emit deckModified(); + QString name = nameEdit->text(); + deckStateManager->setName(name); } -void DeckEditorDeckDockWidget::updateComments() +/** + * @brief Writes the contents of the comments textBox to the DeckStateManager + */ +void DeckEditorDeckDockWidget::writeComments() { - emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)") - .arg(deckLoader->getDeck().deckList.getComments().size()) - .arg(commentsEdit->toPlainText().size())); - - deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); - deckEditor->setModified(commentsEdit->toPlainText().isEmpty()); - emit commentsChanged(); - emit deckModified(); + QString comments = commentsEdit->toPlainText(); + deckStateManager->setComments(comments); } void DeckEditorDeckDockWidget::updateHash() { - hashLabel->setText(deckModel->getDeckList()->getDeckHash()); - emit hashChanged(); - emit deckModified(); + hashLabel->setText(deckStateManager->getDeckHash()); } void DeckEditorDeckDockWidget::updateBannerCardComboBox() { // Store current banner card identity - CardRef wanted = deckModel->getDeckList()->getBannerCard(); + CardRef wanted = deckStateManager->getMetadata().bannerCard; // Block signals temporarily bool wasBlocked = bannerCardComboBox->blockSignals(true); @@ -386,7 +380,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Collect unique (name, providerId) pairs QSet> bannerCardSet; - QList cardsInDeck = deckModel->getCardRefs(); + QList cardsInDeck = getModel()->getCardRefs(); for (auto cardRef : cardsInDeck) { if (!CardDatabaseManager::query()->getCard(cardRef)) { @@ -415,7 +409,6 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Handle results if (restoreIndex != -1) { bannerCardComboBox->setCurrentIndex(restoreIndex); - syncDeckListBannerCardWithComboBox(); } else { // Add a placeholder "-" and set it as the current selection bannerCardComboBox->insertItem(0, "-"); @@ -426,25 +419,14 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() bannerCardComboBox->blockSignals(wasBlocked); } -void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) +/** + * @brief Writes the selected bannerCard to the DeckStateManager + */ +void DeckEditorDeckDockWidget::writeBannerCard(int index) { - emit requestDeckHistorySave(tr("Banner card changed")); - syncDeckListBannerCardWithComboBox(); - deckEditor->setModified(true); - emit deckModified(); -} - -void DeckEditorDeckDockWidget::setTags(const QStringList &tags) -{ - deckModel->getDeckList()->setTags(tags); - deckEditor->setModified(true); - emit deckModified(); -} - -void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox() -{ - auto [name, id] = bannerCardComboBox->currentData().value>(); - deckModel->getDeckList()->setBannerCard({name, id}); + auto [name, id] = bannerCardComboBox->itemData(index).value>(); + CardRef bannerCard = {name, id}; + deckStateManager->setBannerCard(bannerCard); } void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible) @@ -460,7 +442,7 @@ void DeckEditorDeckDockWidget::updateShowTagsWidget(const bool visible) void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() { - if (deckModel->getDeckList()->getBannerCard().name == "") { + if (deckStateManager->getMetadata().bannerCard.name == "") { if (bannerCardComboBox->findText("-") != -1) { bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-")); } else { @@ -468,36 +450,26 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() bannerCardComboBox->setCurrentIndex(0); } } else { - bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().name); + bannerCardComboBox->setCurrentText(deckStateManager->getMetadata().bannerCard.name); } } -/** - * Sets the currently active deck for this tab - * @param _deck The deck. - */ -void DeckEditorDeckDockWidget::setDeck(const LoadedDeck &_deck) +void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex) { - deckLoader->setDeck(_deck); - deckModel->setDeckList(&deckLoader->getDeck().deckList); - connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); - - emit requestDeckHistoryClear(); - historyManagerWidget->setDeckListModel(deckModel); - - syncDisplayWidgetsToModel(); - - emit deckChanged(); + deckView->clearSelection(); + deckView->setCurrentIndex(newCardIndex); + recursiveExpand(newCardIndex); + deckView->setFocus(Qt::FocusReason::MouseFocusReason); } void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() { nameEdit->blockSignals(true); - nameEdit->setText(deckModel->getDeckList()->getName()); + nameEdit->setText(deckStateManager->getMetadata().name); nameEdit->blockSignals(false); commentsEdit->blockSignals(true); - commentsEdit->setText(deckModel->getDeckList()->getComments()); + commentsEdit->setText(deckStateManager->getMetadata().comments); commentsEdit->blockSignals(false); bannerCardComboBox->blockSignals(true); @@ -507,44 +479,22 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() updateHash(); sortDeckModelToDeckView(); - deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); + deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags); } void DeckEditorDeckDockWidget::sortDeckModelToDeckView() { - deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); - formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); - - emit deckChanged(); -} - -DeckLoader *DeckEditorDeckDockWidget::getDeckLoader() -{ - return deckLoader; -} - -const DeckList &DeckEditorDeckDockWidget::getDeckList() const -{ - return *deckModel->getDeckList(); + getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); + getModel()->setActiveFormat(deckStateManager->getMetadata().gameFormat); + formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat)); } /** - * Resets the tab to the state for a blank new tab. + * @brief Convenience method to get the underlying model instance from the DeckStateManager */ -void DeckEditorDeckDockWidget::cleanDeck() +DeckListModel *DeckEditorDeckDockWidget::getModel() const { - deckModel->cleanList(); - nameEdit->setText(QString()); - emit nameChanged(); - commentsEdit->setText(QString()); - emit commentsChanged(); - hashLabel->setText(QString()); - emit hashChanged(); - emit deckModified(); - emit deckChanged(); - updateBannerCardComboBox(); - deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); + return deckStateManager->getModel(); } void DeckEditorDeckDockWidget::selectPrevCard() @@ -635,7 +585,7 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const auto selectedRows = deckView->selectionModel()->selectedRows(); const auto notLeafNode = [this](const QModelIndex &index) { - return deckModel->hasChildren(proxy->mapToSource(index)); + return getModel()->hasChildren(proxy->mapToSource(index)); }; selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end()); @@ -650,21 +600,7 @@ void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString & } QString zoneName = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : _zoneName; - - emit requestDeckHistorySave(tr("Added (%1): %2 (%3) %4") - .arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), - card.getPrinting().getProperty("num"))); - - QModelIndex newCardIndex = deckModel->addCard(card, zoneName); - - if (!newCardIndex.isValid()) { - return; - } - - deckView->clearSelection(); - deckView->setCurrentIndex(newCardIndex); - - emit deckModified(); + deckStateManager->addCard(card, zoneName); } void DeckEditorDeckDockWidget::actIncrementSelection() @@ -681,12 +617,12 @@ void DeckEditorDeckDockWidget::actSwapCard(const ExactCard &card, const QString QString providerId = card.getPrinting().getUuid(); QString collectorNumber = card.getPrinting().getProperty("num"); - QModelIndex foundCard = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber); + QModelIndex foundCard = getModel()->findCard(card.getName(), zoneName, providerId, collectorNumber); if (!foundCard.isValid()) { - foundCard = deckModel->findCard(card.getName(), zoneName); + foundCard = getModel()->findCard(card.getName(), zoneName); } - swapCard(foundCard); + deckStateManager->swapCardAtIndex(foundCard); } void DeckEditorDeckDockWidget::actSwapSelection() @@ -699,54 +635,15 @@ void DeckEditorDeckDockWidget::actSwapSelection() deckView->setSelectionMode(QAbstractItemView::SingleSelection); } - bool isModified = false; for (const auto ¤tIndex : selectedRows) { - if (swapCard(currentIndex)) { - isModified = true; - } + deckStateManager->swapCardAtIndex(currentIndex); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); - if (isModified) { - emit deckModified(); - } - update(); } -/** - * Swaps the card at the index between the maindeck and sideboard - * - * @param currentIndex The index to swap. - * @return True if the swap was successful - */ -bool DeckEditorDeckDockWidget::swapCard(const QModelIndex ¤tIndex) -{ - if (!currentIndex.isValid()) - return false; - const QString cardName = currentIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); - const QString cardProviderID = - currentIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); - const QModelIndex gparent = currentIndex.parent().parent(); - - if (!gparent.isValid()) - return false; - - const QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); - offsetCountAtIndex(currentIndex, false); - const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; - - if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) { - deckModel->addCard(card, otherZoneName); - } else { - // Third argument (true) says create the card no matter what, even if not in DB - deckModel->addPreferredPrintingCard(cardName, otherZoneName, true); - } - - return true; -} - void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName) { if (!card) @@ -754,17 +651,7 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z if (card.getInfo().getIsToken()) zoneName = DECK_ZONE_TOKENS; - QString providerId = card.getPrinting().getUuid(); - QString collectorNumber = card.getPrinting().getProperty("num"); - - QModelIndex idx = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber); - if (!idx.isValid()) { - return; - } - - deckView->clearSelection(); - deckView->setCurrentIndex(proxy->mapToSource(idx)); - offsetCountAtIndex(idx, false); + deckStateManager->decrementCard(card, zoneName); } void DeckEditorDeckDockWidget::actDecrementSelection() @@ -794,25 +681,11 @@ void DeckEditorDeckDockWidget::actRemoveCard() deckView->setSelectionMode(QAbstractItemView::SingleSelection); } - bool isModified = false; - for (const auto &index : selectedRows) { - if (!index.isValid() || deckModel->hasChildren(index)) { - continue; - } - QModelIndex sourceIndex = proxy->mapToSource(index); - QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); - - emit requestDeckHistorySave(QString(tr("Removed \"%1\" (all copies)")).arg(cardName)); - - deckModel->removeRow(sourceIndex.row(), sourceIndex.parent()); - isModified = true; + for (const auto &row : selectedRows) { + deckStateManager->removeCardAtIndex(row); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); - - if (isModified) { - emit deckModified(); - } } /** @@ -822,28 +695,17 @@ void DeckEditorDeckDockWidget::actRemoveCard() */ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool isIncrement) { - if (!idx.isValid() || deckModel->hasChildren(idx)) { + if (!idx.isValid() || getModel()->hasChildren(idx)) { return; } QModelIndex sourceIndex = proxy->mapToSource(idx); - QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); - QString providerId = - sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); - - const auto reason = QString(tr("%1 %2 × \"%3\" (%4)")) - .arg(isIncrement ? tr("Added") : tr("Removed")) - .arg(1) - .arg(cardName) - .arg(providerId); - - emit requestDeckHistorySave(reason); - - int offset = isIncrement ? 1 : -1; - deckModel->offsetCountAtIndex(sourceIndex, offset); - - emit deckModified(); + if (isIncrement) { + deckStateManager->incrementCountAtIndex(sourceIndex); + } else { + deckStateManager->decrementCountAtIndex(sourceIndex); + } } void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index a50434f0b..1a82b00d1 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -28,22 +28,14 @@ class DeckEditorDeckDockWidget : public QDockWidget Q_OBJECT public: explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent); - DeckLoader *deckLoader; + DeckListStyleProxy *proxy; - DeckListModel *deckModel; QTreeView *deckView; QComboBox *bannerCardComboBox; void createDeckDock(); ExactCard getCurrentCard(); void retranslateUi(); - QString getDeckName() - { - return nameEdit->text(); - } - QString getSimpleDeckName() - { - return nameEdit->text().simplified(); - } + QComboBox *getGroupByComboBox() { return activeGroupCriteriaComboBox; @@ -55,15 +47,11 @@ public: } public slots: - void cleanDeck(); void selectPrevCard(); void selectNextCard(); void updateBannerCardComboBox(); - void setDeck(const LoadedDeck &_deck); void syncDisplayWidgetsToModel(); void sortDeckModelToDeckView(); - DeckLoader *getDeckLoader(); - const DeckList &getDeckList() const; void actAddCard(const ExactCard &card, const QString &zoneName); void actIncrementSelection(); void actDecrementCard(const ExactCard &card, QString zoneName); @@ -74,17 +62,12 @@ public slots: void initializeFormats(); signals: - void nameChanged(); - void commentsChanged(); - void hashChanged(); - void deckChanged(); - void deckModified(); - void requestDeckHistorySave(const QString &modificationReason); - void requestDeckHistoryClear(); - void cardChanged(const ExactCard &_card); + void selectedCardChanged(const ExactCard &card); private: AbstractTabDeckEditor *deckEditor; + DeckStateManager *deckStateManager; + DeckListHistoryManagerWidget *historyManagerWidget; KeySignals deckViewKeySignals; QLabel *nameLabel; @@ -107,18 +90,17 @@ private: QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; + DeckListModel *getModel() const; [[nodiscard]] QModelIndexList getSelectedCardNodes() const; void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement); private slots: void decklistCustomMenu(QPoint point); - bool swapCard(const QModelIndex ¤tIndex); void updateCard(QModelIndex, const QModelIndex ¤t); - void updateName(const QString &name); - void updateComments(); - void setBannerCard(int); - void setTags(const QStringList &tags); - void syncDeckListBannerCardWithComboBox(); + void writeName(); + void writeComments(); + void writeBannerCard(int); + void setSelectedIndex(const QModelIndex &newCardIndex); void updateHash(); void refreshShortcuts(); void updateShowBannerCardComboBox(bool visible); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp index c68934ea6..d2d748753 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp @@ -1,10 +1,11 @@ #include "deck_list_history_manager_widget.h" -DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckListModel, +#include "deck_state_manager.h" + +DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_deckStateManager, DeckListStyleProxy *_styleProxy, - DeckListHistoryManager *manager, QWidget *parent) - : QWidget(parent), deckListModel(_deckListModel), styleProxy(_styleProxy), historyManager(manager) + : QWidget(parent), deckStateManager(_deckStateManager), styleProxy(_styleProxy) { layout = new QHBoxLayout(this); @@ -43,8 +44,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckL connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked); - connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, - &DeckListHistoryManagerWidget::refreshList); + connect(deckStateManager, &DeckStateManager::historyChanged, this, &DeckListHistoryManagerWidget::refreshList); refreshList(); retranslateUi(); @@ -58,15 +58,12 @@ void DeckListHistoryManagerWidget::retranslateUi() historyLabel->setText(tr("Click on an entry to revert to that point in the history.")); } -void DeckListHistoryManagerWidget::setDeckListModel(DeckListModel *_deckListModel) -{ - deckListModel = _deckListModel; -} - void DeckListHistoryManagerWidget::refreshList() { historyList->clear(); + DeckListHistoryManager *historyManager = deckStateManager->getHistoryManager(); + // Fill redo section first (oldest redo at top, newest redo closest to divider) const auto redoStack = historyManager->getRedoStack(); for (int i = 0; i < redoStack.size(); ++i) { // iterate forward @@ -98,36 +95,7 @@ void DeckListHistoryManagerWidget::refreshList() redoButton->setEnabled(historyManager->canRedo()); } -void DeckListHistoryManagerWidget::doUndo() -{ - if (!historyManager->canUndo()) { - return; - } - - historyManager->undo(deckListModel->getDeckList()); - deckListModel->rebuildTree(); - emit deckListModel->layoutChanged(); - emit requestDisplayWidgetSync(); - - refreshList(); -} - -void DeckListHistoryManagerWidget::doRedo() -{ - if (!historyManager->canRedo()) { - return; - } - - historyManager->redo(deckListModel->getDeckList()); - deckListModel->rebuildTree(); - - emit deckListModel->layoutChanged(); - emit requestDisplayWidgetSync(); - - refreshList(); -} - -void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item) +void DeckListHistoryManagerWidget::onListClicked(const QListWidgetItem *item) { // Ignore non-selectable items (like divider) if (!(item->flags() & Qt::ItemIsSelectable)) { @@ -138,23 +106,24 @@ void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item) int index = item->data(Qt::UserRole + 1).toInt(); if (mode == "redo") { - const auto redoStack = historyManager->getRedoStack(); + const auto redoStack = deckStateManager->getHistoryManager()->getRedoStack(); int steps = redoStack.size() - index; - for (int i = 0; i < steps; ++i) { - historyManager->redo(deckListModel->getDeckList()); - } + deckStateManager->redo(steps); } else if (mode == "undo") { - const auto undoStack = historyManager->getUndoStack(); - int steps = undoStack.size() - 1 - index; - for (int i = 0; i < steps + 1; ++i) { - historyManager->undo(deckListModel->getDeckList()); - } + const auto undoStack = deckStateManager->getHistoryManager()->getUndoStack(); + int steps = undoStack.size() - index; + deckStateManager->undo(steps); } - deckListModel->rebuildTree(); - - emit deckListModel->layoutChanged(); - emit requestDisplayWidgetSync(); - refreshList(); +} + +void DeckListHistoryManagerWidget::doUndo() +{ + deckStateManager->undo(); +} + +void DeckListHistoryManagerWidget::doRedo() +{ + deckStateManager->redo(); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h index 0e208ad2b..ab53912e2 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h @@ -14,6 +14,8 @@ #include #include +class DeckStateManager; + class DeckListHistoryManagerWidget : public QWidget { Q_OBJECT @@ -25,22 +27,19 @@ public slots: void retranslateUi(); public: - explicit DeckListHistoryManagerWidget(DeckListModel *deckListModel, + explicit DeckListHistoryManagerWidget(DeckStateManager *deckStateManager, DeckListStyleProxy *styleProxy, - DeckListHistoryManager *manager, QWidget *parent = nullptr); - void setDeckListModel(DeckListModel *_deckListModel); private slots: void refreshList(); - void onListClicked(QListWidgetItem *item); + void onListClicked(const QListWidgetItem *item); void doUndo(); void doRedo(); private: - DeckListModel *deckListModel; + DeckStateManager *deckStateManager; DeckListStyleProxy *styleProxy; - DeckListHistoryManager *historyManager; QHBoxLayout *layout; QAction *aUndo; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp new file mode 100644 index 000000000..628d33d51 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -0,0 +1,361 @@ +#include "deck_state_manager.h" + +#include +#include + +DeckStateManager::DeckStateManager(QObject *parent) + : QObject(parent), deckList(QSharedPointer(new DeckList)), + deckListModel(new DeckListModel(this, deckList)), historyManager(new DeckListHistoryManager(this)) +{ + connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, [this] { + setModified(true); + emit historyChanged(); + }); + connect(deckListModel, &DeckListModel::rowsInserted, this, &DeckStateManager::uniqueCardsChanged); + connect(deckListModel, &DeckListModel::rowsRemoved, this, &DeckStateManager::uniqueCardsChanged); +} + +const DeckList &DeckStateManager::getDeckList() const +{ + return *deckList.get(); +} + +LoadedDeck DeckStateManager::toLoadedDeck() const +{ + return {getDeckList(), lastLoadInfo}; +} + +DeckList::Metadata const &DeckStateManager::getMetadata() const +{ + return deckList->getMetadata(); +} + +QString DeckStateManager::getSimpleDeckName() const +{ + return deckList->getMetadata().name.simplified(); +} + +QString DeckStateManager::getDeckHash() const +{ + return deckList->getDeckHash(); +} + +bool DeckStateManager::isModified() const +{ + return modified; +} + +void DeckStateManager::setModified(bool state) +{ + if (state == modified) { + return; + } + + modified = state; + emit isModifiedChanged(modified); +} + +bool DeckStateManager::isBlankNewDeck() const +{ + return !isModified() && deckList->isBlankDeck(); +} + +void DeckStateManager::replaceDeck(const LoadedDeck &deck) +{ + lastLoadInfo = deck.lastLoadInfo; + deckList = QSharedPointer(new DeckList(deck.deckList)); + deckListModel->setDeckList(deckList); + + historyManager->clear(); + + setModified(false); + emit deckReplaced(); +} + +void DeckStateManager::clearDeck() +{ + replaceDeck(LoadedDeck()); +} + +bool DeckStateManager::modifyDeck(const QString &reason, const std::function &operation) +{ + DeckListMemento memento = deckList->createMemento(reason); + bool success = operation(deckListModel); + + if (success) { + historyManager->save(memento); + doCardModified(); + } + + return success; +} + +QModelIndex DeckStateManager::modifyDeck(const QString &reason, + const std::function &operation) +{ + DeckListMemento memento = deckList->createMemento(reason); + QModelIndex idx = operation(deckListModel); + + if (idx.isValid()) { + historyManager->save(memento); + doCardModified(); + } + + return idx; +} + +void DeckStateManager::setName(const QString &name) +{ + QString previous = deckList->getName(); + if (previous == name) { + return; + } + + requestHistorySave(tr("Rename deck to \"%1\" from \"%2\"").arg(name).arg(previous)); + deckList->setName(name); + + doMetadataModified(); +} + +void DeckStateManager::setComments(const QString &comments) +{ + QString previous = deckList->getComments(); + if (previous == comments) { + return; + } + + requestHistorySave(tr("Updated comments (was %1 chars, now %2 chars)").arg(previous.size()).arg(comments.size())); + deckList->setComments(comments); + + doMetadataModified(); +} + +void DeckStateManager::setBannerCard(const CardRef &bannerCard) +{ + CardRef previous = deckList->getBannerCard(); + if (previous == bannerCard) { + return; + } + + requestHistorySave(tr("Set banner card to %1 (%2)").arg(bannerCard.name).arg(bannerCard.providerId)); + deckList->setBannerCard(bannerCard); + + doMetadataModified(); +} + +void DeckStateManager::setTags(const QStringList &tags) +{ + QStringList previous = deckList->getTags(); + if (previous == tags) { + return; + } + + requestHistorySave(tr("Tags changed")); + deckList->setTags(tags); + + doMetadataModified(); +} + +void DeckStateManager::setFormat(const QString &format) +{ + if (deckList->getMetadata().gameFormat == format) { + return; + } + + requestHistorySave(tr("Set format to %1").arg(format)); + deckListModel->setActiveFormat(format); + + doMetadataModified(); +} + +QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zoneName) +{ + if (!card) { + return {}; + } + + QString reason = tr("Added (%1): %2 (%3) %4") + .arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), + card.getPrinting().getProperty("num")); + + QModelIndex idx = modifyDeck(reason, [&card, &zoneName](auto model) { return model->addCard(card, zoneName); }); + + if (idx.isValid()) { + emit focusIndexChanged(idx); + } + + return idx; +} + +QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString &zoneName) +{ + if (!card) + return {}; + + QString providerId = card.getPrinting().getUuid(); + QString collectorNumber = card.getPrinting().getProperty("num"); + + QModelIndex idx = deckListModel->findCard(card.getName(), zoneName, providerId, collectorNumber); + if (!idx.isValid()) { + return {}; + } + + bool success = offsetCountAtIndex(idx, false); + + if (!success) { + return {}; + } + + if (idx.isValid()) { + emit focusIndexChanged(idx); + } + + return idx; +} + +static bool doSwapCard(DeckListModel *model, + const QModelIndex &idx, + const QString &cardName, + const QString &providerId, + const QString &otherZone) +{ + bool success = model->offsetCountAtIndex(idx, -1); + if (!success) { + return false; + } + + if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, providerId})) { + model->addCard(card, otherZone); + } else { + // Third argument (true) says create the card no matter what, even if not in DB + model->addPreferredPrintingCard(cardName, otherZone, true); + } + + return true; +} + +bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx) +{ + if (!idx.isValid()) + return false; + + QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); + QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); + QModelIndex gparent = idx.parent().parent(); + + if (!gparent.isValid()) + return false; + + QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; + + QString reason = tr("Moved to %1 1 × \"%2\" (%3)") // + .arg(otherZoneName) + .arg(cardName) + .arg(providerId); + + return modifyDeck(reason, [&idx, &cardName, &providerId, &otherZoneName](auto model) { + return doSwapCard(model, idx, cardName, providerId, otherZoneName); + }); +} + +bool DeckStateManager::removeCardAtIndex(const QModelIndex &idx) +{ + if (!idx.isValid() || deckListModel->hasChildren(idx)) { + return false; + } + + QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); + + QString reason = tr("Removed \"%1\" (all copies)").arg(cardName); + + return modifyDeck(reason, [&idx](auto model) { return model->removeRow(idx.row(), idx.parent()); }); +} + +bool DeckStateManager::incrementCountAtIndex(const QModelIndex &idx) +{ + return offsetCountAtIndex(idx, 1); +} + +bool DeckStateManager::decrementCountAtIndex(const QModelIndex &idx) +{ + return offsetCountAtIndex(idx, -1); +} + +bool DeckStateManager::offsetCountAtIndex(const QModelIndex &idx, int offset) +{ + if (!idx.isValid()) { + return false; + } + + QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); + + QString reason = tr("%1 1 × \"%2\" (%3)") // + .arg(offset > 0 ? tr("Added") : tr("Removed")) + .arg(cardName) + .arg(providerId); + + return modifyDeck(reason, [&idx, &offset](auto model) { return model->offsetCountAtIndex(idx, offset); }); +} + +void DeckStateManager::undo(int steps) +{ + if (!historyManager->canUndo()) { + return; + } + + for (int i = 0; i < steps; i++) { + if (!historyManager->canUndo()) { + continue; + } + historyManager->undo(deckList.get()); + } + + deckListModel->rebuildTree(); + + emit deckListModel->layoutChanged(); +} + +void DeckStateManager::redo(int steps) +{ + if (!historyManager->canRedo()) { + return; + } + + for (int i = 0; i < steps; i++) { + if (!historyManager->canRedo()) { + continue; + } + historyManager->redo(deckList.get()); + } + + deckListModel->rebuildTree(); + + emit deckListModel->layoutChanged(); +} + +void DeckStateManager::requestHistorySave(const QString &reason) +{ + historyManager->save(deckList->createMemento(reason)); +} + +/** + * @brief Handles updating state and emitting signals whenever the cards are modified + */ +void DeckStateManager::doCardModified() +{ + setModified(true); + emit cardModified(); + emit deckModified(); +} + +/** + * @brief Handles updating state and emitting signals whenever the metadata is modified + */ +void DeckStateManager::doMetadataModified() +{ + setModified(true); + emit metadataModified(); + emit deckModified(); +} diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h new file mode 100644 index 000000000..0f3ba3255 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h @@ -0,0 +1,297 @@ +#ifndef COCKATRICE_DECK_STATE_MANAGER_H +#define COCKATRICE_DECK_STATE_MANAGER_H + +#include "../../deck_loader/loaded_deck.h" +#include "deck_list_model.h" + +#include +#include + +class DeckListHistoryManager; + +/** + * @brief This class centralizes the management of the state of the deck in the deck editor tab. + * It is responsible for owning and managing the DeckListModel, underlying DeckList, load info, and edit history. + * + * Although this class provides getters for the underlying DeckListModel, you should generally refrain from directly + * modifying the returned model. Outside modifications to the deck state should be done through @link + * DeckStateManager::modifyDeck and the metadata setters. + * Those methods ensure that the history is recorded and correct signals are emitted. + */ +class DeckStateManager : public QObject +{ + Q_OBJECT + + LoadedDeck::LoadInfo lastLoadInfo; + QSharedPointer deckList; + DeckListModel *deckListModel; + DeckListHistoryManager *historyManager; + + bool modified = false; + +public: + explicit DeckStateManager(QObject *parent = nullptr); + + /** + * Gets the underlying HistoryManager. + * @return The DeckListHistoryManager instance + */ + DeckListHistoryManager *getHistoryManager() const + { + return historyManager; + } + + /** + * @brief Gets the underlying DeckListModel. + * You should generally refrain modifying the returned model directly. + * However, it's fine (and intended) to perform queries on the returned model. + * @return The DeckListModel instance + */ + DeckListModel *getModel() const + { + return deckListModel; + } + + /** + * @brief Gets a view of the current deck. + */ + const DeckList &getDeckList() const; + + /** + * @brief Creates a LoadedDeck containing the contents of the current deck and the current LoadInfo. + * + * @return A new LoadedDeck instance. + */ + LoadedDeck toLoadedDeck() const; + + /** + * @brief Gets a view of the metadata in the DeckList + */ + DeckList::Metadata const &getMetadata() const; + + /** + * @brief Gets the deck's simplified name. + */ + QString getSimpleDeckName() const; + + /** + * @brief Gets the deck hash. + */ + QString getDeckHash() const; + + /** + * @brief Checks if the deck has been modified since it was last saved + */ + bool isModified() const; + + /** + * @brief Sets the new isModified state, emitting a signal if the state changed. + * This class will automatically update its isModified state, but you may need to set it manually to handle, for + * example, saving. + * @param state The state + */ + void setModified(bool state); + + /** + * @brief Checks if the deck state is as if it was a new deck + */ + bool isBlankNewDeck() const; + + /** + * @brief Overwrites the current deck with a new deck, resetting all history + * @param deck The new deck. + */ + void replaceDeck(const LoadedDeck &deck); + + /** + * @brief Resets the deck to a blank new deck, resetting all history. + */ + void clearDeck(); + + /** + * @brief Sets the lastLoadInfo. + * @param loadInfo The lastLoadInfo + */ + void setLastLoadInfo(const LoadedDeck::LoadInfo &loadInfo) + { + lastLoadInfo = loadInfo; + } + + /** + * @brief Modifies the cards in the deck, in a wrapped operation that is saved to the history. + * + * The operation is a function that accepts a DeckListModel that it operates upon, and returns a bool. + * + * This method will pass the underlying DeckListModel into the operation function. The function can call methods on + * the model to modify the deck. + * The function should return a bool to indicate success/failure. + * + * If the operation returns true, the state of the deck before the operation is ran is saved to the history, and the + * isModified state is updated. + * If the operation returns false, the history and isModified state is not updated. + * + * Note that even if the operation fails, any modifications to the model will already have been made. + * It's recommended for the operation to always return true if any modification has already been made to the model, + * as not doing that may cause the state to become desynced. + * + * @param reason The reason to display in the history + * @param operation The modification operation. + * @return The bool returned from the operation + */ + bool modifyDeck(const QString &reason, const std::function &operation); + + /** + * @brief Modifies the cards in the deck, in a wrapped operation that is saved to the history. + * + * The operation is a function that accepts a DeckListModel that it operates upon, and returns a QModelIndex. + * If the index is invalid, then the operation is considered to be a failure. + * + * See the other @link DeckStateManager::modifyDeck for more info about the behavior of this method. + * + * @param reason The reason to display in the history + * @param operation The modification operation. + * @return The QModelIndex returned from the operation + */ + QModelIndex modifyDeck(const QString &reason, const std::function &operation); + + /// @name Metadata setters + /// @brief These methods set the metadata. Will no-op if the new value is the same as the current value. + /// Saves the operation to history if successful. + ///@{ + void setName(const QString &name); + void setComments(const QString &comments); + void setBannerCard(const CardRef &bannerCard); + void setTags(const QStringList &tags); + void setFormat(const QString &format); + ///@} + + /** + * @brief Adds the given card to the given zone. + * Saves the operation to history if successful. + * + * @param card The card to add + * @param zoneName The zone to add the card to + * @return The index of the added card + */ + QModelIndex addCard(const ExactCard &card, const QString &zoneName); + + /** + * @brief Removes 1 copy of the given card from the given zone. + * Saves the operation to history if successful. + * + * @param card The card to remove + * @param zoneName The zone to remove the card from + * @return The index of the removed card. Will be invalid if the last copy was removed. + */ + QModelIndex decrementCard(const ExactCard &card, const QString &zoneName); + + /** + * @brief Swaps one copy of the card at the given index between the maindeck and sideboard. + * No-ops if index is invalid or not a card node. + * Saves the operation to history if successful. + * + * @param idx The model index + * @return Whether the operation was successfully performed + */ + bool swapCardAtIndex(const QModelIndex &idx); + + /** + * @brief Removes all copies of the card at the given index. + * No-ops if index is invalid or not a card node. + * Saves the operation to history if successful. + * + * @param idx The model index + * @return Whether the operation was successfully performed + */ + bool removeCardAtIndex(const QModelIndex &idx); + + /** + * @brief Increments the number of copies of the card at the given index by 1. + * No-ops if index is invalid or not a card node. + * Saves the operation to history if successful. + * + * @param idx The model index + * @return Whether the operation was successfully performed + */ + bool incrementCountAtIndex(const QModelIndex &idx); + + /** + * @brief Decrements the number of copies of the card at the given index by 1. + * No-ops if index is invalid or not a card node. + * Saves the operation to history if successful. + * + * @param idx The model index + * @return Whether the operation was successfully performed + */ + bool decrementCountAtIndex(const QModelIndex &idx); + + /** + * Undoes n steps of the history, setting the decklist state and updating the current step in the historyManager. + * @param steps Number of steps to undo. + */ + void undo(int steps = 1); + + /** + * Redoes n steps of the history, setting the decklist state and updating the current step in the historyManager. + * @param steps Number of steps to redo. + */ + void redo(int steps = 1); + +public slots: + /** + * Saves the current decklist state to history. + * @param reason The reason that is shown in the history. + */ + void requestHistorySave(const QString &reason); + +private: + bool offsetCountAtIndex(const QModelIndex &idx, int offset); + void doCardModified(); + void doMetadataModified(); + +signals: + /** + * A modification has been made to the cards in the deck + */ + void cardModified(); + + /** + * A card that wasn't previously in the deck was added to the deck, or the last copy of a card was removed from the + * deck. + */ + void uniqueCardsChanged(); + + /** + * A modification has been made to the metadata in the deck + */ + void metadataModified(); + + /** + * A modification has been made to the cards or metadata in the deck + */ + void deckModified(); + + /** + * The history has been greatly changed and needs to be reloaded. + */ + void historyChanged(); + + /** + * The deck has been completely changed. + */ + void deckReplaced(); + + /** + * The isModified state of the deck has changed + * @param isModified the new state + */ + void isModifiedChanged(bool isModified); + + /** + * The selected card on any views connected to this deck should be changed to this index. + * @param index The model index + */ + void focusIndexChanged(QModelIndex index); +}; + +#endif // COCKATRICE_DECK_STATE_MANAGER_H \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp index 587407065..6ed6b67a4 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp @@ -2,6 +2,7 @@ #include "../../deck_loader/card_node_function.h" #include "../../deck_loader/deck_loader.h" +#include "../deck_editor/deck_state_manager.h" #include "../interface/widgets/cards/card_info_picture_widget.h" #include "../interface/widgets/general/layout_containers/flow_widget.h" @@ -21,7 +22,8 @@ #include #include -DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckListModel *_model) : QDialog(parent), model(_model) +DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManger) + : QDialog(parent), deckStateManager(deckStateManger) { setMinimumSize(500, 500); setAcceptDrops(true); @@ -165,36 +167,39 @@ void DlgSelectSetForCards::actOK() if (modifiedSetsAndCardsMap.isEmpty()) { accept(); // Nothing to do - } else { - emit deckAboutToBeModified(tr("Bulk modified printings.")); + return; } - for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { - for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { - swapPrinting(model, modifiedSet, card); + auto bulkModify = [&modifiedSetsAndCardsMap](DeckListModel *model) { + for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { + for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { + swapPrinting(model, modifiedSet, card); + } } - } + return true; + }; + + deckStateManager->modifyDeck(tr("Bulk modified printings."), bulkModify); - if (!modifiedSetsAndCardsMap.isEmpty()) { - emit deckModified(); - } accept(); } void DlgSelectSetForCards::actClear() { - emit deckAboutToBeModified(tr("Cleared all printing information.")); - model->forEachCard(CardNodeFunction::ClearPrintingData()); - emit deckModified(); + deckStateManager->modifyDeck(tr("Cleared all printing information."), [](auto model) { + model->forEachCard(CardNodeFunction::ClearPrintingData()); + return true; + }); accept(); } void DlgSelectSetForCards::actSetAllToPreferred() { - emit deckAboutToBeModified(tr("Set all printings to preferred.")); - model->forEachCard(CardNodeFunction::ClearPrintingData()); - model->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); - emit deckModified(); + deckStateManager->modifyDeck(tr("Set all printings to preferred."), [](auto model) { + model->forEachCard(CardNodeFunction::ClearPrintingData()); + model->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); + return true; + }); accept(); } @@ -227,10 +232,8 @@ void DlgSelectSetForCards::sortSetsByCount() QMap DlgSelectSetForCards::getSetsForCards() { QMap setCounts; - if (!model) - return setCounts; - QList cardNames = model->getCardNames(); + QList cardNames = deckStateManager->getModel()->getCardNames(); for (auto cardName : cardNames) { CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); @@ -269,7 +272,7 @@ void DlgSelectSetForCards::updateCardLists() } } - QList cardNames = model->getCardNames(); + QList cardNames = deckStateManager->getModel()->getCardNames(); for (auto cardName : cardNames) { bool found = false; @@ -351,10 +354,8 @@ void DlgSelectSetForCards::dropEvent(QDropEvent *event) QMap DlgSelectSetForCards::getCardsForSets() { QMap setCards; - if (!model) - return setCards; - QList cardNames = model->getCardNames(); + QList cardNames = deckStateManager->getModel()->getCardNames(); for (auto cardName : cardNames) { CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h index 5cdef5a30..795366b57 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h @@ -18,6 +18,7 @@ #include #include +class DeckStateManager; class SetEntryWidget; // Forward declaration class DlgSelectSetForCards : public QDialog @@ -25,7 +26,7 @@ class DlgSelectSetForCards : public QDialog Q_OBJECT public: - explicit DlgSelectSetForCards(QWidget *parent, DeckListModel *_model); + explicit DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManager); void retranslateUi(); void sortSetsByCount(); QMap getCardsForSets(); @@ -37,7 +38,6 @@ public: signals: void widgetOrderChanged(); void orderChanged(); - void deckAboutToBeModified(const QString &reason); void deckModified(); public slots: @@ -61,7 +61,7 @@ private: QLabel *modifiedCardsLabel; QWidget *listContainer; QListWidget *listWidget; - DeckListModel *model; + DeckStateManager *deckStateManager; QMap setEntries; QPushButton *clearButton; QPushButton *setAllToPreferredButton; diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp index d8bd88b37..b1346e4fd 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -11,16 +11,12 @@ * UI elements for managing card counts in both the mainboard and sideboard zones. * * @param parent The parent widget. - * @param deckEditor Pointer to the TabDeckEditor. - * @param deckModel Pointer to the DeckListModel. - * @param deckView Pointer to the QTreeView for the deck display. + * @param deckStateManager Pointer to the DeckStateManager * @param cardSizeSlider Pointer to the QSlider used for dynamic font resizing. * @param rootCard The root card for the widget. */ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard) : QWidget(parent), cardSizeSlider(cardSizeSlider) @@ -32,11 +28,9 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, setContentsMargins(5, 5, 5, 5); // Padding around the text zoneLabelMainboard = new ShadowBackgroundLabel(this, tr("Mainboard")); - buttonBoxMainboard = - new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_MAIN); + buttonBoxMainboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_MAIN); zoneLabelSideboard = new ShadowBackgroundLabel(this, tr("Sideboard")); - buttonBoxSideboard = - new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_SIDE); + buttonBoxSideboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_SIDE); layout->addWidget(zoneLabelMainboard, 0, Qt::AlignHCenter | Qt::AlignBottom); layout->addWidget(buttonBoxMainboard, 0, Qt::AlignHCenter | Qt::AlignTop); diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index 6ce10cf2e..5a03c5f4a 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -18,9 +18,7 @@ class AllZonesCardAmountWidget : public QWidget Q_OBJECT public: explicit AllZonesCardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard); int getMainboardAmount(); diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index d01725cc4..62c8e2d60 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -1,5 +1,7 @@ #include "card_amount_widget.h" +#include "../deck_editor/deck_state_manager.h" + #include #include @@ -7,22 +9,17 @@ * @brief Constructs a widget for displaying and controlling the card count in a specific zone. * * @param parent The parent widget. - * @param deckEditor Pointer to the TabDeckEditor instance. - * @param deckModel Pointer to the DeckListModel instance. - * @param deckView Pointer to the QTreeView displaying the deck. * @param cardSizeSlider Pointer to the QSlider for adjusting font size. * @param rootCard The root card to manage within the widget. * @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE). */ CardAmountWidget::CardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard, const QString &zoneName) - : QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider), - rootCard(rootCard), zoneName(zoneName), hovered(false) + : QWidget(parent), deckStateManager(deckStateManager), cardSizeSlider(cardSizeSlider), rootCard(rootCard), + zoneName(zoneName), hovered(false) { layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -56,15 +53,10 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, layout->addWidget(incrementButton); // React to model changes - connect(deckModel, &DeckListModel::dataChanged, this, &CardAmountWidget::updateCardCount); - connect(deckModel, &QAbstractItemModel::rowsRemoved, this, &CardAmountWidget::updateCardCount); + connect(deckStateManager, &DeckStateManager::cardModified, this, &CardAmountWidget::updateCardCount); // Connect slider for dynamic font size adjustment connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); - - if (deckEditor) { - connect(this, &CardAmountWidget::deckModified, deckEditor, &AbstractTabDeckEditor::onDeckHistorySaveRequested); - } } /** @@ -168,7 +160,7 @@ static QModelIndex addAndReplacePrintings(DeckListModel *model, void CardAmountWidget::addPrinting(const QString &zone) { // Check if we will need to add extra copies due to replacing copies without providerIds - QModelIndex existing = deckModel->findCard(rootCard.getName(), zone); + QModelIndex existing = deckStateManager->getModel()->findCard(rootCard.getName(), zone); int extraCopies = 0; bool replacingProviderless = false; @@ -192,15 +184,13 @@ void CardAmountWidget::addPrinting(const QString &zone) .arg(rootCard.getPrinting().getUuid()) .arg(replacingProviderless ? " (replaced providerless printings)" : ""); - emit deckModified(reason); - // Add the card and expand the list UI - auto newCardIndex = addAndReplacePrintings(deckModel, existing, rootCard, zone, extraCopies, replacingProviderless); + QModelIndex newCardIndex = deckStateManager->modifyDeck(reason, [&](auto model) { + return addAndReplacePrintings(model, existing, rootCard, zone, extraCopies, replacingProviderless); + }); if (newCardIndex.isValid()) { - deckView->setCurrentIndex(newCardIndex); - deckView->setFocus(Qt::FocusReason::MouseFocusReason); - deckEditor->setModified(true); + emit deckStateManager->focusIndexChanged(newCardIndex); } } @@ -250,13 +240,11 @@ void CardAmountWidget::decrementCardHelper(const QString &zone) .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") .arg(rootCard.getPrinting().getUuid()); - emit deckModified(reason); - - QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), + deckStateManager->modifyDeck(reason, [this, &zone](auto model) { + QModelIndex idx = model->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), rootCard.getPrinting().getProperty("num")); - - deckModel->offsetCountAtIndex(idx, -1); - deckEditor->setModified(true); + return model->offsetCountAtIndex(idx, -1); + }); } /** @@ -273,7 +261,7 @@ int CardAmountWidget::countCardsInZone(const QString &deckZone) return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us. } - QList cards = deckModel->getCardsForZone(deckZone); + QList cards = deckStateManager->getModel()->getCardsForZone(deckZone); return std::count_if(cards.cbegin(), cards.cend(), [&uuid](const ExactCard &card) { return card.getPrinting().getUuid() == uuid; }); diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index b4704cede..983416782 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -27,9 +27,7 @@ signals: public: explicit CardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard, const QString &zoneName); @@ -44,9 +42,7 @@ protected: void showEvent(QShowEvent *event) override; private: - AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; + DeckStateManager *deckStateManager; QSlider *cardSizeSlider; ExactCard rootCard; QString zoneName; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index e64d6a009..95d6b2cdf 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -3,6 +3,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../../interface/card_picture_loader/card_picture_loader.h" #include "../../../interface/widgets/dialogs/dlg_select_set_for_cards.h" +#include "../deck_editor/deck_state_manager.h" #include "printing_selector_card_display_widget.h" #include "printing_selector_card_search_widget.h" #include "printing_selector_card_selection_widget.h" @@ -21,12 +22,9 @@ * * @param parent The parent widget for the PrintingSelector. * @param deckEditor The TabDeckEditor instance used for managing the deck. - * @param deckModel The DeckListModel instance that provides data for the deck's contents. - * @param deckView The QTreeView instance used to display the deck and its contents. */ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deckEditor) - : QWidget(parent), deckEditor(_deckEditor), deckModel(deckEditor->deckDockWidget->deckModel), - deckView(deckEditor->deckDockWidget->deckView) + : QWidget(parent), deckEditor(_deckEditor), deckStateManager(_deckEditor->deckStateManager) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout = new QVBoxLayout(this); @@ -74,13 +72,12 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck layout->addWidget(flowWidget); - cardSelectionBar = new PrintingSelectorCardSelectionWidget(this); + cardSelectionBar = new PrintingSelectorCardSelectionWidget(this, deckStateManager); cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible()); layout->addWidget(cardSelectionBar); // Connect deck model data change signal to update display - connect(deckModel, &DeckListModel::rowsInserted, this, &PrintingSelector::printingsInDeckChanged); - connect(deckModel, &DeckListModel::rowsRemoved, this, &PrintingSelector::printingsInDeckChanged); + connect(deckStateManager, &DeckStateManager::uniqueCardsChanged, this, &PrintingSelector::printingsInDeckChanged); retranslateUi(); } @@ -152,7 +149,8 @@ void PrintingSelector::getAllSetsForCurrentCard() QList printingsToUse; if (SettingsCache::instance().getBumpSetsWithCardsInDeckToTop()) { - printingsToUse = sortToolBar->prependPrintingsInDeck(filteredPrintings, selectedCard, deckModel); + printingsToUse = + sortToolBar->prependPrintingsInDeck(filteredPrintings, selectedCard, deckStateManager->getModel()); } else { printingsToUse = filteredPrintings; } @@ -164,7 +162,7 @@ void PrintingSelector::getAllSetsForCurrentCard() connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable { for (int i = 0; i < BATCH_SIZE && currentIndex < printingsToUse.size(); ++i, ++currentIndex) { auto card = ExactCard(selectedCard, printingsToUse[currentIndex]); - auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckModel, deckView, + auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckStateManager, cardSizeWidget->getSlider(), card); flowWidget->addWidget(cardDisplayWidget); cardDisplayWidget->clampSetNameToPicture(); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index e34ce3fe6..e1c07addf 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -21,6 +21,7 @@ #define BATCH_SIZE 10 +class DeckStateManager; class PrintingSelectorCardSearchWidget; class PrintingSelectorCardSelectionWidget; class PrintingSelectorCardSortingWidget; @@ -35,15 +36,6 @@ public: void setCard(const CardInfoPtr &newCard); void getAllSetsForCurrentCard(); - [[nodiscard]] DeckListModel *getDeckModel() const - { - return deckModel; - } - - [[nodiscard]] AbstractTabDeckEditor *getDeckEditor() const - { - return deckEditor; - } public slots: void retranslateUi(); @@ -75,8 +67,7 @@ private: CardSizeWidget *cardSizeWidget; PrintingSelectorCardSelectionWidget *cardSelectionBar; AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; + DeckStateManager *deckStateManager; CardInfoPtr selectedCard; QTimer *widgetLoadingBufferTimer; int currentIndex = 0; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index 86d6659a8..92cf2437c 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -18,15 +18,13 @@ * * @param parent The parent widget for this display. * @param deckEditor The TabDeckEditor instance for deck management. - * @param deckModel The DeckListModel instance providing deck data. - * @param deckView The QTreeView instance displaying the deck. + * @param deckStateManager The DeckStateManager instance providing deck data. * @param cardSizeSlider The slider controlling the size of the displayed card. * @param rootCard The root card object, representing the card to be displayed. */ PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard) : QWidget(parent) @@ -36,8 +34,7 @@ PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *pa setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Create the overlay widget for the card display - overlayWidget = - new PrintingSelectorCardOverlayWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard); + overlayWidget = new PrintingSelectorCardOverlayWidget(this, deckEditor, deckStateManager, cardSizeSlider, rootCard); connect(overlayWidget, &PrintingSelectorCardOverlayWidget::cardPreferenceChanged, this, [this]() { emit cardPreferenceChanged(); }); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h index 608c2df5c..2637b0e57 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h @@ -21,8 +21,7 @@ class PrintingSelectorCardDisplayWidget : public QWidget public: PrintingSelectorCardDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp index 04ab07a59..ac36f2cf4 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp @@ -22,15 +22,13 @@ * * @param parent The parent widget for this overlay. * @param _deckEditor The TabDeckEditor instance for deck management. - * @param deckModel The DeckListModel instance providing deck data. - * @param deckView The QTreeView instance displaying the deck. + * @param deckStateManager The DeckStateManager instance providing deck data. * @param cardSizeSlider The slider controlling the size of the card. * @param _rootCard The root card object that contains information about the card. */ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &_rootCard) : QWidget(parent), deckEditor(_deckEditor), rootCard(_rootCard) @@ -58,8 +56,7 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa updatePinBadgeVisibility(); // Add AllZonesCardAmountWidget - allZonesCardAmountWidget = - new AllZonesCardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, _rootCard); + allZonesCardAmountWidget = new AllZonesCardAmountWidget(this, deckStateManager, cardSizeSlider, _rootCard); allZonesCardAmountWidget->raise(); // Ensure it's on top of the picture // Set initial visibility based on amounts diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h index 3bd5ce247..8550612bd 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h @@ -20,8 +20,7 @@ class PrintingSelectorCardOverlayWidget : public QWidget public: explicit PrintingSelectorCardOverlayWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, + DeckStateManager *_deckStateManager, QSlider *_cardSizeSlider, const ExactCard &_rootCard); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp index 317ce83b6..2eb2ef245 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp @@ -11,7 +11,9 @@ * * @param parent The parent PrintingSelector widget responsible for managing card selection. */ -PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(PrintingSelector *parent) : parent(parent) +PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(PrintingSelector *parent, + DeckStateManager *deckStateManager) + : parent(parent), deckStateManager(deckStateManager) { cardSelectionBarLayout = new QHBoxLayout(this); cardSelectionBarLayout->setContentsMargins(9, 0, 9, 0); @@ -48,12 +50,6 @@ void PrintingSelectorCardSelectionWidget::connectSignals() void PrintingSelectorCardSelectionWidget::selectSetForCards() { - auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel()); - connect(setSelectionDialog, &DlgSelectSetForCards::deckAboutToBeModified, parent->getDeckEditor(), - &AbstractTabDeckEditor::onDeckHistorySaveRequested); - connect(setSelectionDialog, &DlgSelectSetForCards::deckModified, parent->getDeckEditor(), - &AbstractTabDeckEditor::onDeckModified); - if (!setSelectionDialog->exec()) { - return; - } + auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, deckStateManager); + setSelectionDialog->exec(); } diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h index a1176a76c..ecd5c83e3 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h @@ -18,7 +18,7 @@ class PrintingSelectorCardSelectionWidget : public QWidget Q_OBJECT public: - explicit PrintingSelectorCardSelectionWidget(PrintingSelector *parent); + explicit PrintingSelectorCardSelectionWidget(PrintingSelector *parent, DeckStateManager *deckStateManager); void connectSignals(); @@ -27,6 +27,7 @@ public slots: private: PrintingSelector *parent; + DeckStateManager *deckStateManager; QHBoxLayout *cardSelectionBarLayout; QPushButton *previousCardButton; QPushButton *selectSetForCardsButton; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp index af7cedbeb..725e5df90 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp @@ -183,7 +183,7 @@ QList PrintingSelectorCardSortingWidget::prependPinnedPrintings(co */ QList PrintingSelectorCardSortingWidget::prependPrintingsInDeck(const QList &printings, const CardInfoPtr &selectedCard, - DeckListModel *deckModel) + const DeckListModel *deckModel) { if (!selectedCard) { return {}; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h index d0faea4ac..b5a00b81e 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h @@ -23,7 +23,7 @@ public: QList prependPinnedPrintings(const QList &printings, const QString &cardName); QList prependPrintingsInDeck(const QList &printings, const CardInfoPtr &selectedCard, - DeckListModel *deckModel); + const DeckListModel *deckModel); public slots: void updateSortOrder(); diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp index cb2002199..29bcc1ab1 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -12,6 +12,7 @@ #include "../../../client/settings/cache_settings.h" #include "../client/network/interfaces/deck_stats_interface.h" #include "../client/network/interfaces/tapped_out_interface.h" +#include "../deck_editor/deck_state_manager.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/pixel_map_generator.h" #include "../interface/widgets/dialogs/dlg_load_deck.h" @@ -52,7 +53,7 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta { setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); - historyManager = new DeckListHistoryManager(this); + deckStateManager = new DeckStateManager(this); databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this); deckDockWidget = new DeckEditorDeckDockWidget(this); @@ -64,14 +65,8 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta }); // Connect deck signals to this tab - connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged); - connect(deckDockWidget, &DeckEditorDeckDockWidget::deckModified, this, &AbstractTabDeckEditor::onDeckModified); - connect(deckDockWidget, &DeckEditorDeckDockWidget::requestDeckHistorySave, this, - &AbstractTabDeckEditor::onDeckHistorySaveRequested); - connect(deckDockWidget, &DeckEditorDeckDockWidget::requestDeckHistoryClear, this, - &AbstractTabDeckEditor::onDeckHistoryClearRequested); - connect(deckDockWidget, &DeckEditorDeckDockWidget::cardChanged, this, &AbstractTabDeckEditor::updateCard); - connect(this, &AbstractTabDeckEditor::decrementCard, deckDockWidget, &DeckEditorDeckDockWidget::actDecrementCard); + connect(deckStateManager, &DeckStateManager::isModifiedChanged, this, &AbstractTabDeckEditor::onDeckModified); + connect(deckDockWidget, &DeckEditorDeckDockWidget::selectedCardChanged, this, &AbstractTabDeckEditor::updateCard); // Connect database display signals to this tab connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this, @@ -107,7 +102,6 @@ void AbstractTabDeckEditor::updateCard(const ExactCard &card) /** @brief Placeholder: called when the deck changes. */ void AbstractTabDeckEditor::onDeckChanged() { - historyManager->clear(); } /** @@ -115,24 +109,8 @@ void AbstractTabDeckEditor::onDeckChanged() */ void AbstractTabDeckEditor::onDeckModified() { - setModified(!isBlankNewDeck()); - deckMenu->setSaveStatus(!isBlankNewDeck()); -} - -/** - * @brief Marks the tab as modified and updates the save menu status. - */ -void AbstractTabDeckEditor::onDeckHistorySaveRequested(const QString &modificationReason) -{ - historyManager->save(deckDockWidget->getDeckList().createMemento(modificationReason)); -} - -/** - * @brief Marks the tab as modified and updates the save menu status. - */ -void AbstractTabDeckEditor::onDeckHistoryClearRequested() -{ - historyManager->clear(); + deckMenu->setSaveStatus(!deckStateManager->isBlankNewDeck()); + emit tabTextChanged(this, getTabText()); } /** @@ -142,7 +120,7 @@ void AbstractTabDeckEditor::onDeckHistoryClearRequested() */ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, const QString &zoneName) { - deckDockWidget->actAddCard(card, zoneName); + deckStateManager->addCard(card, zoneName); databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length()); } @@ -170,13 +148,13 @@ void AbstractTabDeckEditor::actAddCardToSideboard(const ExactCard &card) /** @brief Decrements a card from the main deck. */ void AbstractTabDeckEditor::actDecrementCard(const ExactCard &card) { - emit decrementCard(card, DECK_ZONE_MAIN); + deckStateManager->decrementCard(card, DECK_ZONE_MAIN); } /** @brief Decrements a card from the sideboard. */ void AbstractTabDeckEditor::actDecrementCardFromSideboard(const ExactCard &card) { - emit decrementCard(card, DECK_ZONE_SIDE); + deckStateManager->decrementCard(card, DECK_ZONE_SIDE); } /** @@ -198,45 +176,13 @@ void AbstractTabDeckEditor::openDeck(const LoadedDeck &deck) */ void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck) { - deckDockWidget->setDeck(_deck); - CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList().getCardRefList())); - setModified(false); + deckStateManager->replaceDeck(_deck); + CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(_deck.deckList.getCardRefList())); aDeckDockVisible->setChecked(true); deckDockWidget->setVisible(aDeckDockVisible->isChecked()); } -/** @brief Returns the currently loaded deck. */ -DeckLoader *AbstractTabDeckEditor::getDeckLoader() const -{ - return deckDockWidget->getDeckLoader(); -} - -/** @brief Returns the currently loaded deck list. */ -const DeckList &AbstractTabDeckEditor::getDeckList() const -{ - return deckDockWidget->getDeckList(); -} - -/** - * @brief Sets the modified state of the tab. - * @param _modified True if tab is modified, false otherwise. - */ -void AbstractTabDeckEditor::setModified(bool _modified) -{ - modified = _modified; - emit tabTextChanged(this, getTabText()); -} - -/** - * @brief Returns true if the tab is a blank newly created deck. - */ -bool AbstractTabDeckEditor::isBlankNewDeck() const -{ - const LoadedDeck &loadedDeck = deckDockWidget->getDeckLoader()->getDeck(); - return !modified && loadedDeck.isEmpty(); -} - /** @brief Creates a new deck. Handles opening in new tab if needed. */ void AbstractTabDeckEditor::actNewDeck() { @@ -255,9 +201,8 @@ void AbstractTabDeckEditor::actNewDeck() /** @brief Clears the current deck and resets modified flag. */ void AbstractTabDeckEditor::cleanDeckAndResetModified() { + deckStateManager->clearDeck(); deckMenu->setSaveStatus(false); - deckDockWidget->cleanDeck(); - setModified(false); } /** @@ -268,13 +213,13 @@ void AbstractTabDeckEditor::cleanDeckAndResetModified() AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank) { if (SettingsCache::instance().getOpenDeckInNewTab()) { - if (openInSameTabIfBlank && isBlankNewDeck()) + if (openInSameTabIfBlank && deckStateManager->isBlankNewDeck()) return SAME_TAB; else return NEW_TAB; } - if (!modified) + if (!deckStateManager->isModified()) return SAME_TAB; tabSupervisor->setCurrentWidget(this); @@ -325,7 +270,6 @@ void AbstractTabDeckEditor::actLoadDeck() QString fileName = dialog.selectedFiles().at(0); openDeckFromFile(fileName, deckOpenLocation); - deckDockWidget->updateBannerCardComboBox(); } /** @@ -371,7 +315,7 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo */ bool AbstractTabDeckEditor::actSaveDeck() { - const LoadedDeck &loadedDeck = getDeckLoader()->getDeck(); + const auto loadedDeck = deckStateManager->toLoadedDeck(); if (loadedDeck.lastLoadInfo.remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) { QString deckString = loadedDeck.deckList.writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { @@ -392,8 +336,10 @@ bool AbstractTabDeckEditor::actSaveDeck() if (loadedDeck.lastLoadInfo.fileName.isEmpty()) return actSaveDeckAs(); - if (getDeckLoader()->saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) { - setModified(false); + auto deckLoader = DeckLoader(this); + deckLoader.setDeck(loadedDeck); + if (deckLoader.saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) { + deckStateManager->setModified(false); return true; } @@ -409,12 +355,14 @@ bool AbstractTabDeckEditor::actSaveDeck() */ bool AbstractTabDeckEditor::actSaveDeckAs() { + LoadedDeck loadedDeck = deckStateManager->toLoadedDeck(); + QFileDialog dialog(this, tr("Save deck")); dialog.setDirectory(SettingsCache::instance().getDeckPath()); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("cod"); dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); - dialog.selectFile(getDeckList().getName().trimmed()); + dialog.selectFile(loadedDeck.deckList.getName().trimmed()); if (!dialog.exec()) return false; @@ -422,14 +370,18 @@ bool AbstractTabDeckEditor::actSaveDeckAs() QString fileName = dialog.selectedFiles().at(0); DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); - if (!getDeckLoader()->saveToFile(fileName, fmt)) { + DeckLoader deckLoader = DeckLoader(this); + deckLoader.setDeck(loadedDeck); + if (!deckLoader.saveToFile(fileName, fmt)) { QMessageBox::critical( this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); return false; } - setModified(false); + deckStateManager->setLastLoadInfo({.fileName = fileName, .fileFormat = fmt}); + + deckStateManager->setModified(false); SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName); return true; } @@ -443,7 +395,7 @@ void AbstractTabDeckEditor::saveDeckRemoteFinished(const Response &response) if (response.response_code() != Response::RespOk) QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.")); else - setModified(false); + deckStateManager->setModified(false); } /** @@ -464,7 +416,7 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() emit openDeckEditor({.deckList = dlg.getDeckList()}); } else { setDeck({.deckList = dlg.getDeckList()}); - setModified(true); + deckStateManager->setModified(true); } deckMenu->setSaveStatus(true); @@ -476,12 +428,13 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() */ void AbstractTabDeckEditor::editDeckInClipboard(bool annotated) { - DlgEditDeckInClipboard dlg(getDeckLoader()->getDeck().deckList, annotated, this); + LoadedDeck loadedDeck = deckStateManager->toLoadedDeck(); + DlgEditDeckInClipboard dlg(loadedDeck.deckList, annotated, this); if (!dlg.exec()) return; - setDeck({dlg.getDeckList(), getDeckLoader()->getDeck().lastLoadInfo}); - setModified(true); + setDeck({dlg.getDeckList(), loadedDeck.lastLoadInfo}); + deckStateManager->setModified(true); deckMenu->setSaveStatus(true); } @@ -500,25 +453,25 @@ void AbstractTabDeckEditor::actEditDeckInClipboardRaw() /** @brief Saves deck to clipboard with set info and annotation. */ void AbstractTabDeckEditor::actSaveDeckToClipboard() { - DeckLoader::saveToClipboard(getDeckList(), true, true); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), true, true); } /** @brief Saves deck to clipboard with annotation, without set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo() { - DeckLoader::saveToClipboard(getDeckList(), true, false); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), true, false); } /** @brief Saves deck to clipboard without annotations, with set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardRaw() { - DeckLoader::saveToClipboard(getDeckList(), false, true); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), false, true); } /** @brief Saves deck to clipboard without annotations or set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo() { - DeckLoader::saveToClipboard(getDeckList(), false, false); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), false, false); } /** @brief Prints the deck using a QPrintPreviewDialog. */ @@ -526,7 +479,7 @@ void AbstractTabDeckEditor::actPrintDeck() { auto *dlg = new QPrintPreviewDialog(this); connect(dlg, &QPrintPreviewDialog::paintRequested, this, - [this](QPrinter *printer) { DeckLoader::printDeckList(printer, getDeckList()); }); + [this](QPrinter *printer) { DeckLoader::printDeckList(printer, deckStateManager->getDeckList()); }); dlg->exec(); } @@ -547,7 +500,7 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() emit openDeckEditor({.deckList = dlg.getDeck()}); } else { setDeck({.deckList = dlg.getDeck()}); - setModified(true); + deckStateManager->setModified(true); } deckMenu->setSaveStatus(true); @@ -559,7 +512,7 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() */ void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) { - QString decklistUrlString = DeckLoader::exportDeckToDecklist(getDeckList(), website); + QString decklistUrlString = DeckLoader::exportDeckToDecklist(deckStateManager->getDeckList(), website); // Check to make sure the string isn't empty. if (decklistUrlString.isEmpty()) { // Show an error if the deck is empty, and return. @@ -592,14 +545,14 @@ void AbstractTabDeckEditor::actExportDeckDecklistXyz() void AbstractTabDeckEditor::actAnalyzeDeckDeckstats() { auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this); - interface->analyzeDeck(getDeckList()); + interface->analyzeDeck(deckStateManager->getDeckList()); } /** @brief Analyzes the deck using TappedOut. */ void AbstractTabDeckEditor::actAnalyzeDeckTappedout() { auto *interface = new TappedOutInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this); - interface->analyzeDeck(getDeckList()); + interface->analyzeDeck(deckStateManager->getDeckList()); } /** @brief Applies a new filter tree to the database display. */ @@ -658,7 +611,7 @@ bool AbstractTabDeckEditor::eventFilter(QObject *o, QEvent *e) /** @brief Shows a confirmation dialog before closing. */ bool AbstractTabDeckEditor::confirmClose() { - if (modified) { + if (deckStateManager->isModified()) { tabSupervisor->setCurrentWidget(this); int ret = createSaveConfirmationWindow()->exec(); if (ret == QMessageBox::Save) diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index bfbda778c..69b2b4a1c 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -19,6 +19,7 @@ #include +class DeckStateManager; class CardDatabaseModel; class CardDatabaseDisplayModel; @@ -117,30 +118,13 @@ public: */ void openDeck(const LoadedDeck &deck); - /** @brief Returns the currently active deck loader. */ - DeckLoader *getDeckLoader() const; - - /** @brief Returns the currently active deck list. */ - const DeckList &getDeckList() const; - - /** @brief Sets the modified state of the tab. - * @param _windowModified Whether the tab is modified. - */ - void setModified(bool _windowModified); - DeckEditorDeckDockWidget *getDeckDockWidget() const { return deckDockWidget; } - DeckListHistoryManager *getHistoryManager() const - { - return historyManager; - } - - DeckListHistoryManager *historyManager; - // UI Elements + DeckStateManager *deckStateManager; DeckEditorMenu *deckMenu; ///< Menu for deck operations DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget; ///< Database dock DeckEditorCardInfoDockWidget *cardInfoDockWidget; ///< Card info dock @@ -155,14 +139,6 @@ public slots: /** @brief Called when the deck is modified. */ virtual void onDeckModified(); - /** @brief Called when a widget is about to modify the state of the DeckList. - * @param modificationReason The reason for the state modification - */ - virtual void onDeckHistorySaveRequested(const QString &modificationReason); - - /** @brief Called when a widget would like to clear the history. */ - virtual void onDeckHistoryClearRequested(); - /** @brief Updates the card info panel. * @param card The card to display. */ @@ -202,9 +178,6 @@ signals: /** @brief Emitted before the tab is closed. */ void deckEditorClosing(AbstractTabDeckEditor *tab); - /** @brief Emitted when a card should be decremented. */ - void decrementCard(const ExactCard &card, QString zoneName); - protected slots: /** @brief Starts a new deck in this tab. */ virtual void actNewDeck(); @@ -315,9 +288,6 @@ protected: */ QMessageBox *createSaveConfirmationWindow(); - /** @brief Returns true if the tab is a blank newly created deck. */ - bool isBlankNewDeck() const; - /** @brief Helper function to add a card to a specific deck zone. */ void addCardHelper(const ExactCard &card, const QString &zoneName); @@ -330,8 +300,6 @@ protected: QAction *aResetLayout; QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating; QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating; - - bool modified = false; ///< Whether the deck/tab has unsaved changes }; #endif // TAB_GENERIC_DECK_EDITOR_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp index 1547cca74..3a2468368 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -68,7 +68,10 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi model = new DeckListModel(this); connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); - model->getDeckList()->loadFromStream_Plain(deckStream, false); + + auto decklist = QSharedPointer(new DeckList); + decklist->loadFromStream_Plain(deckStream, false); + model->setDeckList(decklist); model->forEachCard(CardNodeFunction::ResolveProviderId()); diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 4e3d0c57d..286252e13 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -1,6 +1,7 @@ #include "tab_deck_editor.h" #include "../../../client/settings/cache_settings.h" +#include "../deck_editor/deck_state_manager.h" #include "../filters/filter_builder.h" #include "../interface/pixel_map_generator.h" #include "../interface/widgets/cards/card_info_frame_widget.h" @@ -114,8 +115,8 @@ void TabDeckEditor::createMenus() */ QString TabDeckEditor::getTabText() const { - QString result = tr("Deck: %1").arg(deckDockWidget->getSimpleDeckName()); - if (modified) + QString result = tr("Deck: %1").arg(deckStateManager->getSimpleDeckName()); + if (deckStateManager->isModified()) result.prepend("* "); return result; } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index a124eaa01..f3d573d27 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -1,6 +1,7 @@ #include "tab_deck_editor_visual.h" #include "../../../../client/settings/cache_settings.h" +#include "../../deck_editor/deck_state_manager.h" #include "../../filters/filter_builder.h" #include "../../interface/pixel_map_generator.h" #include "../../interface/widgets/cards/card_info_frame_widget.h" @@ -61,7 +62,7 @@ void TabDeckEditorVisual::createCentralFrame() centralFrame = new QVBoxLayout; centralWidget->setLayout(centralFrame); - tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckDockWidget->deckModel, + tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckStateManager->getModel(), databaseDisplayDockWidget->databaseModel, databaseDisplayDockWidget->databaseDisplayModel); @@ -85,7 +86,7 @@ void TabDeckEditorVisual::onDeckChanged() AbstractTabDeckEditor::onDeckModified(); tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel(); tabContainer->deckAnalytics->refreshDisplays(); - tabContainer->sampleHandWidget->setDeckModel(deckDockWidget->deckModel); + tabContainer->sampleHandWidget->setDeckModel(deckStateManager->getModel()); } /** @brief Creates menus for deck editing and view options, including dock actions. */ @@ -149,8 +150,8 @@ void TabDeckEditorVisual::createMenus() /** @brief Returns the tab text, prepending a mark if the deck has unsaved changes. */ QString TabDeckEditorVisual::getTabText() const { - QString result = tr("Visual Deck: %1").arg(deckDockWidget->getSimpleDeckName()); - if (modified) + QString result = tr("Visual Deck: %1").arg(deckStateManager->getSimpleDeckName()); + if (deckStateManager->isModified()) result.prepend("* "); return result; } @@ -166,9 +167,9 @@ void TabDeckEditorVisual::changeModelIndexAndCardInfo(const ExactCard &activeCar void TabDeckEditorVisual::changeModelIndexToCard(const ExactCard &activeCard) { QString cardName = activeCard.getName(); - QModelIndex index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_MAIN); + QModelIndex index = deckStateManager->getModel()->findCard(cardName, DECK_ZONE_MAIN); if (!index.isValid()) { - index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_SIDE); + index = deckStateManager->getModel()->findCard(cardName, DECK_ZONE_SIDE); } if (!deckDockWidget->getSelectionModel()->hasSelection()) { deckDockWidget->getSelectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); @@ -182,7 +183,7 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, auto card = instance->getCard(); // Get the model index for the card - QModelIndex idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); + QModelIndex idx = deckStateManager->getModel()->findCard(card.getName(), zoneName); if (!idx.isValid()) { return; } @@ -191,8 +192,8 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, // Double click = swap if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton) { - deckDockWidget->actSwapCard(card, zoneName); - idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); + deckStateManager->swapCardAtIndex(idx); + idx = deckStateManager->getModel()->findCard(card.getName(), zoneName); sel->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect); return; } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index 7551954f3..5098696dd 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -2,6 +2,7 @@ #include "../../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" +#include "../deck_editor/deck_state_manager.h" #include @@ -60,7 +61,7 @@ void VisualDatabaseDisplayNameFilterWidget::retranslateUi() void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() { - DeckListModel *deckListModel = deckEditor->deckDockWidget->deckModel; + DeckListModel *deckListModel = deckEditor->deckStateManager->getModel(); if (!deckListModel) return; diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp index 6f479616e..ece3bc2f8 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -5,14 +5,15 @@ DeckListModel::DeckListModel(QObject *parent) : QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder) { - // This class will leak the decklist object. We cannot safely delete it in the dtor because the deckList field is a - // non-owning pointer and another deckList might have been assigned to it. - // `DeckListModel::cleanList` also leaks for the same reason. - // TODO: fix the leak - deckList = new DeckList; + deckList = QSharedPointer(new DeckList()); root = new InnerDecklistNode; } +DeckListModel::DeckListModel(QObject *parent, const QSharedPointer &deckList) : DeckListModel(parent) +{ + setDeckList(deckList); +} + DeckListModel::~DeckListModel() { delete root; @@ -586,13 +587,13 @@ void DeckListModel::setActiveFormat(const QString &_format) void DeckListModel::cleanList() { - setDeckList(new DeckList); + setDeckList(QSharedPointer(new DeckList())); } /** * @param _deck The deck. */ -void DeckListModel::setDeckList(DeckList *_deck) +void DeckListModel::setDeckList(const QSharedPointer &_deck) { if (deckList != _deck) { deckList = _deck; diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h index b6292d689..e6f10c072 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -245,6 +245,7 @@ signals: public: explicit DeckListModel(QObject *parent = nullptr); + explicit DeckListModel(QObject *parent, const QSharedPointer &deckList); ~DeckListModel() override; /** @@ -314,11 +315,12 @@ public: * @brief Removes all cards and resets the model. */ void cleanList(); - [[nodiscard]] DeckList *getDeckList() const + + [[nodiscard]] QSharedPointer getDeckList() const { return deckList; } - void setDeckList(DeckList *_deck); + void setDeckList(const QSharedPointer &_deck); /** * @brief Apply a function to every card in the deck tree. @@ -351,8 +353,8 @@ public: [[nodiscard]] QList getZones() const; private: - DeckList *deckList; /**< Pointer to the deck loader providing the underlying data. */ - InnerDecklistNode *root; /**< Root node of the model tree. */ + QSharedPointer deckList; /**< Pointer to the decklist providing the underlying data. */ + InnerDecklistNode *root; /**< Root node of the model tree. */ DeckListModelGroupCriteria::Type activeGroupCriteria = DeckListModelGroupCriteria::MAIN_TYPE; int lastKnownColumn; /**< Last column used for sorting. */ Qt::SortOrder lastKnownOrder; /**< Last known sort order. */