From 1ae1d876b188b908bd072cda7bd225a997b8555b Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Wed, 25 Feb 2026 13:37:41 -0500 Subject: [PATCH] feat(command-zone): add context menu system Implement CommandZoneMenu - context menu for interacting with cards in the command zone. Menu actions include: - Cast commander (move to stack) - Return to command zone - View/reveal zone contents - Commander-specific actions (tax reset, etc.) Integration with existing menu system: - CardMenuActionType: new action types for command zone - CardMenu: command zone action routing - MoveMenu: command zone as move destination - PlayerMenu: command zone menu integration - Zone menus: inherit from AbstractZoneMenu This provides the right-click interaction layer for command zones. --- cockatrice/CMakeLists.txt | 1 + .../src/game/player/card_menu_action_type.h | 7 +- cockatrice/src/game/player/menu/card_menu.cpp | 26 +- cockatrice/src/game/player/menu/card_menu.h | 2 +- .../game/player/menu/command_zone_menu.cpp | 305 ++++++++++++++++++ .../src/game/player/menu/command_zone_menu.h | 86 +++++ cockatrice/src/game/player/menu/grave_menu.h | 9 +- cockatrice/src/game/player/menu/hand_menu.h | 9 +- .../src/game/player/menu/library_menu.h | 9 +- cockatrice/src/game/player/menu/move_menu.cpp | 55 +++- cockatrice/src/game/player/menu/move_menu.h | 33 +- .../src/game/player/menu/player_menu.cpp | 141 ++++---- cockatrice/src/game/player/menu/player_menu.h | 30 +- .../src/game/player/menu/sideboard_menu.h | 10 +- .../src/game/player/menu/utility_menu.h | 10 +- 15 files changed, 648 insertions(+), 85 deletions(-) create mode 100644 cockatrice/src/game/player/menu/command_zone_menu.cpp create mode 100644 cockatrice/src/game/player/menu/command_zone_menu.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 70c7a3b04..fd2e97c2f 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -83,6 +83,7 @@ set(cockatrice_SOURCES src/game/phases_toolbar.cpp src/game/player/menu/card_menu.cpp src/game/player/menu/custom_zone_menu.cpp + src/game/player/menu/command_zone_menu.cpp src/game/player/menu/grave_menu.cpp src/game/player/menu/hand_menu.cpp src/game/player/menu/library_menu.cpp diff --git a/cockatrice/src/game/player/card_menu_action_type.h b/cockatrice/src/game/player/card_menu_action_type.h index aec6d6397..5c64e956d 100644 --- a/cockatrice/src/game/player/card_menu_action_type.h +++ b/cockatrice/src/game/player/card_menu_action_type.h @@ -19,7 +19,12 @@ enum CardMenuActionType cmMoveToBottomLibrary, cmMoveToHand, cmMoveToGraveyard, - cmMoveToExile + cmMoveToExile, + cmMoveToCommandZone, + cmMoveToPartnerZone, + cmMoveToCompanionZone, + cmMoveToBackgroundZone, + cmMoveToTable }; #endif // COCKATRICE_CARD_MENU_ACTION_TYPE_H diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game/player/menu/card_menu.cpp index ddf0a55f3..75118b4bc 100644 --- a/cockatrice/src/game/player/menu/card_menu.cpp +++ b/cockatrice/src/game/player/menu/card_menu.cpp @@ -4,6 +4,7 @@ #include "../../../interface/widgets/tabs/tab_game.h" #include "../../board/card_item.h" #include "../../zones/logic/view_zone_logic.h" +#include "../../zones/zone_names.h" #include "../card_menu_action_type.h" #include "../player.h" #include "../player_actions.h" @@ -65,6 +66,8 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive connect(aHide, &QAction::triggered, playerActions, &PlayerActions::actHide); aPlayFacedown = new QAction(this); connect(aPlayFacedown, &QAction::triggered, playerActions, &PlayerActions::actPlayFacedown); + aPlayAndIncreaseTax = new QAction(this); + connect(aPlayAndIncreaseTax, &QAction::triggered, playerActions, &PlayerActions::actPlayAndIncreaseTax); aRevealToAll = new QAction(this); @@ -217,6 +220,13 @@ void CardMenu::createStackMenu() // Card is on the stack if (canModifyCard) { + // Direct "Move into Play" action - first in menu for quick access + QAction *aMoveIntoPlay = new QAction(tr("Move into Play"), this); + aMoveIntoPlay->setData(cmMoveToTable); + connect(aMoveIntoPlay, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); + addAction(aMoveIntoPlay); + addSeparator(); + addAction(aAttach); addAction(aDrawArrow); addSeparator(); @@ -274,11 +284,18 @@ void CardMenu::createHandOrCustomZoneMenu() addAction(aPlay); addAction(aPlayFacedown); - QMenu *revealMenu = addMenu(tr("Re&veal to...")); + // Add "Play and Increase Tax" option for command/partner zones + QString zoneName = card->getZone()->getName(); + if (zoneName == ZoneNames::COMMAND || zoneName == ZoneNames::PARTNER) { + addAction(aPlayAndIncreaseTax); + } - initContextualPlayersMenu(revealMenu, aRevealToAll); - - connect(revealMenu, &QMenu::triggered, player->getPlayerActions(), &PlayerActions::actReveal); + // Command and partner zones are always visible to all players, so reveal is unnecessary + if (zoneName != ZoneNames::COMMAND && zoneName != ZoneNames::PARTNER) { + QMenu *revealMenu = addMenu(tr("Re&veal to...")); + initContextualPlayersMenu(revealMenu, aRevealToAll); + connect(revealMenu, &QMenu::triggered, player->getPlayerActions(), &PlayerActions::actReveal); + } addSeparator(); addAction(aClone); @@ -443,6 +460,7 @@ void CardMenu::retranslateUi() aPlay->setText(tr("&Play")); aHide->setText(tr("&Hide")); aPlayFacedown->setText(tr("Play &Face Down")); + aPlayAndIncreaseTax->setText(tr("Play and &Increase Tax Counter")); aRevealToAll->setText(tr("&All players")); //: Turn sideways or back again aTap->setText(tr("&Tap / Untap")); diff --git a/cockatrice/src/game/player/menu/card_menu.h b/cockatrice/src/game/player/menu/card_menu.h index afe24da62..3faff2e99 100644 --- a/cockatrice/src/game/player/menu/card_menu.h +++ b/cockatrice/src/game/player/menu/card_menu.h @@ -25,7 +25,7 @@ public: QMenu *mCardCounters; - QAction *aPlay, *aPlayFacedown; + QAction *aPlay, *aPlayFacedown, *aPlayAndIncreaseTax; QAction *aRevealToAll; QAction *aHide; QAction *aClone; diff --git a/cockatrice/src/game/player/menu/command_zone_menu.cpp b/cockatrice/src/game/player/menu/command_zone_menu.cpp new file mode 100644 index 000000000..2a475dbdf --- /dev/null +++ b/cockatrice/src/game/player/menu/command_zone_menu.cpp @@ -0,0 +1,305 @@ +#include "command_zone_menu.h" + +#include +#include + +Q_LOGGING_CATEGORY(CommandZoneMenuLog, "cockatrice.command_zone_menu") + +#include "../../../client/settings/cache_settings.h" +#include "../../game_scene.h" +#include "../../zones/command_zone.h" +#include "../../zones/zone_names.h" +#include "../player.h" +#include "../player_actions.h" +#include "../player_graphics_item.h" + +#include +#include + +CommandZoneMenu::CommandZoneMenu(Player *_player, const QString &_zoneName, QMenu *playerMenu) + : QMenu(playerMenu), player(_player), zoneName(_zoneName) +{ + // Initialize shortcut keys based on zone type + if (zoneName == ZoneNames::COMMAND) { + viewZoneShortcutKey = QStringLiteral("Player/aViewCommandZone"); + viewPartnerZoneShortcutKey = QStringLiteral("Player/aViewPartnerZone"); + incTaxShortcutKey = QStringLiteral("Player/aAddCommanderTax"); + decTaxShortcutKey = QStringLiteral("Player/aRemoveCommanderTax"); + incPartnerTaxShortcutKey = QStringLiteral("Player/aAddPartnerTax"); + decPartnerTaxShortcutKey = QStringLiteral("Player/aRemovePartnerTax"); + togglePartnerZoneShortcutKey = QStringLiteral("Player/aTogglePartnerZoneVisibility"); + toggleCompanionZoneShortcutKey = QStringLiteral("Player/aToggleCompanionZoneVisibility"); + toggleBackgroundZoneShortcutKey = QStringLiteral("Player/aToggleBackgroundZoneVisibility"); + } else if (zoneName == ZoneNames::PARTNER) { + viewZoneShortcutKey = QStringLiteral("Player/aViewPartnerZone"); + incTaxShortcutKey = QStringLiteral("Player/aAddPartnerTax"); + decTaxShortcutKey = QStringLiteral("Player/aRemovePartnerTax"); + togglePartnerZoneShortcutKey = QStringLiteral("Player/aTogglePartnerZoneVisibility"); + } else if (zoneName == ZoneNames::COMPANION) { + viewZoneShortcutKey = QStringLiteral("Player/aViewCompanionZone"); + toggleCompanionZoneShortcutKey = QStringLiteral("Player/aToggleCompanionZoneVisibility"); + } else if (zoneName == ZoneNames::BACKGROUND) { + viewZoneShortcutKey = QStringLiteral("Player/aViewBackgroundZone"); + toggleBackgroundZoneShortcutKey = QStringLiteral("Player/aToggleBackgroundZoneVisibility"); + } + + aViewZone = new QAction(this); + connect(aViewZone, &QAction::triggered, this, + [this]() { player->getGameScene()->toggleZoneView(player, zoneName, -1); }); + + if (zoneName == ZoneNames::COMMAND) { + aViewPartnerZone = new QAction(this); + connect(aViewPartnerZone, &QAction::triggered, this, + [this]() { player->getGameScene()->toggleZoneView(player, ZoneNames::PARTNER, -1); }); + } + + if (player->getPlayerInfo()->getLocalOrJudge()) { + addAction(aViewZone); + if (aViewPartnerZone) { + addAction(aViewPartnerZone); + } + addSeparator(); + + if (zoneName == ZoneNames::COMMAND) { + aIncreaseCommanderTax = new QAction(this); + connect(aIncreaseCommanderTax, &QAction::triggered, this, + [this]() { modifyTaxCounter(CounterIds::CommanderTax, 1); }); + addAction(aIncreaseCommanderTax); + + aDecreaseCommanderTax = new QAction(this); + connect(aDecreaseCommanderTax, &QAction::triggered, this, + [this]() { modifyTaxCounter(CounterIds::CommanderTax, -1); }); + addAction(aDecreaseCommanderTax); + + addSeparator(); + + aIncreasePartnerTax = new QAction(this); + connect(aIncreasePartnerTax, &QAction::triggered, this, + [this]() { modifyTaxCounter(CounterIds::PartnerTax, 1); }); + addAction(aIncreasePartnerTax); + + aDecreasePartnerTax = new QAction(this); + connect(aDecreasePartnerTax, &QAction::triggered, this, + [this]() { modifyTaxCounter(CounterIds::PartnerTax, -1); }); + addAction(aDecreasePartnerTax); + + addSeparator(); + } else if (zoneName == ZoneNames::PARTNER) { + aIncreaseCommanderTax = new QAction(this); + connect(aIncreaseCommanderTax, &QAction::triggered, this, + [this]() { modifyTaxCounter(CounterIds::PartnerTax, 1); }); + addAction(aIncreaseCommanderTax); + + aDecreaseCommanderTax = new QAction(this); + connect(aDecreaseCommanderTax, &QAction::triggered, this, + [this]() { modifyTaxCounter(CounterIds::PartnerTax, -1); }); + addAction(aDecreaseCommanderTax); + + addSeparator(); + } + + if (zoneName == ZoneNames::COMMAND) { + aTogglePartnerZone = new QAction(this); + connect(aTogglePartnerZone, &QAction::triggered, this, &CommandZoneMenu::actTogglePartnerZone); + addAction(aTogglePartnerZone); + + aToggleCompanionZone = new QAction(this); + connect(aToggleCompanionZone, &QAction::triggered, this, &CommandZoneMenu::actToggleCompanionZone); + addAction(aToggleCompanionZone); + + aToggleBackgroundZone = new QAction(this); + connect(aToggleBackgroundZone, &QAction::triggered, this, &CommandZoneMenu::actToggleBackgroundZone); + addAction(aToggleBackgroundZone); + + connect(player, &Player::companionZoneSupportChanged, this, + &CommandZoneMenu::updateCompanionZoneToggleVisibility); + connect(player, &Player::backgroundZoneSupportChanged, this, + &CommandZoneMenu::updateBackgroundZoneToggleVisibility); + + updateCompanionZoneToggleVisibility(player->hasServerCompanionZone()); + updateBackgroundZoneToggleVisibility(player->hasServerBackgroundZone()); + + addSeparator(); + } else if (zoneName == ZoneNames::PARTNER) { + aTogglePartnerZone = new QAction(this); + connect(aTogglePartnerZone, &QAction::triggered, this, &CommandZoneMenu::actTogglePartnerZone); + addAction(aTogglePartnerZone); + + addSeparator(); + } else if (zoneName == ZoneNames::COMPANION) { + aToggleCompanionZone = new QAction(this); + connect(aToggleCompanionZone, &QAction::triggered, this, &CommandZoneMenu::actToggleCompanionZone); + addAction(aToggleCompanionZone); + + addSeparator(); + } else if (zoneName == ZoneNames::BACKGROUND) { + aToggleBackgroundZone = new QAction(this); + connect(aToggleBackgroundZone, &QAction::triggered, this, &CommandZoneMenu::actToggleBackgroundZone); + addAction(aToggleBackgroundZone); + + addSeparator(); + } + + aToggleMinimized = new QAction(this); + connect(aToggleMinimized, &QAction::triggered, this, &CommandZoneMenu::actToggleMinimized); + addAction(aToggleMinimized); + } + + retranslateUi(); +} + +void CommandZoneMenu::retranslateUi() +{ + if (zoneName == ZoneNames::COMMAND) { + setTitle(tr("Co&mmander")); + aViewZone->setText(tr("&View command zone")); + if (aViewPartnerZone) { + aViewPartnerZone->setText(tr("View &partner zone")); + } + if (aIncreaseCommanderTax) { + aIncreaseCommanderTax->setText(tr("&Increase Commander Tax Counter (+1)")); + } + if (aDecreaseCommanderTax) { + aDecreaseCommanderTax->setText(tr("&Decrease Commander Tax Counter (-1)")); + } + if (aIncreasePartnerTax) { + aIncreasePartnerTax->setText(tr("Increase &Partner Tax Counter (+1)")); + } + if (aDecreasePartnerTax) { + aDecreasePartnerTax->setText(tr("Decrease P&artner Tax Counter (-1)")); + } + } else if (zoneName == ZoneNames::PARTNER) { + setTitle(tr("&Partner")); + aViewZone->setText(tr("&View partner zone")); + if (aIncreaseCommanderTax) { + aIncreaseCommanderTax->setText(tr("&Increase Partner Tax Counter (+1)")); + } + if (aDecreaseCommanderTax) { + aDecreaseCommanderTax->setText(tr("&Decrease Partner Tax Counter (-1)")); + } + } else if (zoneName == ZoneNames::COMPANION) { + setTitle(tr("Co&mpanion")); + aViewZone->setText(tr("&View companion zone")); + } else if (zoneName == ZoneNames::BACKGROUND) { + setTitle(tr("&Background")); + aViewZone->setText(tr("&View background zone")); + } else { + qCWarning(CommandZoneMenuLog) << "CommandZoneMenu: Unknown zone name:" << zoneName; + setTitle(zoneName); + aViewZone->setText(tr("&View zone")); + } + + if (aTogglePartnerZone) { + aTogglePartnerZone->setText(tr("Toggle &Partner Zone")); + } + if (aToggleCompanionZone) { + aToggleCompanionZone->setText(tr("Toggle C&ompanion Zone")); + } + if (aToggleBackgroundZone) { + aToggleBackgroundZone->setText(tr("Toggle &Background Zone")); + } + if (aToggleMinimized) { + aToggleMinimized->setText(tr("&Minimize")); + } +} + +void CommandZoneMenu::actTogglePartnerZone() +{ + CommandZone *partnerZone = player->getGraphicsItem()->getPartnerZoneGraphicsItem(); + if (partnerZone) { + partnerZone->toggleExpanded(); + } +} + +void CommandZoneMenu::actToggleCompanionZone() +{ + CommandZone *companionZone = player->getGraphicsItem()->getCompanionZoneGraphicsItem(); + if (companionZone) { + companionZone->toggleExpanded(); + } +} + +void CommandZoneMenu::actToggleBackgroundZone() +{ + CommandZone *backgroundZone = player->getGraphicsItem()->getBackgroundZoneGraphicsItem(); + if (backgroundZone) { + backgroundZone->toggleExpanded(); + } +} + +void CommandZoneMenu::actToggleMinimized() +{ + CommandZone *zone = nullptr; + if (zoneName == ZoneNames::COMMAND) { + zone = player->getGraphicsItem()->getCommandZoneGraphicsItem(); + } else if (zoneName == ZoneNames::PARTNER) { + zone = player->getGraphicsItem()->getPartnerZoneGraphicsItem(); + } else if (zoneName == ZoneNames::COMPANION) { + zone = player->getGraphicsItem()->getCompanionZoneGraphicsItem(); + } else if (zoneName == ZoneNames::BACKGROUND) { + zone = player->getGraphicsItem()->getBackgroundZoneGraphicsItem(); + } + if (zone) { + zone->toggleMinimized(); + } +} + +void CommandZoneMenu::modifyTaxCounter(int counterId, int delta) +{ + Command_IncCounter cmd; + cmd.set_counter_id(counterId); + cmd.set_delta(delta); + player->getPlayerActions()->sendGameCommand(cmd); +} + +void CommandZoneMenu::setShortcutsActive() +{ + ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); + + aViewZone->setShortcuts(shortcuts.getShortcut(viewZoneShortcutKey)); + if (aViewPartnerZone) + aViewPartnerZone->setShortcuts(shortcuts.getShortcut(viewPartnerZoneShortcutKey)); + + if (aIncreaseCommanderTax) + aIncreaseCommanderTax->setShortcuts(shortcuts.getShortcut(incTaxShortcutKey)); + if (aDecreaseCommanderTax) + aDecreaseCommanderTax->setShortcuts(shortcuts.getShortcut(decTaxShortcutKey)); + if (aTogglePartnerZone) + aTogglePartnerZone->setShortcuts(shortcuts.getShortcut(togglePartnerZoneShortcutKey)); + if (aToggleCompanionZone) + aToggleCompanionZone->setShortcuts(shortcuts.getShortcut(toggleCompanionZoneShortcutKey)); + if (aToggleBackgroundZone) + aToggleBackgroundZone->setShortcuts(shortcuts.getShortcut(toggleBackgroundZoneShortcutKey)); +} + +void CommandZoneMenu::setShortcutsInactive() +{ + aViewZone->setShortcut(QKeySequence()); + if (aViewPartnerZone) + aViewPartnerZone->setShortcut(QKeySequence()); + + if (aIncreaseCommanderTax) + aIncreaseCommanderTax->setShortcut(QKeySequence()); + if (aDecreaseCommanderTax) + aDecreaseCommanderTax->setShortcut(QKeySequence()); + if (aTogglePartnerZone) + aTogglePartnerZone->setShortcut(QKeySequence()); + if (aToggleCompanionZone) + aToggleCompanionZone->setShortcut(QKeySequence()); + if (aToggleBackgroundZone) + aToggleBackgroundZone->setShortcut(QKeySequence()); +} + +void CommandZoneMenu::updateCompanionZoneToggleVisibility(bool hasCompanionZone) +{ + if (aToggleCompanionZone) { + aToggleCompanionZone->setVisible(hasCompanionZone); + } +} + +void CommandZoneMenu::updateBackgroundZoneToggleVisibility(bool hasBackgroundZone) +{ + if (aToggleBackgroundZone) { + aToggleBackgroundZone->setVisible(hasBackgroundZone); + } +} diff --git a/cockatrice/src/game/player/menu/command_zone_menu.h b/cockatrice/src/game/player/menu/command_zone_menu.h new file mode 100644 index 000000000..d5f2000f4 --- /dev/null +++ b/cockatrice/src/game/player/menu/command_zone_menu.h @@ -0,0 +1,86 @@ +/** + * @file command_zone_menu.h + * @ingroup GameMenusZones + * @brief Menu for command zone and related zone right-click actions. + */ + +#ifndef COCKATRICE_COMMAND_ZONE_MENU_H +#define COCKATRICE_COMMAND_ZONE_MENU_H + +#include "abstract_zone_menu.h" + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(CommandZoneMenuLog) + +class Player; + +/** + * @class CommandZoneMenu + * @brief Context menu for command zone interactions. + * + * This menu appears when right-clicking on command zones. It provides + * zone-specific actions including viewing zone contents, toggling + * zones, and managing tax counters. + * + * Menu structure varies by zone type: + * - Command Zone: Toggle Partner Zone/Companion Zone/Background Zone, tax counter actions + * - Partner Zone: Toggle Partner Zone (self only), tax counter actions + * - Companion Zone: Toggle Companion Zone (self only), no tax actions + * - Background Zone: Toggle Background Zone (self only), no tax actions + * + * Each zone's self-toggle uses the same action type as toggling from parent menus, + * ensuring consistent labeling and keyboard shortcuts. + * + * @see PlayerMenu + * @see CommandZone + */ +class CommandZoneMenu : public QMenu, public AbstractZoneMenu +{ + Q_OBJECT + +public: + explicit CommandZoneMenu(Player *player, const QString &zoneName, QMenu *playerMenu); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; + + QAction *aViewZone = nullptr; + QAction *aViewPartnerZone = nullptr; + QAction *aTogglePartnerZone = nullptr; + QAction *aToggleCompanionZone = nullptr; + QAction *aToggleBackgroundZone = nullptr; + QAction *aToggleMinimized = nullptr; + QAction *aIncreaseCommanderTax = nullptr; + QAction *aDecreaseCommanderTax = nullptr; + QAction *aIncreasePartnerTax = nullptr; + QAction *aDecreasePartnerTax = nullptr; + +public slots: + void updateCompanionZoneToggleVisibility(bool hasCompanionZone); + void updateBackgroundZoneToggleVisibility(bool hasBackgroundZone); + +private slots: + void actTogglePartnerZone(); + void actToggleCompanionZone(); + void actToggleBackgroundZone(); + void actToggleMinimized(); + +private: + void modifyTaxCounter(int counterId, int delta); + Player *player; + QString zoneName; + + QString viewZoneShortcutKey; + QString viewPartnerZoneShortcutKey; + QString incTaxShortcutKey; + QString decTaxShortcutKey; + QString incPartnerTaxShortcutKey; + QString decPartnerTaxShortcutKey; + QString togglePartnerZoneShortcutKey; + QString toggleCompanionZoneShortcutKey; + QString toggleBackgroundZoneShortcutKey; +}; + +#endif // COCKATRICE_COMMAND_ZONE_MENU_H diff --git a/cockatrice/src/game/player/menu/grave_menu.h b/cockatrice/src/game/player/menu/grave_menu.h index faaf497b6..2a3d1d5cc 100644 --- a/cockatrice/src/game/player/menu/grave_menu.h +++ b/cockatrice/src/game/player/menu/grave_menu.h @@ -8,12 +8,13 @@ #define COCKATRICE_GRAVE_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_zone_menu.h" #include #include class Player; -class GraveyardMenu : public TearOffMenu +class GraveyardMenu : public TearOffMenu, public AbstractZoneMenu { Q_OBJECT signals: @@ -25,9 +26,9 @@ public: void createViewActions(); void populateRevealRandomMenuWithActivePlayers(); void onRevealRandomTriggered(); - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; QMenu *mRevealRandomGraveyardCard = nullptr; QMenu *moveGraveMenu = nullptr; diff --git a/cockatrice/src/game/player/menu/hand_menu.h b/cockatrice/src/game/player/menu/hand_menu.h index 51e071a62..b11e3979b 100644 --- a/cockatrice/src/game/player/menu/hand_menu.h +++ b/cockatrice/src/game/player/menu/hand_menu.h @@ -8,6 +8,7 @@ #define COCKATRICE_HAND_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_zone_menu.h" #include #include @@ -15,7 +16,7 @@ class Player; class PlayerActions; -class HandMenu : public TearOffMenu +class HandMenu : public TearOffMenu, public AbstractZoneMenu { Q_OBJECT @@ -31,9 +32,9 @@ public: return mRevealRandomHandCard; } - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; private slots: void populateRevealHandMenuWithActivePlayers(); diff --git a/cockatrice/src/game/player/menu/library_menu.h b/cockatrice/src/game/player/menu/library_menu.h index 96d4b82b1..fc1f63fa4 100644 --- a/cockatrice/src/game/player/menu/library_menu.h +++ b/cockatrice/src/game/player/menu/library_menu.h @@ -8,6 +8,7 @@ #define COCKATRICE_LIBRARY_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_zone_menu.h" #include #include @@ -15,7 +16,7 @@ class Player; class PlayerActions; -class LibraryMenu : public TearOffMenu +class LibraryMenu : public TearOffMenu, public AbstractZoneMenu { Q_OBJECT public slots: @@ -28,15 +29,15 @@ public: void createShuffleActions(); void createMoveActions(); void createViewActions(); - void retranslateUi(); + void retranslateUi() override; void populateRevealLibraryMenuWithActivePlayers(); void populateLendLibraryMenuWithActivePlayers(); void populateRevealTopCardMenuWithActivePlayers(); void onRevealLibraryTriggered(); void onLendLibraryTriggered(); void onRevealTopCardTriggered(); - void setShortcutsActive(); - void setShortcutsInactive(); + void setShortcutsActive() override; + void setShortcutsInactive() override; [[nodiscard]] bool isAlwaysRevealTopCardChecked() const { diff --git a/cockatrice/src/game/player/menu/move_menu.cpp b/cockatrice/src/game/player/menu/move_menu.cpp index d27e16009..dcd81c4c3 100644 --- a/cockatrice/src/game/player/menu/move_menu.cpp +++ b/cockatrice/src/game/player/menu/move_menu.cpp @@ -4,7 +4,7 @@ #include "../player.h" #include "../player_actions.h" -MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) +MoveMenu::MoveMenu(Player *_player) : QMenu(tr("Move to")), player(_player) { aMoveToTopLibrary = new QAction(this); aMoveToTopLibrary->setData(cmMoveToTopLibrary); @@ -17,6 +17,16 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) aMoveToGraveyard->setData(cmMoveToGraveyard); aMoveToExile = new QAction(this); aMoveToExile->setData(cmMoveToExile); + aMoveToCommandZone = new QAction(this); + aMoveToCommandZone->setData(cmMoveToCommandZone); + aMoveToPartnerZone = new QAction(this); + aMoveToPartnerZone->setData(cmMoveToPartnerZone); + aMoveToCompanionZone = new QAction(this); + aMoveToCompanionZone->setData(cmMoveToCompanionZone); + aMoveToBackgroundZone = new QAction(this); + aMoveToBackgroundZone->setData(cmMoveToBackgroundZone); + aMoveToTable = new QAction(this); + aMoveToTable->setData(cmMoveToTable); connect(aMoveToTopLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToBottomLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); @@ -25,20 +35,50 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) connect(aMoveToHand, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToGraveyard, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToExile, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); + connect(aMoveToCommandZone, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); + connect(aMoveToPartnerZone, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); + connect(aMoveToCompanionZone, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); + connect(aMoveToBackgroundZone, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); + connect(aMoveToTable, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); addAction(aMoveToTopLibrary); addAction(aMoveToXfromTopOfLibrary); addAction(aMoveToBottomLibrary); addSeparator(); + addAction(aMoveToTable); + addSeparator(); addAction(aMoveToHand); addSeparator(); addAction(aMoveToGraveyard); addSeparator(); addAction(aMoveToExile); + addSeparator(); + commandSubmenu = new QMenu(this); + commandSubmenu->addAction(aMoveToCommandZone); + commandSubmenu->addAction(aMoveToPartnerZone); + addMenu(commandSubmenu); + addAction(aMoveToCompanionZone); + addAction(aMoveToBackgroundZone); setShortcutsActive(); retranslateUi(); + + auto updateCommandZoneActionsVisibility = [this](bool has) { + commandSubmenu->menuAction()->setVisible(has); + aMoveToCommandZone->setVisible(has); + aMoveToPartnerZone->setVisible(has); + }; + auto updateCompanionZoneActionVisibility = [this](bool has) { aMoveToCompanionZone->setVisible(has); }; + auto updateBackgroundZoneActionVisibility = [this](bool has) { aMoveToBackgroundZone->setVisible(has); }; + + connect(player, &Player::commandZoneSupportChanged, this, updateCommandZoneActionsVisibility); + connect(player, &Player::companionZoneSupportChanged, this, updateCompanionZoneActionVisibility); + connect(player, &Player::backgroundZoneSupportChanged, this, updateBackgroundZoneActionVisibility); + + updateCommandZoneActionsVisibility(player->hasServerCommandZone()); + updateCompanionZoneActionVisibility(player->hasServerCompanionZone()); + updateBackgroundZoneActionVisibility(player->hasServerBackgroundZone()); } void MoveMenu::setShortcutsActive() @@ -50,6 +90,11 @@ void MoveMenu::setShortcutsActive() aMoveToHand->setShortcuts(shortcuts.getShortcut("Player/aMoveToHand")); aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard")); aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile")); + aMoveToCommandZone->setShortcuts(shortcuts.getShortcut("Player/aMoveToCommandZone")); + aMoveToPartnerZone->setShortcuts(shortcuts.getShortcut("Player/aMoveToPartnerZone")); + aMoveToCompanionZone->setShortcuts(shortcuts.getShortcut("Player/aMoveToCompanionZone")); + aMoveToBackgroundZone->setShortcuts(shortcuts.getShortcut("Player/aMoveToBackgroundZone")); + aMoveToTable->setShortcuts(shortcuts.getShortcut("Player/aMoveToTable")); } void MoveMenu::retranslateUi() @@ -60,4 +105,10 @@ void MoveMenu::retranslateUi() aMoveToHand->setText(tr("&Hand")); aMoveToGraveyard->setText(tr("&Graveyard")); aMoveToExile->setText(tr("&Exile")); -} \ No newline at end of file + commandSubmenu->setTitle(tr("Co&mmander")); + aMoveToCommandZone->setText(tr("&Commander")); + aMoveToPartnerZone->setText(tr("&Partner")); + aMoveToCompanionZone->setText(tr("C&ompanion")); + aMoveToBackgroundZone->setText(tr("&Background")); + aMoveToTable->setText(tr("&Battlefield")); +} diff --git a/cockatrice/src/game/player/menu/move_menu.h b/cockatrice/src/game/player/menu/move_menu.h index 5bf657fa4..fa8af8a3a 100644 --- a/cockatrice/src/game/player/menu/move_menu.h +++ b/cockatrice/src/game/player/menu/move_menu.h @@ -1,7 +1,12 @@ /** * @file move_menu.h * @ingroup GameMenusZones - * @brief TODO: Document this. + * @brief Submenu for moving cards between zones. + * + * MoveMenu provides actions for moving selected cards to various game zones + * (library, hand, graveyard, exile, command zone, partner zone). The menu + * dynamically shows/hides command zone actions based on whether the server + * has command zones enabled for the current game format. */ #ifndef COCKATRICE_MOVE_MENU_H @@ -9,6 +14,23 @@ #include class Player; + +/** + * @class MoveMenu + * @brief A context submenu containing actions to move cards between zones. + * + * This menu is displayed as a submenu of the card context menu and provides + * quick access to common card movement operations. Actions include moving + * cards to the library (top/bottom/specific position), hand, graveyard, + * exile, and command zones. + * + * The command zone actions (aMoveToCommandZone, aMoveToPartnerZone) are + * automatically hidden when the current game doesn't support command zones + * (i.e., non-Commander format games). + * + * @see PlayerActions + * @see CardMenu + */ class MoveMenu : public QMenu { Q_OBJECT @@ -25,6 +47,15 @@ public: QAction *aMoveToHand = nullptr; QAction *aMoveToGraveyard = nullptr; QAction *aMoveToExile = nullptr; + QAction *aMoveToCommandZone = nullptr; + QAction *aMoveToPartnerZone = nullptr; + QAction *aMoveToCompanionZone = nullptr; + QAction *aMoveToBackgroundZone = nullptr; + QAction *aMoveToTable = nullptr; + +private: + Player *player; + QMenu *commandSubmenu = nullptr; }; #endif // COCKATRICE_MOVE_MENU_H diff --git a/cockatrice/src/game/player/menu/player_menu.cpp b/cockatrice/src/game/player/menu/player_menu.cpp index 3016a727f..aa5cc6e4c 100644 --- a/cockatrice/src/game/player/menu/player_menu.cpp +++ b/cockatrice/src/game/player/menu/player_menu.cpp @@ -2,9 +2,12 @@ #include "../../../interface/widgets/tabs/tab_game.h" #include "../../board/card_item.h" +#include "../../zones/command_zone.h" #include "../../zones/hand_zone.h" #include "../../zones/pile_zone.h" #include "../../zones/table_zone.h" +#include "../../zones/zone_names.h" +#include "../player_graphics_item.h" #include "card_menu.h" #include "hand_menu.h" @@ -35,6 +38,35 @@ PlayerMenu::PlayerMenu(Player *_player) : player(_player) sideboardMenu = new SideboardMenu(player, playerMenu); playerMenu->addMenu(sideboardMenu); + commandZoneMenu = new CommandZoneMenu(player, ZoneNames::COMMAND, playerMenu); + playerMenu->addMenu(commandZoneMenu); + partnerZoneMenu = new CommandZoneMenu(player, ZoneNames::PARTNER, playerMenu); + companionZoneMenu = new CommandZoneMenu(player, ZoneNames::COMPANION, playerMenu); + playerMenu->addMenu(companionZoneMenu); + backgroundZoneMenu = new CommandZoneMenu(player, ZoneNames::BACKGROUND, playerMenu); + playerMenu->addMenu(backgroundZoneMenu); + + auto updateCommandZoneMenuVisibility = [this](bool has) { + if (commandZoneMenu) + commandZoneMenu->menuAction()->setVisible(has); + }; + auto updateCompanionZoneMenuVisibility = [this](bool has) { + if (companionZoneMenu) + companionZoneMenu->menuAction()->setVisible(has); + }; + auto updateBackgroundZoneMenuVisibility = [this](bool has) { + if (backgroundZoneMenu) + backgroundZoneMenu->menuAction()->setVisible(has); + }; + + connect(player, &Player::commandZoneSupportChanged, this, updateCommandZoneMenuVisibility); + connect(player, &Player::companionZoneSupportChanged, this, updateCompanionZoneMenuVisibility); + connect(player, &Player::backgroundZoneSupportChanged, this, updateBackgroundZoneMenuVisibility); + + updateCommandZoneMenuVisibility(player->hasServerCommandZone()); + updateCompanionZoneMenuVisibility(player->hasServerCompanionZone()); + updateBackgroundZoneMenuVisibility(player->hasServerBackgroundZone()); + customZonesMenu = new CustomZoneMenu(player); playerMenu->addMenu(customZonesMenu); playerMenu->addSeparator(); @@ -42,11 +74,22 @@ PlayerMenu::PlayerMenu(Player *_player) : player(_player) countersMenu = playerMenu->addMenu(QString()); utilityMenu = new UtilityMenu(player, playerMenu); + + // Build zone menu collection for batch shortcut operations + allZoneMenus = {handMenu, libraryMenu, graveMenu, sideboardMenu, commandZoneMenu, + partnerZoneMenu, companionZoneMenu, backgroundZoneMenu, utilityMenu}; } else { sideboardMenu = nullptr; + commandZoneMenu = nullptr; + partnerZoneMenu = nullptr; + companionZoneMenu = nullptr; + backgroundZoneMenu = nullptr; customZonesMenu = nullptr; countersMenu = nullptr; utilityMenu = nullptr; + + // Non-local players only have graveMenu + allZoneMenus = {graveMenu}; } if (player->getPlayerInfo()->getLocal()) { @@ -65,13 +108,38 @@ PlayerMenu::PlayerMenu(Player *_player) : player(_player) void PlayerMenu::setMenusForGraphicItems() { - player->getGraphicsItem()->getTableZoneGraphicsItem()->setMenu(playerMenu); - player->getGraphicsItem()->getGraveyardZoneGraphicsItem()->setMenu(graveMenu, graveMenu->aViewGraveyard); - player->getGraphicsItem()->getRfgZoneGraphicsItem()->setMenu(rfgMenu, rfgMenu->aViewRfg); + auto *graphicsItem = player->getGraphicsItem(); + if (!graphicsItem) { + return; + } + + graphicsItem->getTableZoneGraphicsItem()->setMenu(playerMenu); + graphicsItem->getGraveyardZoneGraphicsItem()->setMenu(graveMenu, graveMenu->aViewGraveyard); + graphicsItem->getRfgZoneGraphicsItem()->setMenu(rfgMenu, rfgMenu->aViewRfg); if (player->getPlayerInfo()->getLocalOrJudge()) { - player->getGraphicsItem()->getHandZoneGraphicsItem()->setMenu(handMenu); - player->getGraphicsItem()->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard); - player->getGraphicsItem()->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu); + graphicsItem->getHandZoneGraphicsItem()->setMenu(handMenu); + graphicsItem->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard); + graphicsItem->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu); + + // Command zone + if (auto *commandZone = graphicsItem->getCommandZoneGraphicsItem()) { + commandZone->setMenu(commandZoneMenu, commandZoneMenu->aViewZone); + } + + // Partner zone + if (auto *partnerZone = graphicsItem->getPartnerZoneGraphicsItem()) { + partnerZone->setMenu(partnerZoneMenu, partnerZoneMenu->aViewZone); + } + + // Companion zone + if (auto *companionZone = graphicsItem->getCompanionZoneGraphicsItem()) { + companionZone->setMenu(companionZoneMenu, companionZoneMenu->aViewZone); + } + + // Background zone + if (auto *backgroundZone = graphicsItem->getBackgroundZoneGraphicsItem()) { + backgroundZone->setMenu(backgroundZoneMenu, backgroundZoneMenu->aViewZone); + } } } @@ -82,8 +150,6 @@ QMenu *PlayerMenu::updateCardMenu(const CardItem *card) return nullptr; } - // If is spectator (as spectators don't need card menus), return - // only update the menu if the card is actually selected if ((player->getGame()->getPlayerManager()->isSpectator() && !player->getGame()->getPlayerManager()->isJudge()) || player->getGame()->getActiveCard() != card) { return nullptr; @@ -99,20 +165,14 @@ void PlayerMenu::retranslateUi() { playerMenu->setTitle(tr("Player \"%1\"").arg(player->getPlayerInfo()->getName())); - if (handMenu) { - handMenu->retranslateUi(); - } - if (libraryMenu) { - libraryMenu->retranslateUi(); + for (auto *menu : allZoneMenus) { + if (menu) { + menu->retranslateUi(); + } } - graveMenu->retranslateUi(); rfgMenu->retranslateUi(); - if (sideboardMenu) { - sideboardMenu->retranslateUi(); - } - if (countersMenu) { countersMenu->setTitle(tr("&Counters")); } @@ -126,10 +186,6 @@ void PlayerMenu::retranslateUi() counterIterator.next().value()->retranslateUi(); } - if (utilityMenu) { - utilityMenu->retranslateUi(); - } - if (sayMenu) { sayMenu->setTitle(tr("S&ay")); } @@ -138,7 +194,6 @@ void PlayerMenu::retranslateUi() void PlayerMenu::refreshShortcuts() { if (shortcutsActive) { - // Judges get access to every player's menus but only want shortcuts to be set for their own. if (player->getPlayerInfo()->getLocalOrJudge() && !player->getPlayerInfo()->getLocal()) { setShortcutsInactive(); } else { @@ -153,52 +208,30 @@ void PlayerMenu::setShortcutsActive() { shortcutsActive = true; - if (handMenu) { - handMenu->setShortcutsActive(); - } - if (libraryMenu) { - libraryMenu->setShortcutsActive(); - } - graveMenu->setShortcutsActive(); - // No shortcuts for RfgMenu yet - - if (sideboardMenu) { - sideboardMenu->setShortcutsActive(); + for (auto *menu : allZoneMenus) { + if (menu) { + menu->setShortcutsActive(); + } } QMapIterator counterIterator(player->getCounters()); while (counterIterator.hasNext()) { counterIterator.next().value()->setShortcutsActive(); } - - if (utilityMenu) { - utilityMenu->setShortcutsActive(); - } } void PlayerMenu::setShortcutsInactive() { shortcutsActive = false; - if (handMenu) { - handMenu->setShortcutsInactive(); - } - if (libraryMenu) { - libraryMenu->setShortcutsInactive(); - } - graveMenu->setShortcutsInactive(); - // No shortcuts for RfgMenu yet - - if (sideboardMenu) { - sideboardMenu->setShortcutsInactive(); + for (auto *menu : allZoneMenus) { + if (menu) { + menu->setShortcutsInactive(); + } } QMapIterator counterIterator(player->getCounters()); while (counterIterator.hasNext()) { counterIterator.next().value()->setShortcutsInactive(); } - - if (utilityMenu) { - utilityMenu->setShortcutsInactive(); - } -} \ No newline at end of file +} diff --git a/cockatrice/src/game/player/menu/player_menu.h b/cockatrice/src/game/player/menu/player_menu.h index 882bfedc5..2b7d97de6 100644 --- a/cockatrice/src/game/player/menu/player_menu.h +++ b/cockatrice/src/game/player/menu/player_menu.h @@ -1,7 +1,7 @@ /** * @file player_menu.h * @ingroup GameMenusPlayers - * @brief TODO: Document this. + * @brief Aggregates all zone-related menus for a player. */ #ifndef COCKATRICE_PLAYER_MENU_H @@ -9,6 +9,8 @@ #include "../../../interface/widgets/menus/tearoff_menu.h" #include "../player.h" +#include "abstract_zone_menu.h" +#include "command_zone_menu.h" #include "custom_zone_menu.h" #include "grave_menu.h" #include "hand_menu.h" @@ -20,8 +22,26 @@ #include #include +#include class CardItem; + +/** + * @class PlayerMenu + * @brief Central manager for a player's zone context menus. + * + * PlayerMenu creates and coordinates all zone-specific context menus for a player, + * including hand, library, graveyard, exile, sideboard, and command zones. + * It attaches menus to their corresponding graphics items and manages keyboard + * shortcut activation/deactivation. + * + * For Commander format games, PlayerMenu creates two CommandZoneMenu instances: + * one for the primary command zone and one for the partner zone. It also connects + * the partner zone's expansion state to the command zone menu's toggle action. + * + * @see CommandZoneMenu + * @see CardMenu + */ class PlayerMenu : public QObject { Q_OBJECT @@ -81,8 +101,14 @@ private: UtilityMenu *utilityMenu; SayMenu *sayMenu; CustomZoneMenu *customZonesMenu; + CommandZoneMenu *commandZoneMenu; + CommandZoneMenu *partnerZoneMenu; + CommandZoneMenu *companionZoneMenu; + CommandZoneMenu *backgroundZoneMenu; - bool shortcutsActive; + QVector allZoneMenus; + + bool shortcutsActive = false; void initSayMenu(); }; diff --git a/cockatrice/src/game/player/menu/sideboard_menu.h b/cockatrice/src/game/player/menu/sideboard_menu.h index 22d5a2d69..2416d22f9 100644 --- a/cockatrice/src/game/player/menu/sideboard_menu.h +++ b/cockatrice/src/game/player/menu/sideboard_menu.h @@ -7,18 +7,20 @@ #ifndef COCKATRICE_SIDEBOARD_MENU_H #define COCKATRICE_SIDEBOARD_MENU_H +#include "abstract_zone_menu.h" + #include class Player; -class SideboardMenu : public QMenu +class SideboardMenu : public QMenu, public AbstractZoneMenu { Q_OBJECT public: explicit SideboardMenu(Player *player, QMenu *playerMenu); - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; private: Player *player; diff --git a/cockatrice/src/game/player/menu/utility_menu.h b/cockatrice/src/game/player/menu/utility_menu.h index ff57e7252..9fa8f7fae 100644 --- a/cockatrice/src/game/player/menu/utility_menu.h +++ b/cockatrice/src/game/player/menu/utility_menu.h @@ -7,17 +7,19 @@ #ifndef COCKATRICE_UTILITY_MENU_H #define COCKATRICE_UTILITY_MENU_H +#include "abstract_zone_menu.h" + #include class Player; -class UtilityMenu : public QMenu +class UtilityMenu : public QMenu, public AbstractZoneMenu { Q_OBJECT public slots: void populatePredefinedTokensMenu(); - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; public: explicit UtilityMenu(Player *player, QMenu *playerMenu);