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.
This commit is contained in:
DawnFire42 2026-02-25 13:37:41 -05:00
parent 54e7ee1b42
commit 1ae1d876b1
15 changed files with 648 additions and 85 deletions

View file

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

View file

@ -19,7 +19,12 @@ enum CardMenuActionType
cmMoveToBottomLibrary,
cmMoveToHand,
cmMoveToGraveyard,
cmMoveToExile
cmMoveToExile,
cmMoveToCommandZone,
cmMoveToPartnerZone,
cmMoveToCompanionZone,
cmMoveToBackgroundZone,
cmMoveToTable
};
#endif // COCKATRICE_CARD_MENU_ACTION_TYPE_H

View file

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

View file

@ -25,7 +25,7 @@ public:
QMenu *mCardCounters;
QAction *aPlay, *aPlayFacedown;
QAction *aPlay, *aPlayFacedown, *aPlayAndIncreaseTax;
QAction *aRevealToAll;
QAction *aHide;
QAction *aClone;

View file

@ -0,0 +1,305 @@
#include "command_zone_menu.h"
#include <QDebug>
#include <QLoggingCategory>
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 <libcockatrice/common/counter_ids.h>
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
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);
}
}

View file

@ -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 <QLoggingCategory>
#include <QMenu>
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

View file

@ -8,12 +8,13 @@
#define COCKATRICE_GRAVE_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_zone_menu.h"
#include <QAction>
#include <QMenu>
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;

View file

@ -8,6 +8,7 @@
#define COCKATRICE_HAND_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_zone_menu.h"
#include <QAction>
#include <QMenu>
@ -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();

View file

@ -8,6 +8,7 @@
#define COCKATRICE_LIBRARY_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_zone_menu.h"
#include <QAction>
#include <QMenu>
@ -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
{

View file

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

View file

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

View file

@ -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<int, AbstractCounter *> 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<int, AbstractCounter *> counterIterator(player->getCounters());
while (counterIterator.hasNext()) {
counterIterator.next().value()->setShortcutsInactive();
}
if (utilityMenu) {
utilityMenu->setShortcutsInactive();
}
}
}

View file

@ -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 <QMenu>
#include <QObject>
#include <QVector>
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<AbstractZoneMenu *> allZoneMenus;
bool shortcutsActive = false;
void initSayMenu();
};

View file

@ -7,18 +7,20 @@
#ifndef COCKATRICE_SIDEBOARD_MENU_H
#define COCKATRICE_SIDEBOARD_MENU_H
#include "abstract_zone_menu.h"
#include <QMenu>
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;

View file

@ -7,17 +7,19 @@
#ifndef COCKATRICE_UTILITY_MENU_H
#define COCKATRICE_UTILITY_MENU_H
#include "abstract_zone_menu.h"
#include <QMenu>
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);