[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
This commit is contained in:
RickyRister 2025-12-31 08:54:47 -08:00 committed by GitHub
parent 0085015ebe
commit b2dd8eed3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 933 additions and 577 deletions

View file

@ -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)

View file

@ -19,6 +19,7 @@
#include <libcockatrice/deck_list/deck_list_history_manager.h>
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

View file

@ -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<DeckList>(new DeckList);
decklist->loadFromStream_Plain(deckStream, false);
model->setDeckList(decklist);
model->forEachCard(CardNodeFunction::ResolveProviderId());

View file

@ -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;
}

View file

@ -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;
}