diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index d77d70d36..522300346 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -53,7 +53,6 @@ set(cockatrice_SOURCES
src/game/board/abstract_card_drag_item.cpp
src/game/board/abstract_card_item.cpp
src/game/board/abstract_counter.cpp
- src/game/board/abstract_graphics_item.cpp
src/game/board/arrow_item.cpp
src/game/board/arrow_target.cpp
src/game/board/card_drag_item.cpp
@@ -113,6 +112,7 @@ set(cockatrice_SOURCES
src/game/zones/table_zone.cpp
src/game/zones/view_zone.cpp
src/game/zones/view_zone_widget.cpp
+ src/game_graphics/board/abstract_graphics_item.cpp
src/interface/card_picture_loader/card_picture_loader.cpp
src/interface/card_picture_loader/card_picture_loader_local.cpp
src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.cpp
@@ -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/resources/icons/arrow_history.svg b/cockatrice/resources/icons/arrow_history.svg
new file mode 100644
index 000000000..d130758e6
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_history.svg
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/arrow_redo.svg b/cockatrice/resources/icons/arrow_redo.svg
new file mode 100644
index 000000000..dd89e3314
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_redo.svg
@@ -0,0 +1,40 @@
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/resources/icons/arrow_undo.svg b/cockatrice/resources/icons/arrow_undo.svg
new file mode 100644
index 000000000..2e182ffb2
--- /dev/null
+++ b/cockatrice/resources/icons/arrow_undo.svg
@@ -0,0 +1,40 @@
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/src/game/board/abstract_card_item.h b/cockatrice/src/game/board/abstract_card_item.h
index a935c97b6..dad73dc14 100644
--- a/cockatrice/src/game/board/abstract_card_item.h
+++ b/cockatrice/src/game/board/abstract_card_item.h
@@ -7,8 +7,8 @@
#ifndef ABSTRACTCARDITEM_H
#define ABSTRACTCARDITEM_H
+#include "../../game_graphics/board/graphics_item_type.h"
#include "arrow_target.h"
-#include "graphics_item_type.h"
#include
#include
diff --git a/cockatrice/src/game/board/arrow_target.h b/cockatrice/src/game/board/arrow_target.h
index 8cc5d55dc..55f4ef678 100644
--- a/cockatrice/src/game/board/arrow_target.h
+++ b/cockatrice/src/game/board/arrow_target.h
@@ -7,7 +7,7 @@
#ifndef ARROWTARGET_H
#define ARROWTARGET_H
-#include "abstract_graphics_item.h"
+#include "../../game_graphics/board/abstract_graphics_item.h"
#include
diff --git a/cockatrice/src/game/board/counter_general.cpp b/cockatrice/src/game/board/counter_general.cpp
index c71077324..d68486a1b 100644
--- a/cockatrice/src/game/board/counter_general.cpp
+++ b/cockatrice/src/game/board/counter_general.cpp
@@ -1,7 +1,7 @@
#include "counter_general.h"
+#include "../../game_graphics/board/abstract_graphics_item.h"
#include "../../interface/pixel_map_generator.h"
-#include "abstract_graphics_item.h"
#include
diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp
index 48c906100..64e9abc46 100644
--- a/cockatrice/src/game/deckview/deck_view.cpp
+++ b/cockatrice/src/game/deckview/deck_view.cpp
@@ -330,7 +330,7 @@ void DeckViewScene::setDeck(const DeckList &_deck)
if (deck)
delete deck;
- deck = new DeckList(_deck);
+ deck = new DeckList(_deck.writeToString_Native());
rebuildTree();
applySideboardPlan(deck->getCurrentSideboardPlan());
rearrangeItems();
diff --git a/cockatrice/src/game/hand_counter.h b/cockatrice/src/game/hand_counter.h
index cd2b0fe81..2c0175ecc 100644
--- a/cockatrice/src/game/hand_counter.h
+++ b/cockatrice/src/game/hand_counter.h
@@ -7,8 +7,8 @@
#ifndef HANDCOUNTER_H
#define HANDCOUNTER_H
-#include "board/abstract_graphics_item.h"
-#include "board/graphics_item_type.h"
+#include "../game_graphics/board/abstract_graphics_item.h"
+#include "../game_graphics/board/graphics_item_type.h"
#include
diff --git a/cockatrice/src/game/phases_toolbar.h b/cockatrice/src/game/phases_toolbar.h
index 57ab6de66..215a97dd1 100644
--- a/cockatrice/src/game/phases_toolbar.h
+++ b/cockatrice/src/game/phases_toolbar.h
@@ -8,7 +8,7 @@
#ifndef PHASESTOOLBAR_H
#define PHASESTOOLBAR_H
-#include "board/abstract_graphics_item.h"
+#include "../game_graphics/board/abstract_graphics_item.h"
#include
#include
diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp
index 300300de3..ffb26c26e 100644
--- a/cockatrice/src/game/player/player.cpp
+++ b/cockatrice/src/game/player/player.cpp
@@ -263,9 +263,10 @@ void Player::deleteCard(CardItem *card)
}
}
-void Player::setDeck(const DeckLoader &_deck)
+// TODO: Does a player need a DeckLoader?
+void Player::setDeck(DeckLoader &_deck)
{
- deck = new DeckLoader(_deck);
+ deck = new DeckLoader(this, _deck.getDeckList());
emit deckChanged();
}
diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h
index 92125530a..0751abb14 100644
--- a/cockatrice/src/game/player/player.h
+++ b/cockatrice/src/game/player/player.h
@@ -7,8 +7,8 @@
#ifndef PLAYER_H
#define PLAYER_H
+#include "../../game_graphics/board/abstract_graphics_item.h"
#include "../../interface/widgets/menus/tearoff_menu.h"
-#include "../board/abstract_graphics_item.h"
#include "../dialogs/dlg_create_token.h"
#include "menu/player_menu.h"
#include "player_actions.h"
@@ -130,7 +130,7 @@ public:
return playerMenu;
}
- void setDeck(const DeckLoader &_deck);
+ void setDeck(DeckLoader &_deck);
[[nodiscard]] DeckLoader *getDeck() const
{
diff --git a/cockatrice/src/game/player/player_area.h b/cockatrice/src/game/player/player_area.h
index ec065fb4b..ec0d23cb6 100644
--- a/cockatrice/src/game/player/player_area.h
+++ b/cockatrice/src/game/player/player_area.h
@@ -7,7 +7,7 @@
#ifndef COCKATRICE_PLAYER_AREA_H
#define COCKATRICE_PLAYER_AREA_H
-#include "../board/graphics_item_type.h"
+#include "../../game_graphics/board/graphics_item_type.h"
#include "QGraphicsItem"
/**
diff --git a/cockatrice/src/game/player/player_target.h b/cockatrice/src/game/player/player_target.h
index bf26838c3..aaa0f0610 100644
--- a/cockatrice/src/game/player/player_target.h
+++ b/cockatrice/src/game/player/player_target.h
@@ -7,9 +7,9 @@
#ifndef PLAYERTARGET_H
#define PLAYERTARGET_H
+#include "../../game_graphics/board/graphics_item_type.h"
#include "../board/abstract_counter.h"
#include "../board/arrow_target.h"
-#include "../board/graphics_item_type.h"
#include
#include
diff --git a/cockatrice/src/game/zones/card_zone.h b/cockatrice/src/game/zones/card_zone.h
index f489915e2..6fe8157e4 100644
--- a/cockatrice/src/game/zones/card_zone.h
+++ b/cockatrice/src/game/zones/card_zone.h
@@ -7,8 +7,8 @@
#ifndef CARDZONE_H
#define CARDZONE_H
-#include "../board/abstract_graphics_item.h"
-#include "../board/graphics_item_type.h"
+#include "../../game_graphics/board/abstract_graphics_item.h"
+#include "../../game_graphics/board/graphics_item_type.h"
#include "logic/card_zone_logic.h"
#include
diff --git a/cockatrice/src/game/board/abstract_graphics_item.cpp b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp
similarity index 100%
rename from cockatrice/src/game/board/abstract_graphics_item.cpp
rename to cockatrice/src/game_graphics/board/abstract_graphics_item.cpp
diff --git a/cockatrice/src/game/board/abstract_graphics_item.h b/cockatrice/src/game_graphics/board/abstract_graphics_item.h
similarity index 100%
rename from cockatrice/src/game/board/abstract_graphics_item.h
rename to cockatrice/src/game_graphics/board/abstract_graphics_item.h
diff --git a/cockatrice/src/game/board/graphics_item_type.h b/cockatrice/src/game_graphics/board/graphics_item_type.h
similarity index 100%
rename from cockatrice/src/game/board/graphics_item_type.h
rename to cockatrice/src/game_graphics/board/graphics_item_type.h
diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp
index ebc951c6e..320302142 100644
--- a/cockatrice/src/interface/deck_loader/deck_loader.cpp
+++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp
@@ -33,19 +33,6 @@ DeckLoader::DeckLoader(QObject *parent)
DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList)
: QObject(parent), deckList(_deckList), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
{
- deckList->setParent(this);
-}
-
-DeckLoader::DeckLoader(const DeckLoader &other)
- : QObject(), deckList(other.deckList), lastFileName(other.lastFileName), lastFileFormat(other.lastFileFormat),
- lastRemoteDeckId(other.lastRemoteDeckId)
-{
-}
-
-void DeckLoader::setDeckList(DeckList *_deckList)
-{
- deckList = _deckList;
- deckList->setParent(this);
}
bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest)
@@ -165,12 +152,14 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
break;
case CockatriceFormat:
result = deckList->saveToFile_Native(&file);
+ qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result;
break;
}
if (result) {
lastFileName = fileName;
lastFileFormat = fmt;
+ qCInfo(DeckLoaderLog) << "Deck was saved -" << result;
}
file.flush();
diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h
index a83542ce9..57305b7de 100644
--- a/cockatrice/src/interface/deck_loader/deck_loader.h
+++ b/cockatrice/src/interface/deck_loader/deck_loader.h
@@ -53,8 +53,9 @@ private:
public:
DeckLoader(QObject *parent);
DeckLoader(QObject *parent, DeckList *_deckList);
- void setDeckList(DeckList *_deckList);
- DeckLoader(const DeckLoader &other);
+ DeckLoader(const DeckLoader &) = delete;
+ DeckLoader &operator=(const DeckLoader &) = delete;
+
const QString &getLastFileName() const
{
return lastFileName;
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 7a4820dd5..56bd16f0a 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
@@ -13,6 +13,30 @@
#include
#include
+static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo)
+{
+ // Try providerId + name (strongest match)
+ if (!wanted.providerId.isEmpty()) {
+ for (int i = 0; i < combo->count(); ++i) {
+ auto pair = combo->itemData(i).value>();
+ if (pair.second == wanted.providerId && pair.first == wanted.name) {
+ return i;
+ }
+ }
+ }
+
+ // Try name only
+ for (int i = 0; i < combo->count(); ++i) {
+ auto pair = combo->itemData(i).value>();
+ if (pair.first == wanted.name) {
+ return i;
+ }
+ }
+
+ // Not found
+ return -1;
+}
+
DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent)
: QDockWidget(parent), deckEditor(parent)
{
@@ -28,6 +52,11 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent
void DeckEditorDeckDockWidget::createDeckDock()
{
+ historyManager = new DeckListHistoryManager();
+
+ connect(deckEditor, &AbstractTabDeckEditor::cardAboutToBeAdded, this,
+ &DeckEditorDeckDockWidget::onCardAboutToBeAdded);
+
deckModel = new DeckListModel(this);
deckModel->setObjectName("deckModel");
connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
@@ -37,6 +66,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 +98,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 +136,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 +237,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 +300,8 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const
void DeckEditorDeckDockWidget::updateName(const QString &name)
{
+ historyManager->save(deckLoader->getDeckList()->createMemento(
+ QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName())));
deckModel->getDeckList()->setName(name);
deckEditor->setModified(name.isEmpty());
emit nameChanged();
@@ -257,6 +310,11 @@ void DeckEditorDeckDockWidget::updateName(const QString &name)
void DeckEditorDeckDockWidget::updateComments()
{
+ historyManager->save(
+ deckLoader->getDeckList()->createMemento(QString(tr("Updated comments (was %1 chars, now %2 chars)"))
+ .arg(deckLoader->getDeckList()->getComments().size())
+ .arg(commentsEdit->toPlainText().size())));
+
deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
deckEditor->setModified(commentsEdit->toPlainText().isEmpty());
emit commentsChanged();
@@ -272,8 +330,8 @@ void DeckEditorDeckDockWidget::updateHash()
void DeckEditorDeckDockWidget::updateBannerCardComboBox()
{
- // Store the current text of the combo box
- QString currentText = bannerCardComboBox->currentText();
+ // Store current banner card identity
+ CardRef wanted = deckModel->getDeckList()->getBannerCard();
// Block signals temporarily
bool wasBlocked = bannerCardComboBox->blockSignals(true);
@@ -281,54 +339,51 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
// Clear the existing items in the combo box
bannerCardComboBox->clear();
- // Prepare the new items with deduplication
+ // Collect unique (name, providerId) pairs
QSet> bannerCardSet;
QList cardsInDeck = deckModel->getDeckList()->getCardNodes();
for (auto currentCard : cardsInDeck) {
- for (int k = 0; k < currentCard->getNumber(); ++k) {
- if (CardDatabaseManager::query()->getCard(currentCard->toCardRef())) {
- bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()});
- }
+ if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) {
+ continue;
}
+
+ // Insert one entry per distinct card, ignore copies
+ bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()});
}
+ // Convert to sorted list
QList> pairList = bannerCardSet.values();
// Sort QList by the first() element of the QPair
- std::sort(pairList.begin(), pairList.end(), [](const QPair &a, const QPair &b) {
- return a.first.toLower() < b.first.toLower();
- });
+ std::sort(pairList.begin(), pairList.end(),
+ [](const auto &a, const auto &b) { return a.first.toLower() < b.first.toLower(); });
+ // Add to combo box
for (const auto &pair : pairList) {
bannerCardComboBox->addItem(pair.first, QVariant::fromValue(pair));
}
- // Try to restore the previous selection by finding the currentText
- int restoredIndex = bannerCardComboBox->findText(currentText);
- if (restoredIndex != -1) {
- bannerCardComboBox->setCurrentIndex(restoredIndex);
- if (deckModel->getDeckList()->getBannerCard().providerId !=
- bannerCardComboBox->currentData().value>().second) {
- setBannerCard(restoredIndex);
- }
+ // Try to find an index with a matching card
+ int restoreIndex = findRestoreIndex(wanted, bannerCardComboBox);
+
+ // Handle results
+ if (restoreIndex != -1) {
+ bannerCardComboBox->setCurrentIndex(restoreIndex);
+ setBannerCard(restoreIndex);
} else {
// Add a placeholder "-" and set it as the current selection
- int bannerIndex = bannerCardComboBox->findText(deckModel->getDeckList()->getBannerCard().name);
- if (bannerIndex != -1) {
- bannerCardComboBox->setCurrentIndex(bannerIndex);
- } else {
- bannerCardComboBox->insertItem(0, "-");
- bannerCardComboBox->setCurrentIndex(0);
- }
+ bannerCardComboBox->insertItem(0, "-");
+ bannerCardComboBox->setCurrentIndex(0);
}
- // Restore the previous signal blocking state
+ // Restore signal state
bannerCardComboBox->blockSignals(wasBlocked);
}
void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */)
{
+ historyManager->save(deckLoader->getDeckList()->createMemento(tr("Banner card changed")));
auto [name, id] = bannerCardComboBox->currentData().value>();
deckModel->getDeckList()->setBannerCard({name, id});
deckEditor->setModified(true);
@@ -372,21 +427,40 @@ 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());
+ historyManager->clear();
+ historyManagerWidget->setDeckListModel(deckModel);
- syncBannerCardComboBoxSelectionWithDeck();
- updateBannerCardComboBox();
- updateHash();
- deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
- deckView->expandAll();
- deckView->expandAll();
-
- deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
+ 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;
@@ -422,6 +496,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.
@@ -441,6 +521,14 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const
return selectedRows;
}
+void DeckEditorDeckDockWidget::onCardAboutToBeAdded(const ExactCard &addedCard, const QString &zoneName)
+{
+ historyManager->save(deckLoader->getDeckList()->createMemento(
+ QString(tr("Added (%1): %2 (%3) %4"))
+ .arg(zoneName, addedCard.getName(), addedCard.getPrinting().getSet()->getCorrectedShortName(),
+ addedCard.getPrinting().getProperty("num"))));
+}
+
void DeckEditorDeckDockWidget::actIncrement()
{
auto selectedRows = getSelectedCardNodes();
@@ -559,6 +647,11 @@ void DeckEditorDeckDockWidget::actRemoveCard()
continue;
}
QModelIndex sourceIndex = proxy->mapToSource(index);
+ QString cardName = sourceIndex.sibling(sourceIndex.row(), 1).data().toString();
+
+ historyManager->save(
+ deckLoader->getDeckList()->createMemento(QString(tr("Removed \"%1\" (all copies)")).arg(cardName)));
+
deckModel->removeRow(sourceIndex.row(), sourceIndex.parent());
isModified = true;
}
@@ -579,9 +672,21 @@ 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;
+ const auto reason =
+ QString(tr("%1 %2 × \"%3\" (%4)"))
+ .arg(offset > 0 ? tr("Added") : tr("Removed"))
+ .arg(qAbs(offset))
+ .arg(cardName)
+ .arg(deckModel->data(sourceIndex.sibling(sourceIndex.row(), 4), Qt::DisplayRole).toString());
+
+ 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..b633bdaed 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();
@@ -61,6 +65,7 @@ public slots:
void actDecrementSelection();
void actSwapCard();
void actRemoveCard();
+ void onCardAboutToBeAdded(const ExactCard &card, const QString &zoneName);
void offsetCountAtIndex(const QModelIndex &idx, int offset);
signals:
@@ -73,14 +78,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 +113,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..c68934ea6
--- /dev/null
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
@@ -0,0 +1,160 @@
+#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)
+{
+ 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"));
+
+ historyLabel = new QLabel(this);
+
+ historyList = new QListWidget(this);
+
+ historyButton->addSettingsWidget(historyLabel);
+ historyButton->addSettingsWidget(historyList);
+
+ layout->addWidget(historyButton);
+
+ connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked);
+
+ connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this,
+ &DeckListHistoryManagerWidget::refreshList);
+
+ refreshList();
+ retranslateUi();
+}
+
+void DeckListHistoryManagerWidget::retranslateUi()
+{
+ undoButton->setToolTip(tr("Undo"));
+ redoButton->setToolTip(tr("Redo"));
+ historyButton->setToolTip(tr("Undo/Redo history"));
+ 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();
+
+ // 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;
+ }
+
+ 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)
+{
+ // 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) {
+ 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) {
+ 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..23619b91b
--- /dev/null
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h
@@ -0,0 +1,58 @@
+#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 slots:
+ void retranslateUi();
+
+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;
+
+ QHBoxLayout *layout;
+ QAction *aUndo;
+ QToolButton *undoButton;
+ QAction *aRedo;
+ QToolButton *redoButton;
+ SettingsButtonWidget *historyButton;
+ QLabel *historyLabel;
+ 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/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
index 3385dd41e..2a276479f 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
@@ -133,16 +133,16 @@ void DlgLoadDeckFromClipboard::actOK()
/**
* Creates the dialog window for the "Edit deck in clipboard" action
*
- * @param deckList The existing deck in the deck editor. Copies the instance
+ * @param _deckLoader The existing deck in the deck editor. Copies the instance
* @param _annotated Whether to add annotations to the text that is loaded from the deck
* @param parent The parent widget
*/
-DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckLoader &deckList, bool _annotated, QWidget *parent)
+DlgEditDeckInClipboard::DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent)
: AbstractDlgDeckTextEdit(parent), annotated(_annotated)
{
setWindowTitle(tr("Edit deck in clipboard"));
- deckLoader = new DeckLoader(deckList);
+ deckLoader = new DeckLoader(this, _deckLoader->getDeckList());
deckLoader->setParent(this);
DlgEditDeckInClipboard::actRefresh();
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h
index d93a020cc..982840228 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h
@@ -88,7 +88,7 @@ private:
bool annotated;
public:
- explicit DlgEditDeckInClipboard(const DeckLoader &deckList, bool _annotated, QWidget *parent = nullptr);
+ explicit DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent = nullptr);
[[nodiscard]] DeckLoader *getDeckList() const override
{
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 65e1e4e71..ad68ecdbc 100644
--- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
+++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
@@ -131,6 +131,8 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneNam
if (card.getInfo().getIsToken())
zoneName = DECK_ZONE_TOKENS;
+ emit cardAboutToBeAdded(card, zoneName);
+
QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName);
deckDockWidget->deckView->clearSelection();
deckDockWidget->deckView->setCurrentIndex(newCardIndex);
@@ -486,7 +488,7 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard()
*/
void AbstractTabDeckEditor::editDeckInClipboard(bool annotated)
{
- DlgEditDeckInClipboard dlg(*getDeckLoader(), annotated, this);
+ DlgEditDeckInClipboard dlg(getDeckLoader(), annotated, this);
if (!dlg.exec())
return;
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 c0a5518c6..86517de63 100644
--- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h
+++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h
@@ -180,6 +180,7 @@ public slots:
virtual void dockTopLevelChanged(bool topLevel) = 0;
signals:
+ void cardAboutToBeAdded(const ExactCard &addedCard, const QString &zoneName);
/** @brief Emitted when a deck should be opened in a new editor tab. */
void openDeckEditor(DeckLoader *deckLoader);
diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp
index bf74b73fb..b483f0616 100644
--- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp
+++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp
@@ -242,11 +242,11 @@ void TabDeckStorage::actOpenLocalDeck()
continue;
QString filePath = localDirModel->filePath(curLeft);
- DeckLoader deckLoader(this);
- if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat, true))
+ auto deckLoader = new DeckLoader(this);
+ if (!deckLoader->loadFromFile(filePath, DeckLoader::CockatriceFormat, true))
continue;
- emit openDeckEditor(&deckLoader);
+ emit openDeckEditor(deckLoader);
}
}
diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp
index 6d550be01..902b1f589 100644
--- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp
+++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp
@@ -869,7 +869,7 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(DeckLoader *deckToOpen)
{
auto *tab = new TabDeckEditor(this);
if (deckToOpen)
- tab->openDeck(new DeckLoader(this, deckToOpen->getDeckList()));
+ tab->openDeck(deckToOpen);
connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed);
connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addDeckEditorTab);
myAddTab(tab);
@@ -882,7 +882,7 @@ TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(DeckLoader *deckToOpe
{
auto *tab = new TabDeckEditorVisual(this);
if (deckToOpen)
- tab->openDeck(new DeckLoader(this, deckToOpen->getDeckList()));
+ tab->openDeck(deckToOpen);
connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed);
connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addVisualDeckEditorTab);
myAddTab(tab);
diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
index b4749f408..bfb498909 100644
--- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
+++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
@@ -27,11 +27,11 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor)
void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath)
{
- DeckLoader deckLoader(this);
- if (!deckLoader.loadFromFile(filePath, DeckLoader::getFormatFromName(filePath), true)) {
+ auto deckLoader = new DeckLoader(this);
+ if (!deckLoader->loadFromFile(filePath, DeckLoader::getFormatFromName(filePath), true)) {
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath));
return;
}
- emit openDeckEditor(&deckLoader);
+ emit openDeckEditor(deckLoader);
}
diff --git a/doc/carddatabase_v4/cards.xsd b/doc/carddatabase_v4/cards.xsd
index 9a879302b..92d30b94f 100644
--- a/doc/carddatabase_v4/cards.xsd
+++ b/doc/carddatabase_v4/cards.xsd
@@ -30,6 +30,7 @@
+
diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp
index 58aa83848..3054a10cf 100644
--- a/libcockatrice_card/libcockatrice/card/card_info.cpp
+++ b/libcockatrice_card/libcockatrice/card/card_info.cpp
@@ -32,7 +32,7 @@ CardInfo::CardInfo(const QString &_name,
{
simpleName = CardInfo::simplifyName(name);
- refreshCachedSetNames();
+ refreshCachedSets();
}
CardInfoPtr CardInfo::newInstance(const QString &_name)
@@ -84,7 +84,7 @@ void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo _info)
setsToPrintings[_set->getShortName()].append(_info);
}
- refreshCachedSetNames();
+ refreshCachedSets();
}
void CardInfo::combineLegalities(const QVariantHash &props)
@@ -98,6 +98,12 @@ void CardInfo::combineLegalities(const QVariantHash &props)
}
}
+void CardInfo::refreshCachedSets()
+{
+ refreshCachedSetNames();
+ refreshCachedAltNames();
+}
+
void CardInfo::refreshCachedSetNames()
{
QStringList setList;
@@ -113,6 +119,21 @@ void CardInfo::refreshCachedSetNames()
setsNames = setList.join(", ");
}
+void CardInfo::refreshCachedAltNames()
+{
+ altNames.clear();
+
+ // update the altNames with the flavorNames
+ for (const auto &printings : setsToPrintings) {
+ for (const auto &printing : printings) {
+ QString flavorName = printing.getFlavorName();
+ if (!flavorName.isEmpty()) {
+ altNames.insert(flavorName);
+ }
+ }
+ }
+}
+
QString CardInfo::simplifyName(const QString &name)
{
static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)");
diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h
index a52c0553a..ab1d51a49 100644
--- a/libcockatrice_card/libcockatrice/card/card_info.h
+++ b/libcockatrice_card/libcockatrice/card/card_info.h
@@ -77,8 +77,9 @@ private:
QList reverseRelatedCards; ///< Cards that refer back to this card.
QList reverseRelatedCardsToMe; ///< Cards that consider this card as related.
SetToPrintingsMap setsToPrintings; ///< Mapping from set names to printing variations.
- QString setsNames; ///< Cached, human-readable list of set names.
UiAttributes uiAttributes; ///< Attributes that affect display and game logic
+ QString setsNames; ///< Cached, human-readable list of set names.
+ QSet altNames; ///< Cached set of alternate names, used when searching
///@}
public:
@@ -114,7 +115,8 @@ public:
: QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text),
isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
- setsToPrintings(other.setsToPrintings), setsNames(other.setsNames), uiAttributes(other.uiAttributes)
+ setsToPrintings(other.setsToPrintings), uiAttributes(other.uiAttributes), setsNames(other.setsNames),
+ altNames(other.altNames)
{
}
@@ -185,6 +187,10 @@ public:
{
return simpleName;
}
+ const QSet &getAltNames()
+ {
+ return altNames;
+ };
const QString &getText() const
{
return text;
@@ -303,11 +309,11 @@ public:
void combineLegalities(const QVariantHash &props);
/**
- * @brief Refreshes the cached, human-readable list of set names.
+ * @brief Refreshes all cached fields that are calculated from the contained sets and printings.
*
- * Typically called after adding or modifying set memberships.
+ * Typically called after adding or modifying set memberships or printings.
*/
- void refreshCachedSetNames();
+ void refreshCachedSets();
/**
* @brief Simplifies a name for fuzzy matching.
@@ -319,6 +325,21 @@ public:
*/
static QString simplifyName(const QString &name);
+private:
+ /**
+ * @brief Refreshes the cached, human-readable list of set names.
+ *
+ * Typically called after adding or modifying set memberships.
+ */
+ void refreshCachedSetNames();
+
+ /**
+ * @brief Refreshes the cached list of alt names for the card.
+ *
+ * Typically called after adding or modifying the contained printings.
+ */
+ void refreshCachedAltNames();
+
signals:
/**
* @brief Emitted when a pixmap for this card has been updated or finished loading.
diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.cpp b/libcockatrice_card/libcockatrice/card/database/card_database.cpp
index 7e23b4663..57b3ce06e 100644
--- a/libcockatrice_card/libcockatrice/card/database/card_database.cpp
+++ b/libcockatrice_card/libcockatrice/card/database/card_database.cpp
@@ -194,8 +194,9 @@ void CardDatabase::markAllSetsAsKnown()
void CardDatabase::notifyEnabledSetsChanged()
{
// refresh the list of cached set names
- for (const CardInfoPtr &card : cards)
- card->refreshCachedSetNames();
+ for (const CardInfoPtr &card : cards) {
+ card->refreshCachedSets();
+ }
// inform the carddatabasemodels that they need to re-check their list of cards
emit cardDatabaseEnabledSetsChanged();
diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp b/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp
index 9de563d78..340998565 100644
--- a/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp
+++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp
@@ -12,4 +12,9 @@ PrintingInfo::PrintingInfo(const CardSetPtr &_set) : set(_set)
QString PrintingInfo::getUuid() const
{
return properties.value("uuid").toString();
+}
+
+QString PrintingInfo::getFlavorName() const
+{
+ return properties.value("flavorName").toString();
}
\ No newline at end of file
diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h
index 37d7a15e2..4329ca2f4 100644
--- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h
+++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h
@@ -110,6 +110,13 @@ public:
* @return A string representing the providerID.
*/
QString getUuid() const;
+
+ /**
+ * @brief Returns the flavorName for this printing.
+ *
+ * @return The flavorName, or empty if it isn't present.
+ */
+ QString getFlavorName() const;
};
#endif // COCKATRICE_PRINTING_INFO_H
diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt
index 1e0511c01..d9b27354b 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
)
@@ -16,9 +20,13 @@ endif()
add_library(
libcockatrice_deck_list STATIC
- ${MOC_SOURCES} libcockatrice/deck_list/abstract_deck_list_card_node.cpp
- libcockatrice/deck_list/abstract_deck_list_node.cpp libcockatrice/deck_list/deck_list.cpp
- libcockatrice/deck_list/deck_list_card_node.cpp libcockatrice/deck_list/inner_deck_list_node.cpp
+ ${MOC_SOURCES}
+ libcockatrice/deck_list/abstract_deck_list_card_node.cpp
+ libcockatrice/deck_list/abstract_deck_list_node.cpp
+ libcockatrice/deck_list/deck_list.cpp
+ libcockatrice/deck_list/deck_list_card_node.cpp
+ libcockatrice/deck_list/deck_list_history_manager.cpp
+ libcockatrice/deck_list/inner_deck_list_node.cpp
)
add_dependencies(libcockatrice_deck_list libcockatrice_protocol)
diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp
index a12c6fe0e..e26e55e08 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
@@ -80,20 +81,6 @@ DeckList::DeckList()
root = new InnerDecklistNode;
}
-// TODO: https://qt-project.org/doc/qt-4.8/qobject.html#no-copy-constructor-or-assignment-operator
-DeckList::DeckList(const DeckList &other)
- : QObject(), name(other.name), comments(other.comments), bannerCard(other.bannerCard),
- lastLoadedTimestamp(other.lastLoadedTimestamp), tags(other.tags), cachedDeckHash(other.cachedDeckHash)
-{
- root = new InnerDecklistNode(other.getRoot());
-
- QMapIterator spIterator(other.getSideboardPlans());
- while (spIterator.hasNext()) {
- spIterator.next();
- sideboardPlans.insert(spIterator.key(), new SideboardPlan(spIterator.key(), spIterator.value()->getMoveList()));
- }
-}
-
DeckList::DeckList(const QString &nativeString)
{
root = new InnerDecklistNode;
@@ -733,4 +720,15 @@ void DeckList::forEachCard(const std::function
@@ -215,8 +216,9 @@ public slots:
public:
/// @brief Construct an empty deck.
explicit DeckList();
- /// @brief Deep-copy constructor.
- DeckList(const DeckList &other);
+ /// @brief Delete copy constructor.
+ DeckList(const DeckList &) = delete;
+ DeckList &operator=(const DeckList &) = delete;
/// @brief Construct from a serialized native-format string.
explicit DeckList(const QString &nativeString);
~DeckList() override;
@@ -316,6 +318,8 @@ public:
* @param func Function taking (zone node, card node).
*/
void forEachCard(const std::function &func) const;
+ DeckListMemento createMemento(const QString &reason) const;
+ void restoreMemento(const DeckListMemento &m);
};
#endif
diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp
new file mode 100644
index 000000000..83c9cc0bb
--- /dev/null
+++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp
@@ -0,0 +1,53 @@
+#include "deck_list_history_manager.h"
+
+void DeckListHistoryManager::save(const DeckListMemento &memento)
+{
+ undoStack.push(memento);
+ redoStack.clear();
+ emit undoRedoStateChanged();
+}
+
+void DeckListHistoryManager::clear()
+{
+ undoStack.clear();
+ redoStack.clear();
+ emit undoRedoStateChanged();
+}
+
+void DeckListHistoryManager::undo(DeckList *deck)
+{
+ if (undoStack.isEmpty())
+ return;
+
+ // Peek at the memento we are going to restore
+ const DeckListMemento &mementoToRestore = undoStack.top();
+
+ // Save current state for redo
+ 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();
+}
+
+void DeckListHistoryManager::redo(DeckList *deck)
+{
+ if (redoStack.isEmpty())
+ return;
+
+ // Peek at the memento we are going to restore
+ const DeckListMemento &mementoToRestore = redoStack.top();
+
+ // Save current state for undo
+ 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();
+}
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..f30c0affe
--- /dev/null
+++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h
@@ -0,0 +1,54 @@
+#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
+
+class DeckListHistoryManager : public QObject
+{
+ Q_OBJECT
+
+signals:
+ void undoRedoStateChanged();
+
+public:
+ explicit DeckListHistoryManager(QObject *parent = nullptr) : QObject(parent)
+ {
+ }
+
+ void save(const DeckListMemento &memento);
+
+ void clear();
+
+ bool canUndo() const
+ {
+ return !undoStack.isEmpty();
+ }
+
+ bool canRedo() const
+ {
+ return !redoStack.isEmpty();
+ }
+
+ void undo(DeckList *deck);
+
+ void redo(DeckList *deck);
+
+ 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..d2e36b804
--- /dev/null
+++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h
@@ -0,0 +1,28 @@
+#ifndef COCKATRICE_DECK_LIST_MEMENTO_H
+#define COCKATRICE_DECK_LIST_MEMENTO_H
+#include
+
+class DeckListMemento
+{
+public:
+ DeckListMemento() = default;
+ explicit DeckListMemento(const QString &memento, const 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
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 52e3b678c..ed77b55ca 100644
--- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp
+++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp
@@ -533,14 +533,14 @@ void DeckListModel::cleanList()
}
/**
- * @param _deck The deck. Takes ownership of the object
+ * @param _deck The deck.
*/
void DeckListModel::setDeckList(DeckList *_deck)
{
- deckList->deleteLater();
- deckList = _deck;
- deckList->setParent(this);
- rebuildTree();
+ if (deckList != _deck) {
+ deckList = _deck;
+ rebuildTree();
+ }
}
QList DeckListModel::getCards() const
diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp
index 41335d72d..f316e439d 100644
--- a/oracle/src/oracleimporter.cpp
+++ b/oracle/src/oracleimporter.cpp
@@ -293,6 +293,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
printingInfo.setProperty(xmlPropertyName, propertyValue);
}
+ // handle flavorNames specially due to double-faced cards
+ QString faceFlavorName = getStringPropertyFromMap(card, "faceFlavorName");
+ QString flavorName = !faceFlavorName.isEmpty() ? faceFlavorName : getStringPropertyFromMap(card, "flavorName");
+ if (!flavorName.isEmpty()) {
+ printingInfo.setProperty("flavorName", flavorName);
+ }
+
// Identifiers
for (auto i = identifierProperties.cbegin(), end = identifierProperties.cend(); i != end; ++i) {
QString mtgjsonProperty = i.key();