Merge branch 'Cockatrice:master' into master

This commit is contained in:
scotland0208 2026-03-24 14:53:21 -05:00 committed by GitHub
commit d74717dd6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 164 additions and 108 deletions

View file

@ -8,6 +8,7 @@
#define COUNTER_H #define COUNTER_H
#include "../../interface/widgets/menus/tearoff_menu.h" #include "../../interface/widgets/menus/tearoff_menu.h"
#include "../player/menu/abstract_player_component.h"
#include <QGraphicsItem> #include <QGraphicsItem>
#include <QInputDialog> #include <QInputDialog>
@ -18,7 +19,7 @@ class QKeyEvent;
class QMenu; class QMenu;
class QString; class QString;
class AbstractCounter : public QObject, public QGraphicsItem class AbstractCounter : public QObject, public QGraphicsItem, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
Q_INTERFACES(QGraphicsItem) Q_INTERFACES(QGraphicsItem)
@ -56,10 +57,10 @@ public:
QGraphicsItem *parent = nullptr); QGraphicsItem *parent = nullptr);
~AbstractCounter() override; ~AbstractCounter() override;
void retranslateUi(); void retranslateUi() override;
void setValue(int _value); void setValue(int _value);
void setShortcutsActive(); void setShortcutsActive() override;
void setShortcutsInactive(); void setShortcutsInactive() override;
void delCounter(); void delCounter();
QMenu *getMenu() const QMenu *getMenu() const

View file

@ -0,0 +1,32 @@
/**
* @file abstract_player_component.h
* @ingroup GameMenusPlayers
* @brief Polymorphic interface for player-bound UI components managed by PlayerMenu.
*/
#ifndef COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H
#define COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H
/**
* @brief Interface for player-bound UI components that need shortcut and translation lifecycle management.
*
* Not a QObject avoids diamond inheritance with Qt's MOC. Each concrete component
* inherits QObject through its Qt base class (QMenu, TearOffMenu, QGraphicsItem, etc.)
* and this interface through regular multiple inheritance.
*/
class AbstractPlayerComponent
{
public:
virtual ~AbstractPlayerComponent() = default;
/// Bind keyboard shortcuts. Called when this player gains focus.
virtual void setShortcutsActive() = 0;
/// Unbind keyboard shortcuts. Called when this player loses focus.
virtual void setShortcutsInactive() = 0;
/// Retranslate all user-visible strings. Called on language change.
virtual void retranslateUi() = 0;
};
#endif // COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H

View file

@ -7,15 +7,23 @@
#ifndef COCKATRICE_CUSTOM_ZONE_MENU_H #ifndef COCKATRICE_CUSTOM_ZONE_MENU_H
#define COCKATRICE_CUSTOM_ZONE_MENU_H #define COCKATRICE_CUSTOM_ZONE_MENU_H
#include "abstract_player_component.h"
#include <QMenu> #include <QMenu>
class Player; class Player;
class CustomZoneMenu : public QMenu class CustomZoneMenu : public QMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit CustomZoneMenu(Player *player); explicit CustomZoneMenu(Player *player);
void retranslateUi(); void retranslateUi() override;
void setShortcutsActive() override
{
}
void setShortcutsInactive() override
{
}
private: private:
Player *player; Player *player;

View file

@ -8,12 +8,13 @@
#define COCKATRICE_GRAVE_MENU_H #define COCKATRICE_GRAVE_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h" #include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
class Player; class Player;
class GraveyardMenu : public TearOffMenu class GraveyardMenu : public TearOffMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
signals: signals:
@ -25,9 +26,9 @@ public:
void createViewActions(); void createViewActions();
void populateRevealRandomMenuWithActivePlayers(); void populateRevealRandomMenuWithActivePlayers();
void onRevealRandomTriggered(); void onRevealRandomTriggered();
void retranslateUi(); void retranslateUi() override;
void setShortcutsActive(); void setShortcutsActive() override;
void setShortcutsInactive(); void setShortcutsInactive() override;
QMenu *mRevealRandomGraveyardCard = nullptr; QMenu *mRevealRandomGraveyardCard = nullptr;
QMenu *moveGraveMenu = nullptr; QMenu *moveGraveMenu = nullptr;

View file

@ -8,6 +8,7 @@
#define COCKATRICE_HAND_MENU_H #define COCKATRICE_HAND_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h" #include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
@ -15,7 +16,7 @@
class Player; class Player;
class PlayerActions; class PlayerActions;
class HandMenu : public TearOffMenu class HandMenu : public TearOffMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
@ -31,9 +32,9 @@ public:
return mRevealRandomHandCard; return mRevealRandomHandCard;
} }
void retranslateUi(); void retranslateUi() override;
void setShortcutsActive(); void setShortcutsActive() override;
void setShortcutsInactive(); void setShortcutsInactive() override;
private slots: private slots:
void populateRevealHandMenuWithActivePlayers(); void populateRevealHandMenuWithActivePlayers();

View file

@ -8,6 +8,7 @@
#define COCKATRICE_LIBRARY_MENU_H #define COCKATRICE_LIBRARY_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h" #include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
@ -15,7 +16,7 @@
class Player; class Player;
class PlayerActions; class PlayerActions;
class LibraryMenu : public TearOffMenu class LibraryMenu : public TearOffMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
public slots: public slots:
@ -28,15 +29,15 @@ public:
void createShuffleActions(); void createShuffleActions();
void createMoveActions(); void createMoveActions();
void createViewActions(); void createViewActions();
void retranslateUi(); void retranslateUi() override;
void populateRevealLibraryMenuWithActivePlayers(); void populateRevealLibraryMenuWithActivePlayers();
void populateLendLibraryMenuWithActivePlayers(); void populateLendLibraryMenuWithActivePlayers();
void populateRevealTopCardMenuWithActivePlayers(); void populateRevealTopCardMenuWithActivePlayers();
void onRevealLibraryTriggered(); void onRevealLibraryTriggered();
void onLendLibraryTriggered(); void onLendLibraryTriggered();
void onRevealTopCardTriggered(); void onRevealTopCardTriggered();
void setShortcutsActive(); void setShortcutsActive() override;
void setShortcutsInactive(); void setShortcutsInactive() override;
[[nodiscard]] bool isAlwaysRevealTopCardChecked() const [[nodiscard]] bool isAlwaysRevealTopCardChecked() const
{ {

View file

@ -15,33 +15,24 @@ PlayerMenu::PlayerMenu(Player *_player) : player(_player)
playerMenu = new TearOffMenu(); playerMenu = new TearOffMenu();
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getPlayerInfo()->getLocalOrJudge()) {
handMenu = new HandMenu(player, player->getPlayerActions(), playerMenu); handMenu = addManagedMenu<HandMenu>(player, player->getPlayerActions(), playerMenu);
playerMenu->addMenu(handMenu); libraryMenu = addManagedMenu<LibraryMenu>(player, playerMenu);
libraryMenu = new LibraryMenu(player, playerMenu);
playerMenu->addMenu(libraryMenu);
} else { } else {
handMenu = nullptr; handMenu = nullptr;
libraryMenu = nullptr; libraryMenu = nullptr;
} }
graveMenu = new GraveyardMenu(player, playerMenu); graveMenu = addManagedMenu<GraveyardMenu>(player, playerMenu);
playerMenu->addMenu(graveMenu); rfgMenu = addManagedMenu<RfgMenu>(player, playerMenu);
rfgMenu = new RfgMenu(player, playerMenu);
playerMenu->addMenu(rfgMenu);
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getPlayerInfo()->getLocalOrJudge()) {
sideboardMenu = new SideboardMenu(player, playerMenu); sideboardMenu = addManagedMenu<SideboardMenu>(player, playerMenu);
playerMenu->addMenu(sideboardMenu); customZonesMenu = addManagedMenu<CustomZoneMenu>(player);
customZonesMenu = new CustomZoneMenu(player);
playerMenu->addMenu(customZonesMenu);
playerMenu->addSeparator(); playerMenu->addSeparator();
countersMenu = playerMenu->addMenu(QString()); countersMenu = playerMenu->addMenu(QString());
utilityMenu = new UtilityMenu(player, playerMenu); utilityMenu = createManagedComponent<UtilityMenu>(player, playerMenu);
} else { } else {
sideboardMenu = nullptr; sideboardMenu = nullptr;
customZonesMenu = nullptr; customZonesMenu = nullptr;
@ -50,8 +41,7 @@ PlayerMenu::PlayerMenu(Player *_player) : player(_player)
} }
if (player->getPlayerInfo()->getLocal()) { if (player->getPlayerInfo()->getLocal()) {
sayMenu = new SayMenu(player); sayMenu = addManagedMenu<SayMenu>(player);
playerMenu->addMenu(sayMenu);
} else { } else {
sayMenu = nullptr; sayMenu = nullptr;
} }
@ -99,40 +89,18 @@ void PlayerMenu::retranslateUi()
{ {
playerMenu->setTitle(tr("Player \"%1\"").arg(player->getPlayerInfo()->getName())); playerMenu->setTitle(tr("Player \"%1\"").arg(player->getPlayerInfo()->getName()));
if (handMenu) { for (auto *component : managedComponents) {
handMenu->retranslateUi(); component->retranslateUi();
}
if (libraryMenu) {
libraryMenu->retranslateUi();
}
graveMenu->retranslateUi();
rfgMenu->retranslateUi();
if (sideboardMenu) {
sideboardMenu->retranslateUi();
} }
if (countersMenu) { if (countersMenu) {
countersMenu->setTitle(tr("&Counters")); countersMenu->setTitle(tr("&Counters"));
} }
if (customZonesMenu) {
customZonesMenu->retranslateUi();
}
QMapIterator<int, AbstractCounter *> counterIterator(player->getCounters()); QMapIterator<int, AbstractCounter *> counterIterator(player->getCounters());
while (counterIterator.hasNext()) { while (counterIterator.hasNext()) {
counterIterator.next().value()->retranslateUi(); counterIterator.next().value()->retranslateUi();
} }
if (utilityMenu) {
utilityMenu->retranslateUi();
}
if (sayMenu) {
sayMenu->setTitle(tr("S&ay"));
}
} }
void PlayerMenu::refreshShortcuts() void PlayerMenu::refreshShortcuts()
@ -153,52 +121,29 @@ void PlayerMenu::setShortcutsActive()
{ {
shortcutsActive = true; shortcutsActive = true;
if (handMenu) { for (auto *component : managedComponents) {
handMenu->setShortcutsActive(); component->setShortcutsActive();
}
if (libraryMenu) {
libraryMenu->setShortcutsActive();
}
graveMenu->setShortcutsActive();
// No shortcuts for RfgMenu yet
if (sideboardMenu) {
sideboardMenu->setShortcutsActive();
} }
// Counters implement AbstractPlayerComponent but are iterated via Player::counters
// (the authoritative source) rather than managedComponents to avoid a redundant
// list that must stay in sync with the map.
QMapIterator<int, AbstractCounter *> counterIterator(player->getCounters()); QMapIterator<int, AbstractCounter *> counterIterator(player->getCounters());
while (counterIterator.hasNext()) { while (counterIterator.hasNext()) {
counterIterator.next().value()->setShortcutsActive(); counterIterator.next().value()->setShortcutsActive();
} }
if (utilityMenu) {
utilityMenu->setShortcutsActive();
}
} }
void PlayerMenu::setShortcutsInactive() void PlayerMenu::setShortcutsInactive()
{ {
shortcutsActive = false; shortcutsActive = false;
if (handMenu) { for (auto *component : managedComponents) {
handMenu->setShortcutsInactive(); component->setShortcutsInactive();
}
if (libraryMenu) {
libraryMenu->setShortcutsInactive();
}
graveMenu->setShortcutsInactive();
// No shortcuts for RfgMenu yet
if (sideboardMenu) {
sideboardMenu->setShortcutsInactive();
} }
QMapIterator<int, AbstractCounter *> counterIterator(player->getCounters()); QMapIterator<int, AbstractCounter *> counterIterator(player->getCounters());
while (counterIterator.hasNext()) { while (counterIterator.hasNext()) {
counterIterator.next().value()->setShortcutsInactive(); counterIterator.next().value()->setShortcutsInactive();
} }
if (utilityMenu) {
utilityMenu->setShortcutsInactive();
}
} }

View file

@ -1,7 +1,7 @@
/** /**
* @file player_menu.h * @file player_menu.h
* @ingroup GameMenusPlayers * @ingroup GameMenusPlayers
* @brief TODO: Document this. * @brief Orchestrates lifecycle management for all player-bound UI components.
*/ */
#ifndef COCKATRICE_PLAYER_MENU_H #ifndef COCKATRICE_PLAYER_MENU_H
@ -18,6 +18,7 @@
#include "sideboard_menu.h" #include "sideboard_menu.h"
#include "utility_menu.h" #include "utility_menu.h"
#include <QList>
#include <QMenu> #include <QMenu>
#include <QObject> #include <QObject>
@ -37,6 +38,7 @@ private slots:
public: public:
PlayerMenu(Player *player); PlayerMenu(Player *player);
/// Lifecycle methods: delegate to all managedComponents, plus counters separately via player->getCounters().
void retranslateUi(); void retranslateUi();
QMenu *updateCardMenu(const CardItem *card); QMenu *updateCardMenu(const CardItem *card);
@ -66,7 +68,9 @@ public:
return shortcutsActive; return shortcutsActive;
} }
/// Delegates to all managedComponents, plus counters separately.
void setShortcutsActive(); void setShortcutsActive();
/// Delegates to all managedComponents, plus counters separately.
void setShortcutsInactive(); void setShortcutsInactive();
private: private:
@ -82,9 +86,26 @@ private:
SayMenu *sayMenu; SayMenu *sayMenu;
CustomZoneMenu *customZonesMenu; CustomZoneMenu *customZonesMenu;
bool shortcutsActive; /// Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via player->getCounters().
QList<AbstractPlayerComponent *> managedComponents;
bool shortcutsActive = false;
void initSayMenu(); /// Creates component, adds it as a submenu of playerMenu, and registers in managedComponents.
template <typename MenuT, typename... Args> MenuT *addManagedMenu(Args &&...args)
{
auto *menu = new MenuT(std::forward<Args>(args)...);
playerMenu->addMenu(menu);
managedComponents.append(menu);
return menu;
}
/// Creates component and registers in managedComponents, but does NOT add it as a submenu.
template <typename ComponentT, typename... Args> ComponentT *createManagedComponent(Args &&...args)
{
auto *component = new ComponentT(std::forward<Args>(args)...);
managedComponents.append(component);
return component;
}
}; };
#endif // COCKATRICE_PLAYER_MENU_H #endif // COCKATRICE_PLAYER_MENU_H

View file

@ -8,19 +8,26 @@
#define COCKATRICE_RFG_MENU_H #define COCKATRICE_RFG_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h" #include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
class Player; class Player;
class RfgMenu : public TearOffMenu class RfgMenu : public TearOffMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit RfgMenu(Player *player, QWidget *parent = nullptr); explicit RfgMenu(Player *player, QWidget *parent = nullptr);
void createMoveActions(); void createMoveActions();
void createViewActions(); void createViewActions();
void retranslateUi(); void retranslateUi() override;
void setShortcutsActive() override
{
}
void setShortcutsInactive() override
{
}
QMenu *moveRfgMenu = nullptr; QMenu *moveRfgMenu = nullptr;

View file

@ -8,6 +8,31 @@ SayMenu::SayMenu(Player *_player) : player(_player)
{ {
connect(&SettingsCache::instance().messages(), &MessageSettings::messageMacrosChanged, this, &SayMenu::initSayMenu); connect(&SettingsCache::instance().messages(), &MessageSettings::messageMacrosChanged, this, &SayMenu::initSayMenu);
initSayMenu(); initSayMenu();
retranslateUi();
}
void SayMenu::retranslateUi()
{
setTitle(tr("S&ay"));
}
void SayMenu::setShortcutsActive()
{
shortcutsActive = true;
const auto menuActions = actions();
for (int i = 0; i < menuActions.size() && i < 10; ++i) {
menuActions[i]->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10)));
}
}
void SayMenu::setShortcutsInactive()
{
shortcutsActive = false;
for (auto *action : actions()) {
action->setShortcut(QKeySequence());
}
} }
void SayMenu::initSayMenu() void SayMenu::initSayMenu()
@ -19,10 +44,11 @@ void SayMenu::initSayMenu()
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
auto *newAction = new QAction(SettingsCache::instance().messages().getMessageAt(i), this); auto *newAction = new QAction(SettingsCache::instance().messages().getMessageAt(i), this);
if (i < 10) {
newAction->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10)));
}
connect(newAction, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actSayMessage); connect(newAction, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actSayMessage);
addAction(newAction); addAction(newAction);
} }
if (shortcutsActive) {
setShortcutsActive();
}
} }

View file

@ -7,18 +7,27 @@
#ifndef COCKATRICE_SAY_MENU_H #ifndef COCKATRICE_SAY_MENU_H
#define COCKATRICE_SAY_MENU_H #define COCKATRICE_SAY_MENU_H
#include "abstract_player_component.h"
#include <QMenu> #include <QMenu>
class Player; class Player;
class SayMenu : public QMenu class SayMenu : public QMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit SayMenu(Player *player); explicit SayMenu(Player *player);
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
private slots:
void initSayMenu(); void initSayMenu();
private: private:
Player *player; Player *player;
bool shortcutsActive = false;
}; };
#endif // COCKATRICE_SAY_MENU_H #endif // COCKATRICE_SAY_MENU_H

View file

@ -7,18 +7,20 @@
#ifndef COCKATRICE_SIDEBOARD_MENU_H #ifndef COCKATRICE_SIDEBOARD_MENU_H
#define COCKATRICE_SIDEBOARD_MENU_H #define COCKATRICE_SIDEBOARD_MENU_H
#include "abstract_player_component.h"
#include <QMenu> #include <QMenu>
class Player; class Player;
class SideboardMenu : public QMenu class SideboardMenu : public QMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit SideboardMenu(Player *player, QMenu *playerMenu); explicit SideboardMenu(Player *player, QMenu *playerMenu);
void retranslateUi(); void retranslateUi() override;
void setShortcutsActive(); void setShortcutsActive() override;
void setShortcutsInactive(); void setShortcutsInactive() override;
private: private:
Player *player; Player *player;

View file

@ -7,17 +7,19 @@
#ifndef COCKATRICE_UTILITY_MENU_H #ifndef COCKATRICE_UTILITY_MENU_H
#define COCKATRICE_UTILITY_MENU_H #define COCKATRICE_UTILITY_MENU_H
#include "abstract_player_component.h"
#include <QMenu> #include <QMenu>
class Player; class Player;
class UtilityMenu : public QMenu class UtilityMenu : public QMenu, public AbstractPlayerComponent
{ {
Q_OBJECT Q_OBJECT
public slots: public slots:
void populatePredefinedTokensMenu(); void populatePredefinedTokensMenu();
void retranslateUi(); void retranslateUi() override;
void setShortcutsActive(); void setShortcutsActive() override;
void setShortcutsInactive(); void setShortcutsInactive() override;
public: public:
explicit UtilityMenu(Player *player, QMenu *playerMenu); explicit UtilityMenu(Player *player, QMenu *playerMenu);