mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
[Client] Expose custom zone management in the deck editor
Wires the state layer into every editor surface that shows deck zones. - Deck dock: context menu on zones gains New/Rename/Delete/Change board actions, with per-zone submenus for adding cards. - Card database dock and visual database display gain an add-to-zone submenu listing custom zones per board plus a create-zone entry. - All prompt call sites pass validateNewZoneName so duplicates and reserved names are rejected inline before Ok unlocks. - Rename reuses the same dialog in name-only mode, keeping one validation contract for every zone-name entry point. - Change board marks the current board instead of offering a no-op, and the state layer refuses moves onto boards holding a same-named zone from imported decks.
This commit is contained in:
parent
2253e4ff5e
commit
3a23d955d2
11 changed files with 258 additions and 0 deletions
|
|
@ -90,6 +90,13 @@ void CardDatabaseView::decrementCard(const QString &zoneName)
|
|||
emit cardDecremented(currentCardName(), zoneName);
|
||||
}
|
||||
|
||||
void CardDatabaseView::setZoneMenuProvider(const std::function<QList<QPair<QString, QStringList>>()> &provider,
|
||||
const std::function<void()> &newZoneHandler)
|
||||
{
|
||||
zoneMenuProvider = provider;
|
||||
this->newZoneHandler = newZoneHandler;
|
||||
}
|
||||
|
||||
void CardDatabaseView::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
||||
{
|
||||
if (!current.isValid()) {
|
||||
|
|
@ -142,6 +149,39 @@ void CardDatabaseView::openCustomMenu(QPoint point)
|
|||
[this, card] { emit cardAdded(card->getName(), DECK_ZONE_SIDE); });
|
||||
connect(selectPrinting, &QAction::triggered, this, &CardDatabaseView::selectPrintingClicked);
|
||||
|
||||
if (zoneMenuProvider) {
|
||||
QMenu *addToZoneMenu = menu.addMenu(tr("Add to Zone"));
|
||||
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||
QAction *action = addToZoneMenu->addAction(InnerDecklistNode::visibleNameFromName(boardName));
|
||||
connect(action, &QAction::triggered, this,
|
||||
[this, card, boardName] { emit cardAdded(card->getName(), boardName); });
|
||||
}
|
||||
|
||||
bool anyCustomZone = false;
|
||||
const auto zoneBoards = zoneMenuProvider();
|
||||
for (const auto &zoneBoard : zoneBoards) {
|
||||
const QString &boardName = zoneBoard.first;
|
||||
const QStringList &customZones = zoneBoard.second;
|
||||
if (customZones.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
anyCustomZone = true;
|
||||
QMenu *boardSubmenu = addToZoneMenu->addMenu(InnerDecklistNode::visibleNameFromName(boardName));
|
||||
for (const QString &zoneName : customZones) {
|
||||
QAction *action = boardSubmenu->addAction(zoneName);
|
||||
connect(action, &QAction::triggered, this,
|
||||
[this, card, zoneName] { emit cardAdded(card->getName(), zoneName); });
|
||||
}
|
||||
}
|
||||
|
||||
if (anyCustomZone) {
|
||||
addToZoneMenu->addSeparator();
|
||||
}
|
||||
|
||||
QAction *newZoneAction = addToZoneMenu->addAction(tr("Create &new zone..."));
|
||||
connect(newZoneAction, &QAction::triggered, this, [this] { newZoneHandler(); });
|
||||
}
|
||||
|
||||
if (canBeCommander(*card)) {
|
||||
QAction *edhRecCommander = menu.addAction(tr("Show on EDHRec (Commander)"));
|
||||
connect(edhRecCommander, &QAction::triggered, this, [this, card] { emit edhrecClicked(card, true); });
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "../../key_signals.h"
|
||||
|
||||
#include <QTreeView>
|
||||
#include <functional>
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
|
||||
class CardDatabaseModel;
|
||||
|
|
@ -19,6 +20,12 @@ class CardDatabaseView : public QTreeView
|
|||
KeySignals searchKeySignals;
|
||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||
|
||||
/// Provides the custom zones available in the current deck, grouped by board zone.
|
||||
/// The list contains (board zone name, custom zone names) pairs for every board.
|
||||
std::function<QList<QPair<QString, QStringList>>()> zoneMenuProvider;
|
||||
/// Handler invoked when the user picks "New zone..." from the add-to-zone menu.
|
||||
std::function<void()> newZoneHandler;
|
||||
|
||||
public:
|
||||
explicit CardDatabaseView(QWidget *parent, CardDatabaseDisplayModel *model);
|
||||
|
||||
|
|
@ -33,6 +40,16 @@ public:
|
|||
return &searchKeySignals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the provider used to populate the "Add to zone" submenu of the context menu.
|
||||
* If no provider is set, the submenu is not shown.
|
||||
*
|
||||
* @param provider Returns the custom zones of the current deck, grouped by board zone
|
||||
* @param newZoneHandler Invoked when the user chooses "New zone..." in the submenu
|
||||
*/
|
||||
void setZoneMenuProvider(const std::function<QList<QPair<QString, QStringList>>()> &provider,
|
||||
const std::function<void()> &newZoneHandler);
|
||||
|
||||
signals:
|
||||
void cardChanged(const QString &cardName);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
#include "deck_editor_card_database_dock_widget.h"
|
||||
|
||||
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
|
||||
#include "card_database_view.h"
|
||||
#include "deck_state_manager.h"
|
||||
#include "deck_zone_dialog.h"
|
||||
|
||||
#include <libcockatrice/deck_list/deck_list_node_tree.h>
|
||||
|
||||
DeckEditorCardDatabaseDockWidget::DeckEditorCardDatabaseDockWidget(AbstractTabDeckEditor *parent) : QDockWidget(parent)
|
||||
{
|
||||
setObjectName("databaseDisplayDock");
|
||||
|
|
@ -15,6 +22,26 @@ void DeckEditorCardDatabaseDockWidget::createDatabaseDisplayDock(AbstractTabDeck
|
|||
{
|
||||
databaseDisplayWidget = new DeckEditorDatabaseDisplayWidget(this, deckEditor->databaseModel);
|
||||
|
||||
databaseDisplayWidget->getDatabaseView()->setZoneMenuProvider(
|
||||
[deckEditor]() -> QList<QPair<QString, QStringList>> {
|
||||
QList<QPair<QString, QStringList>> result;
|
||||
auto *deckListModel = deckEditor->deckStateManager->getModel();
|
||||
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||
result.append({boardName, deckListModel->getCustomZoneNames(boardName)});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[this, deckEditor] {
|
||||
QString boardName;
|
||||
const QString zoneName =
|
||||
DeckZoneDialog::promptForNewZone(this, {}, &boardName, [deckEditor](const QString &candidate) {
|
||||
return deckEditor->deckStateManager->validateNewZoneName(candidate);
|
||||
});
|
||||
if (!zoneName.isEmpty()) {
|
||||
deckEditor->deckStateManager->createCustomZone(boardName, zoneName);
|
||||
}
|
||||
});
|
||||
|
||||
auto *frame = new QVBoxLayout;
|
||||
frame->setObjectName("databaseDisplayFrame");
|
||||
frame->addWidget(databaseDisplayWidget);
|
||||
|
|
|
|||
|
|
@ -7,15 +7,18 @@
|
|||
#include "../tabs/api/commander_spellbook/commander_bracket_widget.h"
|
||||
#include "deck_list_style_proxy.h"
|
||||
#include "deck_state_manager.h"
|
||||
#include "deck_zone_dialog.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDockWidget>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSplitter>
|
||||
#include <QTextEdit>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/deck_list_node_tree.h>
|
||||
#include <libcockatrice/settings/deck_editor_settings.h>
|
||||
#include <libcockatrice/settings/interface_settings.h>
|
||||
#include <libcockatrice/utility/macros.h>
|
||||
|
|
@ -772,14 +775,143 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool i
|
|||
|
||||
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
|
||||
{
|
||||
const QModelIndex sourceIndex = proxy->mapToSource(deckView->indexAt(point));
|
||||
|
||||
QMenu menu;
|
||||
|
||||
const bool isCustomZoneRow = sourceIndex.isValid() && sourceIndex.data(DeckRoles::IsCustomZoneRole).toBool();
|
||||
const bool isBoardZoneRow = sourceIndex.isValid() && !isCustomZoneRow && !sourceIndex.parent().isValid();
|
||||
const bool isCardRow =
|
||||
sourceIndex.isValid() && !isCustomZoneRow && !isBoardZoneRow && !getModel()->hasChildren(sourceIndex);
|
||||
|
||||
if (isCardRow) {
|
||||
addMoveToZoneMenu(&menu, sourceIndex);
|
||||
menu.addSeparator();
|
||||
} else if (isCustomZoneRow) {
|
||||
const QString zoneName =
|
||||
sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||
|
||||
QAction *renameAction = menu.addAction(tr("&Rename zone..."));
|
||||
connect(renameAction, &QAction::triggered, this, [this, zoneName] {
|
||||
const QString newName = DeckZoneDialog::promptForRename(this, zoneName, [this](const QString &candidate) {
|
||||
return deckStateManager->validateNewZoneName(candidate);
|
||||
});
|
||||
if (!newName.isEmpty() && newName != zoneName) {
|
||||
deckStateManager->renameCustomZone(zoneName, newName);
|
||||
}
|
||||
});
|
||||
|
||||
QMenu *boardMenu = menu.addMenu(tr("Change &board"));
|
||||
addChangeBoardMenu(boardMenu, zoneName);
|
||||
|
||||
QAction *deleteAction = menu.addAction(tr("&Delete zone"));
|
||||
deleteAction->setEnabled(!getModel()->hasChildren(sourceIndex));
|
||||
deleteAction->setStatusTip(tr("Move or remove all cards first."));
|
||||
connect(deleteAction, &QAction::triggered, this, [this, zoneName] {
|
||||
const auto result =
|
||||
QMessageBox::warning(this, tr("Delete zone"), tr("Delete the zone \"%1\"?").arg(zoneName),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (result == QMessageBox::Yes) {
|
||||
deckStateManager->removeCustomZone(zoneName);
|
||||
}
|
||||
});
|
||||
menu.addSeparator();
|
||||
} else if (isBoardZoneRow) {
|
||||
const QString boardName =
|
||||
sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||
// Tokens cannot host custom zones, so only offer the action on real boards.
|
||||
const bool canHostCustomZones =
|
||||
boardName == DECK_ZONE_MAIN || boardName == DECK_ZONE_SIDE || boardName == DECK_ZONE_MAYBEBOARD;
|
||||
if (canHostCustomZones) {
|
||||
addNewZoneAction(&menu, boardName);
|
||||
menu.addSeparator();
|
||||
}
|
||||
} else if (!sourceIndex.isValid()) {
|
||||
addNewZoneAction(&menu);
|
||||
menu.addSeparator();
|
||||
}
|
||||
|
||||
QAction *selectPrinting = menu.addAction(tr("Select Printing"));
|
||||
connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector);
|
||||
|
||||
menu.exec(deckView->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::addMoveToZoneMenu(QMenu *menu, const QModelIndex &sourceCardIndex)
|
||||
{
|
||||
const auto moveToZone = [this, sourceCardIndex](const QString &targetZoneName) {
|
||||
deckStateManager->moveCardToZone(sourceCardIndex, targetZoneName);
|
||||
};
|
||||
|
||||
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||
QAction *action = menu->addAction(InnerDecklistNode::visibleNameFromName(boardName));
|
||||
connect(action, &QAction::triggered, this, [moveToZone, boardName] { moveToZone(boardName); });
|
||||
}
|
||||
|
||||
const auto tree = deckStateManager->getDeckListShared()->getTree();
|
||||
bool anyCustomZone = false;
|
||||
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||
QList<const InnerDecklistNode *> customZones = tree->getCustomZones(boardName);
|
||||
if (customZones.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
anyCustomZone = true;
|
||||
QMenu *boardSubmenu = menu->addMenu(InnerDecklistNode::visibleNameFromName(boardName));
|
||||
for (const auto *customZone : customZones) {
|
||||
QAction *action = boardSubmenu->addAction(customZone->getName());
|
||||
connect(action, &QAction::triggered, this, [moveToZone, customZone] { moveToZone(customZone->getName()); });
|
||||
}
|
||||
}
|
||||
|
||||
if (anyCustomZone) {
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
addNewZoneAction(menu);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::addChangeBoardMenu(QMenu *menu, const QString &zoneName)
|
||||
{
|
||||
const auto tree = deckStateManager->getDeckListShared()->getTree();
|
||||
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||
QAction *action = menu->addAction(InnerDecklistNode::visibleNameFromName(boardName));
|
||||
|
||||
// The board currently holding the zone is marked instead of offered.
|
||||
// Duplicate names cannot come up through the editor, so this doubles as
|
||||
// the uniqueness guard for imported decks.
|
||||
bool holdsTheZone = false;
|
||||
for (const auto *customZone : tree->getCustomZones(boardName)) {
|
||||
if (customZone->getName() == zoneName) {
|
||||
holdsTheZone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (holdsTheZone) {
|
||||
action->setCheckable(true);
|
||||
action->setChecked(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
connect(action, &QAction::triggered, this,
|
||||
[this, zoneName, boardName] { deckStateManager->moveCustomZone(zoneName, boardName); });
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::addNewZoneAction(QMenu *menu, const QString &initialBoardName)
|
||||
{
|
||||
QAction *newZoneAction = menu->addAction(tr("Create &new zone..."));
|
||||
connect(newZoneAction, &QAction::triggered, this, [this, initialBoardName] {
|
||||
QString boardName;
|
||||
const QString zoneName =
|
||||
DeckZoneDialog::promptForNewZone(this, initialBoardName, &boardName, [this](const QString &candidate) {
|
||||
return deckStateManager->validateNewZoneName(candidate);
|
||||
});
|
||||
if (!zoneName.isEmpty()) {
|
||||
deckStateManager->createCustomZone(boardName, zoneName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::refreshShortcuts()
|
||||
{
|
||||
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <QComboBox>
|
||||
#include <QDockWidget>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QTreeView>
|
||||
|
|
@ -102,6 +103,10 @@ private:
|
|||
[[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const;
|
||||
void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement);
|
||||
|
||||
void addMoveToZoneMenu(QMenu *menu, const QModelIndex &sourceCardIndex);
|
||||
void addChangeBoardMenu(QMenu *menu, const QString &zoneName);
|
||||
void addNewZoneAction(QMenu *menu, const QString &initialBoardName = {});
|
||||
|
||||
private slots:
|
||||
void decklistCustomMenu(QPoint point);
|
||||
void updateCard(QModelIndex, const QModelIndex ¤t);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "../../../../client/settings/shortcuts_settings.h"
|
||||
#include "../../cards/card_info_display_widget.h"
|
||||
#include "../../deck_editor/deck_state_manager.h"
|
||||
#include "../../deck_editor/deck_zone_dialog.h"
|
||||
#include "../../filters/filter_builder.h"
|
||||
#include "../../interface/pixel_map_generator.h"
|
||||
#include "../../interface/widgets/cards/card_info_frame_widget.h"
|
||||
|
|
@ -84,6 +85,7 @@ void TabDeckEditorVisual::createCentralFrame()
|
|||
connect(tabContainer, &TabDeckEditorVisualTabWidget::printingSelectorRequested, this,
|
||||
&TabDeckEditorVisual::showPrintingSelector);
|
||||
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardInfoRequested, this, &TabDeckEditorVisual::updateCardInfo);
|
||||
connect(tabContainer, &TabDeckEditorVisualTabWidget::newZoneRequested, this, &TabDeckEditorVisual::createNewZone);
|
||||
|
||||
centralFrame->addWidget(tabContainer);
|
||||
setCentralWidget(centralWidget);
|
||||
|
|
@ -269,6 +271,18 @@ bool TabDeckEditorVisual::actSaveDeckAs()
|
|||
return result;
|
||||
}
|
||||
|
||||
/** @brief Prompts for and creates a new custom deck zone. */
|
||||
void TabDeckEditorVisual::createNewZone()
|
||||
{
|
||||
QString boardName;
|
||||
const QString zoneName = DeckZoneDialog::promptForNewZone(this, {}, &boardName, [this](const QString &candidate) {
|
||||
return deckStateManager->validateNewZoneName(candidate);
|
||||
});
|
||||
if (!zoneName.isEmpty()) {
|
||||
deckStateManager->createCustomZone(boardName, zoneName);
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Refreshes keyboard shortcuts for this tab from settings. */
|
||||
void TabDeckEditorVisual::refreshShortcuts()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -165,6 +165,11 @@ public slots:
|
|||
*/
|
||||
bool actSaveDeckAs() override;
|
||||
|
||||
/**
|
||||
* @brief Prompts for and creates a new custom deck zone.
|
||||
*/
|
||||
void createNewZone();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Sets the deck for this tab and selects the sub-tab to open on
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent,
|
|||
&TabDeckEditorVisualTabWidget::printingSelectorRequested);
|
||||
connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardInfoRequested, this,
|
||||
&TabDeckEditorVisualTabWidget::cardInfoRequested);
|
||||
connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::newZoneRequested, this,
|
||||
&TabDeckEditorVisualTabWidget::newZoneRequested);
|
||||
|
||||
statsAnalyzer = new DeckListStatisticsAnalyzer(this, deckModel);
|
||||
statsAnalyzer->analyze();
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ signals:
|
|||
void edhrecRequested(const CardInfoPtr &cardInfo, bool isCommander);
|
||||
void printingSelectorRequested();
|
||||
void cardInfoRequested(const ExactCard &cardName);
|
||||
void newZoneRequested();
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout; ///< Layout for tabs and controls.
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <libcockatrice/card/card_info_comparator.h>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/tree/inner_deck_list_node.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -89,6 +90,19 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
|
|||
databaseView->setItemDelegate(nullptr);
|
||||
databaseView->setVisible(false);
|
||||
|
||||
// Without a deck model there is nothing to add cards to, so the zone menu stays hidden.
|
||||
if (deckListModel) {
|
||||
databaseView->setZoneMenuProvider(
|
||||
[deckListModel]() -> QList<QPair<QString, QStringList>> {
|
||||
QList<QPair<QString, QStringList>> result;
|
||||
for (const QString &boardName : InnerDecklistNode::boardZoneNames()) {
|
||||
result.append({boardName, deckListModel->getCustomZoneNames(boardName)});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[this] { emit newZoneRequested(); });
|
||||
}
|
||||
|
||||
searchEdit->setTreeView(databaseView);
|
||||
searchEdit->installEventFilter(databaseView->getKeySignals());
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ signals:
|
|||
void edhrecRequested(const CardInfoPtr &cardInfo, bool isCommander);
|
||||
void printingSelectorRequested();
|
||||
void cardInfoRequested(const ExactCard &cardName);
|
||||
void newZoneRequested();
|
||||
|
||||
protected slots:
|
||||
void initialize();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue