diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 03a64ba3a..522300346 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -145,6 +145,7 @@ set(cockatrice_SOURCES src/interface/widgets/deck_analytics/mana_base_widget.cpp src/interface/widgets/deck_analytics/mana_curve_widget.cpp src/interface/widgets/deck_analytics/mana_devotion_widget.cpp + src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 469cf3d77..99f5bff23 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -7,11 +7,14 @@ resources/icons/arrow_bottom_green.svg resources/icons/arrow_down_green.svg + resources/icons/arrow_history.svg resources/icons/arrow_left_green.svg + resources/icons/arrow_redo.svg resources/icons/arrow_right_blue.svg resources/icons/arrow_right_green.svg resources/icons/arrow_top_green.svg resources/icons/arrow_up_green.svg + resources/icons/arrow_undo.svg resources/icons/clearsearch.svg resources/icons/cogwheel.svg resources/icons/conceded.svg 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 11818a912..97f87dd52 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 @@ -28,6 +28,8 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent void DeckEditorDeckDockWidget::createDeckDock() { + historyManager = new DeckListHistoryManager(); + deckModel = new DeckListModel(this); deckModel->setObjectName("deckModel"); connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); @@ -37,6 +39,10 @@ void DeckEditorDeckDockWidget::createDeckDock() proxy = new DeckListStyleProxy(this); proxy->setSourceModel(deckModel); + historyManagerWidget = new DeckListHistoryManagerWidget(deckModel, proxy, historyManager, this); + connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this, + &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); + deckView = new QTreeView(); deckView->setObjectName("deckView"); deckView->setModel(proxy); @@ -65,7 +71,15 @@ void DeckEditorDeckDockWidget::createDeckDock() nameEdit->setMaxLength(MAX_NAME_LENGTH); nameEdit->setObjectName("nameEdit"); nameLabel->setBuddy(nameEdit); - connect(nameEdit, &LineEditUnfocusable::textChanged, this, &DeckEditorDeckDockWidget::updateName); + + nameDebounceTimer = new QTimer(this); + nameDebounceTimer->setSingleShot(true); + nameDebounceTimer->setInterval(300); // debounce duration in ms + connect(nameDebounceTimer, &QTimer::timeout, this, [this]() { updateName(nameEdit->text()); }); + + connect(nameEdit, &LineEditUnfocusable::textChanged, this, [this]() { + nameDebounceTimer->start(); // restart debounce timer + }); quickSettingsWidget = new SettingsButtonWidget(this); @@ -95,7 +109,16 @@ void DeckEditorDeckDockWidget::createDeckDock() commentsEdit->setMinimumHeight(nameEdit->minimumSizeHint().height()); commentsEdit->setObjectName("commentsEdit"); commentsLabel->setBuddy(commentsEdit); - connect(commentsEdit, &QTextEdit::textChanged, this, &DeckEditorDeckDockWidget::updateComments); + + commentsDebounceTimer = new QTimer(this); + commentsDebounceTimer->setSingleShot(true); + commentsDebounceTimer->setInterval(400); // longer debounce for multi-line + connect(commentsDebounceTimer, &QTimer::timeout, this, [this]() { updateComments(); }); + + connect(commentsEdit, &QTextEdit::textChanged, this, [this]() { + commentsDebounceTimer->start(); // restart debounce timer + }); + bannerCardLabel = new QLabel(); bannerCardLabel->setObjectName("bannerCardLabel"); bannerCardLabel->setText(tr("Banner Card")); @@ -187,7 +210,8 @@ void DeckEditorDeckDockWidget::createDeckDock() lowerLayout->addWidget(tbDecrement, 0, 3); lowerLayout->addWidget(tbRemoveCard, 0, 4); lowerLayout->addWidget(tbSwapCard, 0, 5); - lowerLayout->addWidget(deckView, 1, 0, 1, 6); + lowerLayout->addWidget(historyManagerWidget, 0, 6); + lowerLayout->addWidget(deckView, 1, 0, 1, 7); // Create widgets for both layouts to make splitter work correctly auto *topWidget = new QWidget; @@ -249,6 +273,8 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const void DeckEditorDeckDockWidget::updateName(const QString &name) { + historyManager->save(deckLoader->getDeckList()->createMemento( + QString("Rename deck to \"%1\" from \"%2\"").arg(name).arg(deckLoader->getDeckList()->getName()))); deckModel->getDeckList()->setName(name); deckEditor->setModified(name.isEmpty()); emit nameChanged(); @@ -257,6 +283,7 @@ void DeckEditorDeckDockWidget::updateName(const QString &name) void DeckEditorDeckDockWidget::updateComments() { + historyManager->save(deckLoader->getDeckList()->createMemento("Comments changed")); deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); deckEditor->setModified(commentsEdit->toPlainText().isEmpty()); emit commentsChanged(); @@ -329,6 +356,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) { + historyManager->save(deckLoader->getDeckList()->createMemento("Banner card changed")); auto [name, id] = bannerCardComboBox->currentData().value>(); deckModel->getDeckList()->setBannerCard({name, id}); deckEditor->setModified(true); @@ -372,21 +400,39 @@ void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck) connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); connect(deckLoader->getDeckList(), &DeckList::deckHashChanged, deckModel, &DeckListModel::deckHashChanged); - nameEdit->setText(deckModel->getDeckList()->getName()); - commentsEdit->setText(deckModel->getDeckList()->getComments()); + historyManagerWidget->setDeckListModel(deckModel); - syncBannerCardComboBoxSelectionWithDeck(); - updateBannerCardComboBox(); - updateHash(); - deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - deckView->expandAll(); - deckView->expandAll(); - - deckTagsDisplayWidget->connectDeckList(); + syncDisplayWidgetsToModel(); emit deckChanged(); } +void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() +{ + nameEdit->blockSignals(true); + nameEdit->setText(deckModel->getDeckList()->getName()); + nameEdit->blockSignals(false); + + commentsEdit->blockSignals(true); + commentsEdit->setText(deckModel->getDeckList()->getComments()); + commentsEdit->blockSignals(false); + + bannerCardComboBox->blockSignals(true); + syncBannerCardComboBoxSelectionWithDeck(); + updateBannerCardComboBox(); + bannerCardComboBox->blockSignals(false); + updateHash(); + sortDeckModelToDeckView(); + expandAll(); + + deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); +} + +void DeckEditorDeckDockWidget::sortDeckModelToDeckView() +{ + deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); +} + DeckLoader *DeckEditorDeckDockWidget::getDeckLoader() { return deckLoader; @@ -412,7 +458,7 @@ void DeckEditorDeckDockWidget::cleanDeck() emit deckModified(); emit deckChanged(); updateBannerCardComboBox(); - deckTagsDisplayWidget->connectDeckList(); + deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); } void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) @@ -422,6 +468,12 @@ void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) deckView->expand(index); } +void DeckEditorDeckDockWidget::expandAll() +{ + deckView->expandAll(); + deckView->expandAll(); +} + /** * Gets the index of all the currently selected card nodes in the decklist table. * The list is in reverse order of the visual selection, so that rows can be deleted while iterating over them. @@ -545,6 +597,8 @@ void DeckEditorDeckDockWidget::actDecrementSelection() void DeckEditorDeckDockWidget::actRemoveCard() { + historyManager->save(deckLoader->getDeckList()->createMemento("Card removed")); + auto selectedRows = getSelectedCardNodes(); // hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted @@ -579,9 +633,16 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of QModelIndex sourceIndex = proxy->mapToSource(idx); const QModelIndex numberIndex = sourceIndex.sibling(sourceIndex.row(), 0); + const QModelIndex nameIndex = sourceIndex.sibling(sourceIndex.row(), 1); + + const QString cardName = deckModel->data(nameIndex, Qt::EditRole).toString(); const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); const int new_count = count + offset; + QString reason = QString("%1 %2 %3").arg(offset > 0 ? "Added" : "Removed").arg(qAbs(offset)).arg(cardName); + + historyManager->save(deckLoader->getDeckList()->createMemento(reason)); + if (new_count <= 0) { deckModel->removeRow(sourceIndex.row(), sourceIndex.parent()); } else { 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 d4703a5dc..7b0e6a1e8 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 @@ -12,7 +12,9 @@ #include "../../key_signals.h" #include "../utility/custom_line_edit.h" #include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h" +#include "deck_list_history_manager_widget.h" #include "deck_list_style_proxy.h" +#include "libcockatrice/deck_list/deck_list_history_manager.h" #include #include @@ -53,6 +55,8 @@ public slots: void cleanDeck(); void updateBannerCardComboBox(); void setDeck(DeckLoader *_deck); + void syncDisplayWidgetsToModel(); + void sortDeckModelToDeckView(); DeckLoader *getDeckLoader(); DeckList *getDeckList(); void actIncrement(); @@ -73,14 +77,18 @@ signals: private: AbstractTabDeckEditor *deckEditor; + DeckListHistoryManager *historyManager; + DeckListHistoryManagerWidget *historyManagerWidget; KeySignals deckViewKeySignals; QLabel *nameLabel; LineEditUnfocusable *nameEdit; + QTimer *nameDebounceTimer; SettingsButtonWidget *quickSettingsWidget; QCheckBox *showBannerCardCheckBox; QCheckBox *showTagsWidgetCheckBox; QLabel *commentsLabel; QTextEdit *commentsEdit; + QTimer *commentsDebounceTimer; QLabel *bannerCardLabel; DeckPreviewDeckTagsDisplayWidget *deckTagsDisplayWidget; QLabel *hashLabel1; @@ -104,6 +112,7 @@ private slots: void updateShowBannerCardComboBox(bool visible); void updateShowTagsWidget(bool visible); void syncBannerCardComboBoxSelectionWithDeck(); + void expandAll(); }; #endif // DECK_EDITOR_DECK_DOCK_WIDGET_H 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 new file mode 100644 index 000000000..fa47ba9b3 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp @@ -0,0 +1,148 @@ +#include "deck_list_history_manager_widget.h" + +DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckListModel, + DeckListStyleProxy *_styleProxy, + DeckListHistoryManager *manager, + QWidget *parent) + : QWidget(parent), deckListModel(_deckListModel), styleProxy(_styleProxy), historyManager(manager) +{ + auto *layout = new QHBoxLayout(this); + + aUndo = new QAction(QString(), this); + aUndo->setIcon(QPixmap("theme:icons/arrow_undo")); + aUndo->setShortcut(QKeySequence::Undo); + aUndo->setShortcutContext(Qt::ApplicationShortcut); + connect(aUndo, &QAction::triggered, this, &DeckListHistoryManagerWidget::doUndo); + + undoButton = new QToolButton(this); + undoButton->setDefaultAction(aUndo); + + aRedo = new QAction(QString(), this); + aRedo->setIcon(QPixmap("theme:icons/arrow_redo")); + aRedo->setShortcut(QKeySequence::Redo); + aRedo->setShortcutContext(Qt::ApplicationShortcut); + connect(aRedo, &QAction::triggered, this, &DeckListHistoryManagerWidget::doRedo); + + redoButton = new QToolButton(this); + redoButton->setDefaultAction(aRedo); + + layout->addWidget(undoButton); + layout->addWidget(redoButton); + + historyButton = new SettingsButtonWidget(this); + historyButton->setButtonIcon(QPixmap("theme:icons/arrow_history")); + + historyList = new QListWidget(this); + + historyButton->addSettingsWidget(historyList); + + layout->addWidget(historyButton); + + connect(undoButton, &QPushButton::clicked, this, &DeckListHistoryManagerWidget::doUndo); + connect(redoButton, &QPushButton::clicked, this, &DeckListHistoryManagerWidget::doRedo); + + connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked); + + connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, + &DeckListHistoryManagerWidget::refreshList); + + refreshList(); +} + +void DeckListHistoryManagerWidget::setDeckListModel(DeckListModel *_deckListModel) +{ + deckListModel = _deckListModel; +} + +void DeckListHistoryManagerWidget::refreshList() +{ + historyList->clear(); + + // 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 + auto item = new QListWidgetItem(tr("[redo] ") + redoStack[i]->getReason(), historyList); + item->setData(Qt::UserRole, QVariant("redo")); + item->setData(Qt::UserRole + 1, i); // index in redo stack + item->setForeground(Qt::gray); + historyList->addItem(item); + } + + // Divider + if (!historyManager->getUndoStack().isEmpty() && !historyManager->getRedoStack().isEmpty()) { + auto divider = new QListWidgetItem("──────────", historyList); + divider->setFlags(Qt::NoItemFlags); // not selectable + historyList->addItem(divider); + } + + // Fill undo section + const auto undoStack = historyManager->getUndoStack(); + for (int i = undoStack.size() - 1; i >= 0; --i) { + auto item = new QListWidgetItem(tr("[undo] ") + undoStack[i]->getReason(), historyList); + item->setData(Qt::UserRole, QVariant("undo")); + item->setData(Qt::UserRole + 1, i); // index in undo stack + historyList->addItem(item); + } + + // Button enabled states + undoButton->setEnabled(historyManager->canUndo()); + redoButton->setEnabled(historyManager->canRedo()); +} + +void DeckListHistoryManagerWidget::doUndo() +{ + if (!historyManager->canUndo()) + return; + + deckListModel->getDeckList()->restoreMemento(historyManager->undo(deckListModel->getDeckList())); + deckListModel->rebuildTree(); + emit deckListModel->layoutChanged(); + emit requestDisplayWidgetSync(); + + refreshList(); +} + +void DeckListHistoryManagerWidget::doRedo() +{ + if (!historyManager->canRedo()) + return; + + deckListModel->getDeckList()->restoreMemento(historyManager->redo(deckListModel->getDeckList())); + deckListModel->rebuildTree(); + + emit deckListModel->layoutChanged(); + emit requestDisplayWidgetSync(); + + refreshList(); +} + +void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item) +{ + // Ignore non-selectable items (like divider) + if (!(item->flags() & Qt::ItemIsSelectable)) + return; + + const QString mode = item->data(Qt::UserRole).toString(); + int index = item->data(Qt::UserRole + 1).toInt(); + + if (mode == "redo") { + const auto redoStack = historyManager->getRedoStack(); + int steps = redoStack.size() - index; + for (int i = 0; i < steps; ++i) { + deckListModel->getDeckList()->restoreMemento(historyManager->redo(deckListModel->getDeckList())); + } + } else if (mode == "undo") { + const auto undoStack = historyManager->getUndoStack(); + int steps = undoStack.size() - 1 - index; + for (int i = 0; i < steps + 1; ++i) { + deckListModel->getDeckList()->restoreMemento(historyManager->undo(deckListModel->getDeckList())); + } + } + + deckListModel->rebuildTree(); + + emit deckListModel->layoutChanged(); + emit requestDisplayWidgetSync(); + + refreshList(); +} \ 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 new file mode 100644 index 000000000..0028d167e --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h @@ -0,0 +1,53 @@ +#ifndef COCKATRICE_DECK_EDITOR_DECK_LIST_HISTORY_MANAGER_WIDGET_H +#define COCKATRICE_DECK_EDITOR_DECK_LIST_HISTORY_MANAGER_WIDGET_H + +#ifndef COCKATRICE_DECK_UNDO_WIDGET_H +#define COCKATRICE_DECK_UNDO_WIDGET_H + +#include "../quick_settings/settings_button_widget.h" +#include "deck_list_style_proxy.h" + +#include +#include +#include +#include +#include +#include +#include + +class DeckListHistoryManagerWidget : public QWidget +{ + Q_OBJECT + +signals: + void requestDisplayWidgetSync(); + +public: + explicit DeckListHistoryManagerWidget(DeckListModel *deckListModel, + DeckListStyleProxy *styleProxy, + DeckListHistoryManager *manager, + QWidget *parent = nullptr); + void setDeckListModel(DeckListModel *_deckListModel); + +private slots: + void refreshList(); + void onListClicked(QListWidgetItem *item); + void doUndo(); + void doRedo(); + +private: + DeckListModel *deckListModel; + DeckListStyleProxy *styleProxy; + DeckListHistoryManager *historyManager; + + QAction *aUndo; + QToolButton *undoButton; + QAction *aRedo; + QToolButton *redoButton; + SettingsButtonWidget *historyButton; + QListWidget *historyList; +}; + +#endif // COCKATRICE_DECK_UNDO_WIDGET_H + +#endif // COCKATRICE_DECK_EDITOR_DECK_LIST_HISTORY_MANAGER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp index b697f130f..9c2265dfd 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp @@ -7,9 +7,13 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const { + QModelIndex src = mapToSource(index); + if (!src.isValid()) + return {}; + QVariant value = QIdentityProxyModel::data(index, role); - const bool isCard = QIdentityProxyModel::data(index, DeckRoles::IsCardRole).toBool(); + bool isCard = src.data(DeckRoles::IsCardRole).toBool(); if (role == Qt::FontRole && !isCard) { QFont f; @@ -24,7 +28,7 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const int base = 255 - (index.row() % 2) * 30; return legal ? QBrush(QColor(base, base, base)) : QBrush(QColor(255, base / 3, base / 3)); } else { - int depth = QIdentityProxyModel::data(index, DeckRoles::DepthRole).toInt(); + int depth = src.data(DeckRoles::DepthRole).toInt(); int color = 90 + 60 * depth; return QBrush(QColor(color, 255, color)); } diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index 1e0511c01..b76c12ec3 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -3,8 +3,12 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(HEADERS - libcockatrice/deck_list/abstract_deck_list_card_node.h libcockatrice/deck_list/abstract_deck_list_node.h - libcockatrice/deck_list/deck_list.h libcockatrice/deck_list/deck_list_card_node.h + libcockatrice/deck_list/abstract_deck_list_card_node.h + libcockatrice/deck_list/abstract_deck_list_node.h + libcockatrice/deck_list/deck_list.h + libcockatrice/deck_list/deck_list_card_node.h + libcockatrice/deck_list/deck_list_history_manager.h + libcockatrice/deck_list/deck_list_memento.h libcockatrice/deck_list/inner_deck_list_node.h ) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index be26c51a3..f4f1eb086 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -2,6 +2,7 @@ #include "abstract_deck_list_node.h" #include "deck_list_card_node.h" +#include "deck_list_memento.h" #include "inner_deck_list_node.h" #include @@ -719,4 +720,15 @@ void DeckList::forEachCard(const std::functiongetMemento()); +} diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 824d62f45..b5e1e7cdb 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -10,6 +10,7 @@ #ifndef DECKLIST_H #define DECKLIST_H +#include "deck_list_memento.h" #include "inner_deck_list_node.h" #include @@ -317,6 +318,8 @@ public: * @param func Function taking (zone node, card node). */ void forEachCard(const std::function &func) const; + DeckListMemento *createMemento(QString reason) const; + void restoreMemento(const DeckListMemento *m); }; #endif diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h new file mode 100644 index 000000000..d8d0b473f --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h @@ -0,0 +1,96 @@ +#ifndef COCKATRICE_DECK_LIST_HISTORY_MANAGER_H +#define COCKATRICE_DECK_LIST_HISTORY_MANAGER_H + +#include "deck_list.h" +#include "deck_list_memento.h" + +#include +#include +#include + +class DeckListHistoryManager : public QObject +{ + Q_OBJECT + +signals: + void undoRedoStateChanged(); + +public: + explicit DeckListHistoryManager(QObject *parent = nullptr) : QObject(parent) + { + } + + void save(DeckListMemento *memento) + { + undoStack.push(memento); + redoStack.clear(); + emit undoRedoStateChanged(); + } + + bool canUndo() const + { + return !undoStack.isEmpty(); + } + bool canRedo() const + { + return !redoStack.isEmpty(); + } + + DeckListMemento *undo(DeckList *deck) + { + if (undoStack.isEmpty()) { + return nullptr; + } + + // Peek at the memento we are going to restore + DeckListMemento *mementoToRestore = undoStack.top(); + + // Save current state for redo using the same reason as the memento we're restoring + DeckListMemento *currentState = deck->createMemento(mementoToRestore->getReason()); + redoStack.push(currentState); + + // Pop the last state from undo stack and restore it + DeckListMemento *memento = undoStack.pop(); + deck->restoreMemento(memento); + + emit undoRedoStateChanged(); + return memento; + } + + DeckListMemento *redo(DeckList *deck) + { + if (redoStack.isEmpty()) { + return nullptr; + } + + // Peek at the memento we are going to restore + DeckListMemento *mementoToRestore = redoStack.top(); + + // Save current state for undo using the same reason as the memento we're restoring + DeckListMemento *currentState = deck->createMemento(mementoToRestore->getReason()); + undoStack.push(currentState); + + // Pop the next state from redo stack and restore it + DeckListMemento *memento = redoStack.pop(); + deck->restoreMemento(memento); + + emit undoRedoStateChanged(); + return memento; + } + + QStack getRedoStack() const + { + return redoStack; + } + + QStack getUndoStack() const + { + return undoStack; + } + +private: + QStack undoStack; + QStack redoStack; +}; + +#endif // COCKATRICE_DECK_LIST_HISTORY_MANAGER_H diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h new file mode 100644 index 000000000..e8daa2d11 --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h @@ -0,0 +1,27 @@ +#ifndef COCKATRICE_DECK_LIST_MEMENTO_H +#define COCKATRICE_DECK_LIST_MEMENTO_H +#include + +class DeckListMemento +{ +public: + explicit DeckListMemento(QString memento, QString reason = QString()) : memento(memento), reason(reason) + { + } + + QString getMemento() const + { + return memento; + }; + + QString getReason() const + { + return reason; + } + +private: + QString memento; + QString reason; +}; + +#endif // COCKATRICE_DECK_LIST_MEMENTO_H