[Game] Move graphics out of game and into game_graphics (#6928)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run

* [Game][Player] Pull out graphics_items out of player_logic

Took 25 seconds


Took 9 minutes

* [Game] Move graphics files into game_graphics

Took 1 minute

Took 2 minutes

Took 23 seconds

Took 1 minute

Took 2 seconds

* Include.

Took 4 minutes

Took 3 minutes

Took 4 minutes

Took 1 minute

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-06-09 09:51:13 +02:00 committed by GitHub
parent cbfd286908
commit da4ba222c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
116 changed files with 208 additions and 198 deletions

View file

@ -3,7 +3,7 @@
#include "../interface/widgets/tabs/tab_game.h"
#include "player/player_logic.h"
AbstractGame::AbstractGame(TabGame *_tab) : QObject(_tab), tab(_tab)
AbstractGame::AbstractGame(QObject *_parent) : QObject(_parent)
{
gameMetaInfo = new GameMetaInfo(this);
gameEventHandler = new GameEventHandler(this);

View file

@ -16,26 +16,19 @@
#include <libcockatrice/protocol/pb/game_replay.pb.h>
class CardItem;
class TabGame;
class AbstractGame : public QObject
{
Q_OBJECT
public:
explicit AbstractGame(TabGame *tab);
explicit AbstractGame(QObject *parent);
TabGame *tab;
GameMetaInfo *gameMetaInfo;
GameState *gameState;
GameEventHandler *gameEventHandler;
PlayerManager *playerManager;
CardItem *activeCard;
TabGame *getTab() const
{
return tab;
}
GameMetaInfo *getGameMetaInfo()
{
return gameMetaInfo;

View file

@ -1,6 +1,6 @@
#include "arrow_registry.h"
#include "board/arrow_item.h"
#include "../game_graphics/board/arrow_item.h"
void ArrowRegistry::insert(QSharedPointer<ArrowData> data, ArrowItem *arrow)
{

View file

@ -1,72 +0,0 @@
#include "abstract_card_drag_item.h"
#include "../../client/settings/cache_settings.h"
#include "../z_values.h"
#include <QCursor>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
const QColor GHOST_MASK = QColor(255, 255, 255, 50);
AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
const QPointF &_hotSpot,
AbstractCardDragItem *parentDrag)
: QGraphicsItem(), item(_item), hotSpot(_hotSpot)
{
if (parentDrag) {
parentDrag->addChildDrag(this);
setZValue(ZValues::childDragZValue(hotSpot.x(), hotSpot.y()));
connect(parentDrag, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater);
} else {
hotSpot = QPointF{qBound(0.0, hotSpot.x(), CardDimensions::WIDTH_F - 1),
qBound(0.0, hotSpot.y(), CardDimensions::HEIGHT_F - 1)};
setCursor(Qt::ClosedHandCursor);
setZValue(ZValues::DRAG_ITEM);
}
if (item->getTapped()) {
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(90)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
}
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
prepareGeometryChange();
update();
});
connect(item, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater);
}
QPainterPath AbstractCardDragItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
void AbstractCardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
item->paint(painter, option, widget);
// adds a mask to the card so it looks like the card hasnt been placed yet
painter->fillPath(shape(), GHOST_MASK);
}
void AbstractCardDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
event->accept();
updatePosition(event->scenePos());
}
void AbstractCardDragItem::addChildDrag(AbstractCardDragItem *child)
{
childDrags << child;
}

View file

@ -1,56 +0,0 @@
/**
* @file abstract_card_drag_item.h
* @ingroup GameGraphicsCards
*/
//! \todo Document this file.
#ifndef ABSTRACTCARDDRAGITEM_H
#define ABSTRACTCARDDRAGITEM_H
#include "abstract_card_item.h"
class QGraphicsScene;
class CardZone;
class CardInfo;
class AbstractCardDragItem : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
protected:
AbstractCardItem *item;
QPointF hotSpot;
QList<AbstractCardDragItem *> childDrags;
public:
enum
{
Type = typeCardDrag
};
[[nodiscard]] int type() const override
{
return Type;
}
AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0);
[[nodiscard]] QRectF boundingRect() const override
{
return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
}
[[nodiscard]] QPainterPath shape() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] AbstractCardItem *getItem() const
{
return item;
}
[[nodiscard]] QPointF getHotSpot() const
{
return hotSpot;
}
void addChildDrag(AbstractCardDragItem *child);
virtual void updatePosition(const QPointF &cursorScenePos) = 0;
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif

View file

@ -1,349 +0,0 @@
#include "abstract_card_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../game_scene.h"
#include "../z_values.h"
#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <algorithm>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id)
: ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0),
bgColor(Qt::transparent), isHovered(false), realZValue(0)
{
setCursor(Qt::OpenHandCursor);
setFlag(ItemIsSelectable);
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); });
refreshCardInfo();
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
prepareGeometryChange();
update();
});
}
AbstractCardItem::~AbstractCardItem()
{
emit deleteCardInfoPopup(cardRef.name);
}
QRectF AbstractCardItem::boundingRect() const
{
return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
}
QPainterPath AbstractCardItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
void AbstractCardItem::pixmapUpdated()
{
update();
emit sigPixmapUpdated();
}
void AbstractCardItem::refreshCardInfo()
{
exactCard = CardDatabaseManager::query()->getCard(cardRef);
if (!exactCard && !cardRef.name.isEmpty()) {
CardInfo::UiAttributes attributes = {.tableRow = -1};
auto info = CardInfo::newInstance(cardRef.name, "", true, {}, {}, {}, {}, attributes);
exactCard = ExactCard(info);
}
if (exactCard) {
connect(exactCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &AbstractCardItem::pixmapUpdated);
}
cacheBgColor();
update();
}
/**
* Convenience method to get the CardInfo of the exactCard
* @return A const reference to the CardInfo, or an empty CardInfo if card was null
*/
const CardInfo &AbstractCardItem::getCardInfo() const
{
return exactCard.getInfo();
}
void AbstractCardItem::setRealZValue(qreal _zValue)
{
realZValue = _zValue;
// During hover, zValue is overridden to HOVERED_CARD. Layout operations
// like reorganizeCards() call setRealZValue() on all cards including the
// hovered one — skip setZValue() here to avoid clobbering the override.
if (!isHovered) {
setZValue(_zValue);
}
}
QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
{
return QSizeF(painter->combinedTransform().map(QLineF(0, 0, boundingRect().width(), 0)).length(),
painter->combinedTransform().map(QLineF(0, 0, 0, boundingRect().height())).length());
}
void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle)
{
const int MAX_FONT_SIZE = SettingsCache::instance().getMaxFontSize();
const int fontSize = std::max(9, MAX_FONT_SIZE);
QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect());
int scale = resetPainterTransform(painter);
painter->translate(totalBoundingRect.width() / 2, totalBoundingRect.height() / 2);
painter->rotate(angle);
painter->translate(-translatedSize.width() / 2, -translatedSize.height() / 2);
QFont f;
f.setPixelSize(fontSize * scale);
painter->setFont(f);
}
void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle)
{
qreal scaleFactor = translatedSize.width() / boundingRect().width();
QPixmap translatedPixmap;
bool paintImage = true;
if (facedown || cardRef.name.isEmpty()) {
// never reveal card color, always paint the card back
CardPictureLoader::getCardBackPixmap(translatedPixmap, translatedSize.toSize());
} else {
// don't even spend time trying to load the picture if our size is too small
if (translatedSize.width() > 10) {
CardPictureLoader::getPixmap(translatedPixmap, exactCard, translatedSize.toSize());
if (translatedPixmap.isNull()) {
paintImage = false;
}
} else {
paintImage = false;
}
}
painter->save();
if (paintImage) {
painter->save();
painter->setClipPath(shape());
painter->drawPixmap(boundingRect(), translatedPixmap, QRectF({0, 0}, translatedPixmap.size()));
painter->restore();
} else {
painter->setBrush(bgColor);
painter->drawPath(shape());
}
if (translatedPixmap.isNull() || SettingsCache::instance().getDisplayCardNames() || facedown) {
painter->save();
transformPainter(painter, translatedSize, angle);
painter->setPen(Qt::white);
painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode);
QString nameStr;
if (facedown) {
nameStr = "# " + QString::number(id);
} else {
QString prefix = "";
if (SettingsCache::instance().debug().getShowCardId()) {
prefix = "#" + QString::number(id) + " ";
}
nameStr = prefix + cardRef.name;
}
painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor,
translatedSize.height() - 6 * scaleFactor),
Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere, nameStr);
painter->restore();
}
painter->restore();
}
void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
painter->save();
QSizeF translatedSize = getTranslatedSize(painter);
paintPicture(painter, translatedSize, tapAngle);
painter->setRenderHint(QPainter::Antialiasing, false);
if (isSelected() || isHovered) {
QPen pen;
if (isHovered) {
pen.setColor(Qt::yellow);
}
if (isSelected()) {
pen.setColor(Qt::red);
}
pen.setWidth(0); // Cosmetic pen
painter->setPen(pen);
painter->drawPath(shape());
}
painter->restore();
}
void AbstractCardItem::setCardRef(const CardRef &_cardRef)
{
if (cardRef == _cardRef) {
return;
}
emit deleteCardInfoPopup(cardRef.name);
if (exactCard) {
disconnect(exactCard.getCardPtr().data(), nullptr, this, nullptr);
}
cardRef = _cardRef;
refreshCardInfo();
}
void AbstractCardItem::setHovered(bool _hovered)
{
if (isHovered == _hovered) {
return;
}
if (_hovered) {
processHoverEvent();
} else {
// Mark the hovered card's current scene footprint dirty so overlapped
// sibling zones (e.g. StackZone) repaint after the card moves away.
if (scene()) {
scene()->update(sceneBoundingRect());
}
}
isHovered = _hovered;
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
setTransformOriginPoint(_hovered ? CardDimensions::WIDTH_HALF_F : 0, _hovered ? CardDimensions::HEIGHT_HALF_F : 0);
update();
}
void AbstractCardItem::setColor(const QString &_color)
{
color = _color;
cacheBgColor();
update();
}
void AbstractCardItem::cacheBgColor()
{
QChar colorChar;
if (color.isEmpty()) {
colorChar = exactCard.getInfo().getColorChar();
} else {
colorChar = color.at(0);
}
switch (colorChar.toLower().toLatin1()) {
case 'b':
bgColor = QColor(0, 0, 0);
break;
case 'u':
bgColor = QColor(0, 140, 180);
break;
case 'w':
bgColor = QColor(255, 250, 140);
break;
case 'r':
bgColor = QColor(230, 0, 0);
break;
case 'g':
bgColor = QColor(0, 160, 0);
break;
case 'm':
bgColor = QColor(250, 190, 30);
break;
default:
bgColor = QColor(230, 230, 230);
break;
}
}
void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
{
if (tapped == _tapped) {
return;
}
tapped = _tapped;
if (SettingsCache::instance().getTapAnimation() && canAnimate) {
static_cast<GameScene *>(scene())->registerAnimationItem(this);
} else {
tapAngle = tapped ? 90 : 0;
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(tapAngle)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
update();
}
}
void AbstractCardItem::setFaceDown(bool _facedown)
{
facedown = _facedown;
update();
}
void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->modifiers() & Qt::AltModifier) && event->button() == Qt::LeftButton) {
emit cardShiftClicked(cardRef.name);
} else if ((event->modifiers() & Qt::ControlModifier)) {
setSelected(!isSelected());
} else if (!isSelected()) {
scene()->clearSelection();
setSelected(true);
}
if (event->button() == Qt::LeftButton) {
setCursor(Qt::ClosedHandCursor);
} else if (event->button() == Qt::MiddleButton) {
emit showCardInfoPopup(event->screenPos(), cardRef);
}
event->accept();
}
void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::MiddleButton) {
emit deleteCardInfoPopup(cardRef.name);
}
// This function ensures the parent function doesn't mess around with our selection.
event->accept();
}
void AbstractCardItem::processHoverEvent()
{
emit hovered(this);
}
QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == ItemSelectedHasChanged) {
update();
return value;
} else {
return ArrowTarget::itemChange(change, value);
}
}

View file

@ -1,137 +0,0 @@
/**
* @file abstract_card_item.h
* @ingroup GameGraphicsCards
* @brief Base class for graphical card items, providing shared rendering, identity, and interaction logic.
*/
#ifndef ABSTRACTCARDITEM_H
#define ABSTRACTCARDITEM_H
#include "../../game_graphics/board/graphics_item_type.h"
#include "../card_dimensions.h"
#include "arrow_target.h"
#include <libcockatrice/card/printing/exact_card.h>
#include <libcockatrice/utility/card_ref.h>
class PlayerLogic;
class AbstractCardItem : public ArrowTarget
{
Q_OBJECT
protected:
ExactCard exactCard;
int id;
CardRef cardRef;
bool tapped;
bool facedown;
int tapAngle;
QString color;
QColor bgColor;
private:
bool isHovered;
qreal realZValue;
private slots:
void pixmapUpdated();
public slots:
void refreshCardInfo();
signals:
void hovered(AbstractCardItem *card);
void showCardInfoPopup(const QPoint &pos, const CardRef &cardRef);
void deleteCardInfoPopup(QString cardName);
void sigPixmapUpdated();
void cardShiftClicked(QString cardName);
void rightClicked(AbstractCardItem *card, QPoint screenPos);
void playSelected(AbstractCardItem *card);
void playSelectedFaceDown(AbstractCardItem *card);
void hideSelected(AbstractCardItem *card);
void selectionChanged(AbstractCardItem *card, bool selected);
public:
enum
{
Type = typeCard
};
int type() const override
{
return Type;
}
explicit AbstractCardItem(QGraphicsItem *parent = nullptr,
const CardRef &cardRef = {},
PlayerLogic *_owner = nullptr,
int _id = -1);
~AbstractCardItem() override;
QRectF boundingRect() const override;
QPainterPath shape() const override;
QSizeF getTranslatedSize(QPainter *painter) const;
void paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
ExactCard getCard() const
{
return exactCard;
}
const CardInfo &getCardInfo() const;
int getId() const
{
return id;
}
void setId(int _id)
{
id = _id;
}
QString getName() const
{
return cardRef.name;
}
QString getProviderId() const
{
return cardRef.providerId;
}
void setCardRef(const CardRef &_cardRef);
CardRef getCardRef() const
{
return cardRef;
}
qreal getRealZValue() const
{
return realZValue;
}
void setRealZValue(qreal _zValue);
void setHovered(bool _hovered);
bool getIsHovered() const
{
return isHovered;
}
QString getColor() const
{
return color;
}
void setColor(const QString &_color);
bool getTapped() const
{
return tapped;
}
void setTapped(bool _tapped, bool canAnimate = false);
bool getFaceDown() const
{
return facedown;
}
void setFaceDown(bool _facedown);
void processHoverEvent();
void deleteCardInfoPopup()
{
emit deleteCardInfoPopup(cardRef.name);
}
protected:
void transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle);
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
void cacheBgColor();
};
#endif

View file

@ -1,229 +0,0 @@
#include "abstract_counter.h"
#include "../../client/settings/cache_settings.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "translate_counter_name.h"
#include <QAction>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QKeyEvent>
#include <QMenu>
#include <QString>
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
#include <libcockatrice/utility/expression.h>
AbstractCounter::AbstractCounter(CounterState *state,
PlayerLogic *_player,
bool _shownInCounterArea,
bool _useNameForShortcut,
QGraphicsItem *parent)
: QGraphicsItem(parent), player(_player), id(state->getId()), name(state->getName()), value(state->getValue()),
color(state->getColor()), radius(state->getRadius()), useNameForShortcut(_useNameForShortcut),
shownInCounterArea(_shownInCounterArea)
{
setAcceptHoverEvents(true);
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) {
value = newValue;
update();
});
if (player->getPlayerInfo()->getLocalOrJudge()) {
menu = new TearOffMenu(TranslateCounterName::getDisplayName(state->getName()));
aSet = new QAction(this);
connect(aSet, &QAction::triggered, this, &AbstractCounter::setCounter);
menu->addAction(aSet);
menu->addSeparator();
for (int i = 10; i >= -10; --i) {
if (i == 0) {
menu->addSeparator();
continue;
}
auto *a = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this);
if (i == -1) {
aDec = a;
}
if (i == 1) {
aInc = a;
}
a->setData(i);
connect(a, &QAction::triggered, this, &AbstractCounter::incrementCounter);
menu->addAction(a);
}
} else {
menu = nullptr;
}
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&AbstractCounter::refreshShortcuts);
refreshShortcuts();
retranslateUi();
}
AbstractCounter::~AbstractCounter()
{
delete menu;
}
void AbstractCounter::delCounter()
{
if (dialogSemaphore) {
deleteAfterDialog = true;
} else {
deleteLater();
}
}
void AbstractCounter::retranslateUi()
{
if (aSet) {
aSet->setText(tr("&Set counter..."));
}
}
void AbstractCounter::setShortcutsActive()
{
if (!menu || !player->getPlayerInfo()->getLocal()) {
return;
}
ShortcutsSettings &sc = SettingsCache::instance().shortcuts();
shortcutActive = true;
if (name == "life") {
aSet->setShortcuts(sc.getShortcut("Player/aSet"));
aDec->setShortcuts(sc.getShortcut("Player/aDec"));
aInc->setShortcuts(sc.getShortcut("Player/aInc"));
} else if (useNameForShortcut) {
aSet->setShortcuts(sc.getShortcut("Player/aSetCounter_" + name));
aDec->setShortcuts(sc.getShortcut("Player/aDecCounter_" + name));
aInc->setShortcuts(sc.getShortcut("Player/aIncCounter_" + name));
}
}
void AbstractCounter::setShortcutsInactive()
{
if (!menu) {
return;
}
shortcutActive = false;
if (name == "life" || useNameForShortcut) {
aSet->setShortcut(QKeySequence());
aDec->setShortcut(QKeySequence());
aInc->setShortcut(QKeySequence());
}
}
void AbstractCounter::refreshShortcuts()
{
if (shortcutActive) {
setShortcutsActive();
}
}
void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (!isUnderMouse() || !player->getPlayerInfo()->getLocalOrJudge()) {
event->ignore();
return;
}
if (event->button() == Qt::MiddleButton || QApplication::keyboardModifiers() & Qt::ShiftModifier) {
if (menu) {
menu->exec(event->screenPos());
}
} else {
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(event->button() == Qt::LeftButton ? 1 : -1);
player->getPlayerActions()->sendGameCommand(cmd);
}
event->accept();
}
void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
hovered = true;
update();
}
void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
{
hovered = false;
update();
}
void AbstractCounter::incrementCounter()
{
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(static_cast<QAction *>(sender())->data().toInt());
player->getPlayerActions()->sendGameCommand(cmd);
}
void AbstractCounter::setCounter()
{
QWidget *parent = nullptr;
if (auto *view = scene() ? scene()->views().value(0) : nullptr) {
parent = view->window();
}
dialogSemaphore = true;
AbstractCounterDialog dlg(name, QString::number(value), parent);
const int ok = dlg.exec();
dialogSemaphore = false;
if (deleteAfterDialog) {
deleteLater();
return;
}
if (!ok) {
return;
}
Expression exp(value);
Command_SetCounter cmd;
cmd.set_counter_id(id);
cmd.set_value(static_cast<int>(exp.parse(dlg.textValue())));
player->getPlayerActions()->sendGameCommand(cmd);
}
AbstractCounterDialog::AbstractCounterDialog(const QString &name, const QString &value, QWidget *parent)
: QInputDialog(parent)
{
setWindowTitle(tr("Set counter"));
setLabelText(tr("New value for counter '%1':").arg(name));
setTextValue(value);
qApp->installEventFilter(this);
}
bool AbstractCounterDialog::eventFilter(QObject *obj, QEvent *event)
{
Q_UNUSED(obj);
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch (keyEvent->key()) {
case Qt::Key_Up:
changeValue(+1);
return true;
case Qt::Key_Down:
changeValue(-1);
return true;
}
}
return false;
}
void AbstractCounterDialog::changeValue(int diff)
{
bool ok;
int curValue = textValue().toInt(&ok);
if (!ok) {
return;
}
curValue += diff;
setTextValue(QString::number(curValue));
}

View file

@ -1,109 +0,0 @@
/**
* @file abstract_counter.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef COUNTER_H
#define COUNTER_H
#include "../../interface/widgets/menus/tearoff_menu.h"
#include "../player/menu/abstract_player_component.h"
#include "counter_state.h"
#include <QGraphicsItem>
#include <QInputDialog>
class PlayerLogic;
class QAction;
class QKeyEvent;
class QMenu;
class QString;
class AbstractCounter : public QObject, public QGraphicsItem, public AbstractPlayerComponent
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
protected:
PlayerLogic *player;
int id;
QString name;
int value;
QColor color;
int radius;
bool hovered = false;
bool useNameForShortcut;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private:
QAction *aSet = nullptr, *aDec = nullptr, *aInc = nullptr;
TearOffMenu *menu = nullptr;
bool dialogSemaphore = false;
bool deleteAfterDialog = false;
bool shownInCounterArea;
bool shortcutActive = false;
private slots:
void refreshShortcuts();
void incrementCounter();
void setCounter();
public:
AbstractCounter(CounterState *state,
PlayerLogic *player,
bool shownInCounterArea,
bool useNameForShortcut = false,
QGraphicsItem *parent = nullptr);
~AbstractCounter() override;
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
void delCounter();
QMenu *getMenu() const
{
return menu;
}
int getId() const
{
return id;
}
QString getName() const
{
return name;
}
QColor getColor() const
{
return color;
}
int getRadius() const
{
return radius;
}
int getValue() const
{
return value;
}
bool getShownInCounterArea() const
{
return shownInCounterArea;
}
};
class AbstractCounterDialog : public QInputDialog
{
Q_OBJECT
public:
AbstractCounterDialog(const QString &name, const QString &value, QWidget *parent = nullptr);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
void changeValue(int diff);
};
#endif

View file

@ -1,392 +0,0 @@
#define _USE_MATH_DEFINES
#include "arrow_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../game_graphics/zones/card_zone.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "../player/player_target.h"
#include "../z_values.h"
#include "card_item.h"
#include <QDebug>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QtMath>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
#include <libcockatrice/protocol/pb/command_delete_arrow.pb.h>
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h>
ArrowItem::ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem)
: data(std::move(_data)), startItem(_startItem), targetItem(_targetItem)
{
setZValue(ZValues::ARROWS);
auto doUpdate = [this]() {
if (startItem && targetItem) {
updatePath();
}
};
if (startItem) {
connect(startItem, &ArrowTarget::scenePositionChanged, this, doUpdate);
connect(startItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed);
}
if (targetItem) {
connect(targetItem, &ArrowTarget::scenePositionChanged, this, doUpdate);
connect(targetItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed);
}
if (startItem && targetItem) {
updatePath();
}
}
void ArrowItem::onTargetDestroyed()
{
emit requestDeletion(data->creatorId, data->id);
}
void ArrowItem::delArrow()
{
if (targetItem) {
targetItem->setBeingPointedAt(false);
}
deleteLater();
}
void ArrowItem::updatePath()
{
if (!targetItem) {
return;
}
QPointF endPoint = targetItem->mapToScene(
QPointF(targetItem->boundingRect().width() / 2, targetItem->boundingRect().height() / 2));
updatePath(endPoint);
}
void ArrowItem::updatePath(const QPointF &endPoint)
{
const double arrowWidth = 15.0;
const double headWidth = 40.0;
const double headLength =
headWidth / qPow(2, 0.5); // aka headWidth / sqrt (2) but this produces a compile error with MSVC++
const double phi = 15;
if (!startItem) {
return;
}
QPointF startPoint =
startItem->mapToScene(QPointF(startItem->boundingRect().width() / 2, startItem->boundingRect().height() / 2));
QLineF line(startPoint, endPoint);
qreal lineLength = line.length();
prepareGeometryChange();
if (lineLength < 30) {
path = QPainterPath();
} else {
QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength);
QPainterPath centerLine;
centerLine.moveTo(0, 0);
centerLine.quadTo(c, QPointF(lineLength, 0));
double percentage = 1 - headLength / lineLength;
QPointF arrowBodyEndPoint = centerLine.pointAtPercent(percentage);
QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(percentage + 0.001));
qreal alpha = testLine.angle() - 90;
QPointF endPoint1 =
arrowBodyEndPoint + arrowWidth / 2 * QPointF(qCos(alpha * M_PI / 180), -qSin(alpha * M_PI / 180));
QPointF endPoint2 =
arrowBodyEndPoint + arrowWidth / 2 * QPointF(-qCos(alpha * M_PI / 180), qSin(alpha * M_PI / 180));
QPointF point1 =
endPoint1 + (headWidth - arrowWidth) / 2 * QPointF(qCos(alpha * M_PI / 180), -qSin(alpha * M_PI / 180));
QPointF point2 =
endPoint2 + (headWidth - arrowWidth) / 2 * QPointF(-qCos(alpha * M_PI / 180), qSin(alpha * M_PI / 180));
path = QPainterPath(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
path.quadTo(c, endPoint1);
path.lineTo(point1);
path.lineTo(QPointF(lineLength, 0));
path.lineTo(point2);
path.lineTo(endPoint2);
path.quadTo(c, arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
path.lineTo(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
}
setPos(startPoint);
setTransform(QTransform().rotate(-line.angle()));
}
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
QColor paintColor(data->color);
if (fullColor) {
paintColor.setAlpha(200);
} else {
paintColor.setAlpha(150);
}
painter->setBrush(paintColor);
painter->drawPath(path);
}
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (!data->isLocalCreator) {
event->ignore();
return;
}
for (auto *item : scene()->items(event->scenePos())) {
if (qgraphicsitem_cast<CardItem *>(item)) {
event->ignore();
return;
}
}
event->accept();
if (event->button() == Qt::RightButton) {
emit requestDeletion(data->creatorId, data->id);
}
}
// ArrowDragItem
ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _owner->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = _color}),
_startItem,
nullptr),
player(_owner), deleteInPhase(_deleteInPhase)
{
}
void ArrowDragItem::addChildArrow(ArrowDragItem *child)
{
childArrows.append(child);
}
void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (targetLocked || !startItem) {
return;
}
const QPointF endPos = event->scenePos();
ArrowTarget *cursorItem = nullptr;
qreal cursorItemZ = -1;
for (auto *item : scene()->items(endPos)) {
ArrowTarget *candidate = nullptr;
if (auto *card = qgraphicsitem_cast<CardItem *>(item)) {
candidate = card;
} else if (auto *pt = qgraphicsitem_cast<PlayerTarget *>(item)) {
candidate = pt;
}
if (candidate && candidate->zValue() > cursorItemZ) {
cursorItem = candidate;
cursorItemZ = candidate->zValue();
}
}
if (cursorItem != targetItem) {
if (targetItem) {
disconnect(positionConnection);
targetItem->setBeingPointedAt(false);
}
targetItem = cursorItem;
fullColor = (cursorItem != nullptr);
if (cursorItem && cursorItem != startItem) {
cursorItem->setBeingPointedAt(true);
positionConnection =
connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); });
}
}
targetItem ? updatePath() : updatePath(endPos);
update();
for (auto *child : childArrows) {
child->mouseMoveEvent(event);
}
}
void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem) {
return;
}
if (targetItem && targetItem != startItem) {
CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
// For now, we can safely assume that the start item is always a card.
// The target item can be a player as well.
if (!startCard) {
delArrow();
return;
}
CardZoneLogic *startZone = startCard->getZone();
Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(data->color));
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId());
if (auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem)) {
CardZoneLogic *targetZone = targetCard->getZone();
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
} else if (auto *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem)) {
cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId());
} else {
delArrow();
return;
}
// if the card is in hand then we will move the card to stack or table as part of drawing the arrow
if (startZone->getName() == ZoneNames::HAND) {
startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr();
bool playToStack = SettingsCache::instance().getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK))) {
cmd.set_start_zone(ZoneNames::STACK);
} else {
cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
}
}
if (deleteInPhase != 0) {
cmd.set_delete_in_phase(deleteInPhase);
}
player->getPlayerActions()->sendGameCommand(cmd);
}
delArrow();
for (auto *child : childArrows) {
child->mouseReleaseEvent(event);
}
}
// ArrowAttachItem
ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
: ArrowItem(
QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _startItem->getOwner()->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = Qt::green}),
_startItem,
nullptr),
player(_startItem->getOwner())
{
}
void ArrowAttachItem::addChildArrow(ArrowAttachItem *child)
{
childArrows.append(child);
}
void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (targetLocked || !startItem) {
return;
}
const QPointF endPos = event->scenePos();
ArrowTarget *cursorItem = nullptr;
qreal cursorItemZ = -1;
for (auto *item : scene()->items(endPos)) {
if (auto *card = qgraphicsitem_cast<CardItem *>(item)) {
if (card->zValue() > cursorItemZ) {
cursorItem = card;
cursorItemZ = card->zValue();
}
}
}
if (cursorItem != targetItem) {
if (targetItem) {
disconnect(positionConnection);
targetItem->setBeingPointedAt(false);
}
targetItem = cursorItem;
fullColor = (cursorItem != nullptr);
if (cursorItem && cursorItem != startItem) {
cursorItem->setBeingPointedAt(true);
positionConnection =
connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); });
}
}
targetItem ? updatePath() : updatePath(endPos);
update();
for (auto *child : childArrows) {
child->mouseMoveEvent(event);
}
}
void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem) {
return;
}
// Attaching could move startItem under the current cursor position, causing all children to retarget to it right
// before they are processed. Prevent that.
for (auto *child : childArrows) {
child->setTargetLocked(true);
}
if (targetItem && targetItem != startItem) {
auto *startCard = qgraphicsitem_cast<CardItem *>(startItem);
auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
if (startCard && targetCard) {
attachCards(startCard, targetCard);
}
}
delArrow();
for (auto *child : childArrows) {
child->mouseReleaseEvent(event);
}
}
void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard)
{
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) {
return;
}
// move card onto table first if attaching from some other zone
if (startCard->getZone()->getName() != ZoneNames::TABLE) {
player->getPlayerActions()->playCardToTable(startCard, false);
}
Command_AttachCard cmd;
cmd.set_start_zone(ZoneNames::TABLE);
cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetCard->getZone()->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
player->getPlayerActions()->sendGameCommand(cmd);
}

View file

@ -1,109 +0,0 @@
#ifndef ARROWITEM_H
#define ARROWITEM_H
#include "arrow_data.h"
#include "arrow_target.h"
#include <QGraphicsItem>
#include <QPointer>
#include <QSharedPointer>
class CardItem;
class QGraphicsSceneMouseEvent;
class PlayerLogic;
class ArrowItem : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
signals:
void requestDeletion(int creatorId, int id);
private:
QPainterPath path;
protected:
QSharedPointer<const ArrowData> data;
QPointer<ArrowTarget> startItem;
QPointer<ArrowTarget> targetItem;
bool targetLocked = false;
bool fullColor = true;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
public:
ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem);
void onTargetDestroyed();
void delArrow();
void updatePath();
void updatePath(const QPointF &endPoint);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QRectF boundingRect() const override
{
return path.boundingRect();
}
[[nodiscard]] QPainterPath shape() const override
{
return path;
}
[[nodiscard]] int getId() const
{
return data->id;
}
[[nodiscard]] int getCreatorId() const
{
return data->creatorId;
}
[[nodiscard]] ArrowTarget *getStartItem() const
{
return startItem;
}
[[nodiscard]] ArrowTarget *getTargetItem() const
{
return targetItem;
}
void setTargetLocked(bool _targetLocked)
{
targetLocked = _targetLocked;
}
};
class ArrowDragItem : public ArrowItem
{
Q_OBJECT
private:
PlayerLogic *player;
int deleteInPhase;
QList<ArrowDragItem *> childArrows;
QMetaObject::Connection positionConnection;
public:
ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase);
void addChildArrow(ArrowDragItem *child);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
class ArrowAttachItem : public ArrowItem
{
Q_OBJECT
private:
PlayerLogic *player;
QList<ArrowAttachItem *> childArrows;
QMetaObject::Connection positionConnection;
void attachCards(CardItem *startCard, const CardItem *targetCard);
public:
explicit ArrowAttachItem(ArrowTarget *_startItem);
void addChildArrow(ArrowAttachItem *child);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif

View file

@ -1,23 +0,0 @@
#include "arrow_target.h"
#include "../player/player_logic.h"
#include "arrow_item.h"
ArrowTarget::ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent) : AbstractGraphicsItem(parent), owner(_owner)
{
setFlag(ItemSendsScenePositionChanges);
}
void ArrowTarget::setBeingPointedAt(bool _beingPointedAt)
{
beingPointedAt = _beingPointedAt;
update();
}
QVariant ArrowTarget::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemScenePositionHasChanged) {
emit scenePositionChanged();
}
return AbstractGraphicsItem::itemChange(change, value);
}

View file

@ -1,47 +0,0 @@
/**
* @file arrow_target.h
* @ingroup GameGraphics
*/
//! \todo Document this file.
#ifndef ARROWTARGET_H
#define ARROWTARGET_H
#include "../../game_graphics/board/abstract_graphics_item.h"
#include <QList>
class PlayerLogic;
class ArrowItem;
class ArrowTarget : public AbstractGraphicsItem
{
Q_OBJECT
protected:
PlayerLogic *owner;
private:
bool beingPointedAt = false;
signals:
void scenePositionChanged();
public:
explicit ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent = nullptr);
~ArrowTarget() override = default;
[[nodiscard]] PlayerLogic *getOwner() const
{
return owner;
}
void setBeingPointedAt(bool _beingPointedAt);
[[nodiscard]] bool getBeingPointedAt() const
{
return beingPointedAt;
}
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
};
#endif

View file

@ -1,144 +0,0 @@
#include "card_drag_item.h"
#include "../../game_graphics/zones/card_zone.h"
#include "../../game_graphics/zones/table_zone.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../game_scene.h"
#include "card_item.h"
#include <QCursor>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
CardDragItem::CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
bool _forceFaceDown,
AbstractCardDragItem *parentDrag)
: AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), forceFaceDown(_forceFaceDown), occupied(false),
currentZone(0)
{
}
void CardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
AbstractCardDragItem::paint(painter, option, widget);
if (occupied) {
painter->fillPath(shape(), QColor(200, 0, 0, 100));
}
}
void CardDragItem::updatePosition(const QPointF &cursorScenePos)
{
QList<QGraphicsItem *> colliding =
scene()->items(cursorScenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder,
static_cast<GameScene *>(scene())->getViewTransform());
CardZone *cardZone = 0;
ZoneViewZone *zoneViewZone = 0;
for (int i = colliding.size() - 1; i >= 0; i--) {
CardZone *temp = qgraphicsitem_cast<CardZone *>(colliding.at(i));
if (!cardZone) {
cardZone = temp;
}
if (!zoneViewZone) {
zoneViewZone = qobject_cast<ZoneViewZone *>(temp);
}
}
CardZone *cursorZone = 0;
if (zoneViewZone) {
cursorZone = zoneViewZone;
} else if (cardZone) {
cursorZone = cardZone;
}
// Always update the current zone, even if its null, to cancel the drag
// instead of dropping cards into an non-intuitive location.
currentZone = cursorZone;
if (!cursorZone) {
// Avoid the cards getting stuck visually when not over
// any zone.
QPointF newPos = cursorScenePos - hotSpot;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++) {
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos);
}
return;
}
QPointF zonePos = currentZone->scenePos();
QPointF cursorPosInZone = cursorScenePos - zonePos;
// If we are on a Table, we center the card around the cursor, because we
// snap it into place and no longer see it being dragged.
//
// For other zones (where we do display the card under the cursor), we use
// the hotspot to feel like the card was dragged at the corresponding
// position.
TableZone *tableZone = qobject_cast<TableZone *>(cursorZone);
QPointF closestGridPoint;
if (tableZone) {
closestGridPoint = tableZone->closestGridPoint(cursorPosInZone);
} else {
closestGridPoint = cursorPosInZone - hotSpot;
}
QPointF newPos = zonePos + closestGridPoint;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++) {
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos);
bool newOccupied = false;
TableZone *table = qobject_cast<TableZone *>(cursorZone);
if (table) {
if (table->getCardFromCoords(closestGridPoint)) {
newOccupied = true;
}
}
if (newOccupied != occupied) {
occupied = newOccupied;
update();
}
}
}
void CardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
QGraphicsScene *sc = scene();
QPointF sp = pos();
sc->removeItem(this);
QList<CardDragItem *> dragItemList;
CardZoneLogic *startZone = static_cast<CardItem *>(item)->getZone();
if (currentZone && !(static_cast<CardItem *>(item)->getAttachedTo() && (startZone == currentZone->getLogic()))) {
if (!occupied) {
dragItemList.append(this);
}
for (int i = 0; i < childDrags.size(); i++) {
CardDragItem *c = static_cast<CardDragItem *>(childDrags[i]);
if (!occupied &&
!(static_cast<CardItem *>(c->item)->getAttachedTo() && (startZone == currentZone->getLogic())) &&
!c->occupied) {
dragItemList.append(c);
}
sc->removeItem(c);
}
}
if (currentZone) {
currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint());
}
event->accept();
}

View file

@ -1,44 +0,0 @@
/**
* @file card_drag_item.h
* @ingroup GameGraphicsCards
*/
//! \todo Document this file.
#ifndef CARDDRAGITEM_H
#define CARDDRAGITEM_H
#include "abstract_card_drag_item.h"
class CardItem;
class CardDragItem : public AbstractCardDragItem
{
Q_OBJECT
private:
int id;
bool forceFaceDown;
bool occupied;
CardZone *currentZone;
public:
CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
bool _forceFaceDown,
AbstractCardDragItem *parentDrag = 0);
int getId() const
{
return id;
}
bool isForceFaceDown() const
{
return forceFaceDown;
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void updatePosition(const QPointF &cursorScenePos) override;
protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif

View file

@ -1,542 +0,0 @@
#include "card_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../game_graphics/zones/table_zone.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../game_scene.h"
#include "../phase.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "../zones/view_zone_logic.h"
#include "arrow_item.h"
#include "card_drag_item.h"
#include <../../client/settings/card_counter_settings.h>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QMenu>
#include <QPainter>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
CardItem::CardItem(PlayerLogic *_owner,
QGraphicsItem *parent,
const CardRef &cardRef,
int _cardid,
CardZoneLogic *_zone)
: AbstractCardItem(parent, cardRef, _owner, _cardid), state(new CardState(this, _zone)), dragItem(nullptr)
{
owner->addCard(this);
connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, this, [this](int counterId) {
if (state->getCounters().contains(counterId)) {
update();
}
});
}
void CardItem::prepareDelete()
{
if (owner != nullptr) {
if (owner->getGame()->getActiveCard() == this) {
emit owner->requestCardMenuUpdate(nullptr);
owner->getGame()->setActiveCard(nullptr);
}
owner = nullptr;
}
while (!attachedCards.isEmpty()) {
attachedCards.first()->setZone(nullptr); // so that it won't try to call reorganizeCards()
attachedCards.first()->setAttachedTo(nullptr);
}
if (state->getAttachedTo() != nullptr) {
state->getAttachedTo()->removeAttachedCard(this);
state->setAttachedTo(nullptr);
}
}
void CardItem::deleteLater()
{
prepareDelete();
if (scene()) {
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
AbstractCardItem::deleteLater();
}
void CardItem::setZone(CardZoneLogic *_zone)
{
state->setZone(_zone);
}
void CardItem::retranslateUi()
{
}
void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
auto &cardCounterSettings = SettingsCache::instance().cardCounters();
painter->save();
AbstractCardItem::paint(painter, option, widget);
int i = 0;
QMapIterator<int, int> counterIterator(state->getCounters());
while (counterIterator.hasNext()) {
counterIterator.next();
QColor _color = cardCounterSettings.color(counterIterator.key());
paintNumberEllipse(counterIterator.value(), 14, _color, i, state->getCounters().size(), painter);
++i;
}
QSizeF translatedSize = getTranslatedSize(painter);
qreal scaleFactor = translatedSize.width() / boundingRect().width();
if (!state->getPT().isEmpty()) {
painter->save();
transformPainter(painter, translatedSize, tapAngle);
if (!getFaceDown() && state->getPT() == exactCard.getInfo().getPowTough()) {
painter->setPen(Qt::white);
} else {
painter->setPen(QColor(255, 150, 0)); // dark orange
}
painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode);
painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 10 * scaleFactor,
translatedSize.height() - 8 * scaleFactor),
Qt::AlignRight | Qt::AlignBottom, state->getPT());
painter->restore();
}
if (!state->getAnnotation().isEmpty()) {
painter->save();
transformPainter(painter, translatedSize, tapAngle);
painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode);
painter->setPen(Qt::white);
painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor,
translatedSize.height() - 8 * scaleFactor),
Qt::AlignCenter | Qt::TextWrapAnywhere, state->getAnnotation());
painter->restore();
}
if (getBeingPointedAt()) {
painter->fillPath(shape(), QBrush(QColor(255, 0, 0, 100)));
}
if (state->getDoesntUntap()) {
painter->save();
painter->setRenderHint(QPainter::Antialiasing, false);
QPen pen;
pen.setColor(Qt::magenta);
pen.setWidth(0); // Cosmetic pen
painter->setPen(pen);
painter->drawPath(shape());
painter->restore();
}
painter->restore();
}
void CardItem::setAttacking(bool _attacking)
{
state->setAttacking(_attacking);
update();
}
void CardItem::setCounter(int _id, int _value)
{
state->setCounter(_id, _value);
update();
}
void CardItem::setAnnotation(const QString &_annotation)
{
state->setAnnotation(_annotation);
update();
}
void CardItem::setDoesntUntap(bool _doesntUntap)
{
state->setDoesntUntap(_doesntUntap);
update();
}
void CardItem::setPT(const QString &_pt)
{
state->setPT(_pt);
update();
}
void CardItem::setAttachedTo(CardItem *_attachedTo)
{
if (state->getAttachedTo() != nullptr) {
state->getAttachedTo()->removeAttachedCard(this);
}
gridPoint.setX(-1);
state->setAttachedTo(_attachedTo);
if (state->getAttachedTo() != nullptr) {
// If the zone is being torn down, it might already be null by the time a card tries to un-attach all its
// attached cards
if (state->getAttachedTo()->getZone() == nullptr) {
deleteLater();
} else {
emit state->getAttachedTo()->getZone()->cardAdded(this);
state->getAttachedTo()->addAttachedCard(this);
if (state->getZone() != state->getAttachedTo()->getZone()) {
state->getAttachedTo()->getZone()->reorganizeCards();
}
}
} else {
// If the zone is being torn down, it might already be null by the time a card tries to un-attach all its
// attached cards
if (state->getZone() == nullptr) {
deleteLater();
} else {
emit state->getZone()->cardAdded(this);
}
}
if (state->getZone() != nullptr) {
state->getZone()->reorganizeCards();
}
}
/**
* @brief Resets the fields that should be reset after a zone transition
*/
void CardItem::resetState(bool keepAnnotations)
{
state->resetState(keepAnnotations);
attachedCards.clear();
setTapped(false, false);
setDoesntUntap(false);
if (scene()) {
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
update();
}
void CardItem::processCardInfo(const ServerInfo_Card &_info)
{
state->clearCounters();
const int counterListSize = _info.counter_list_size();
for (int i = 0; i < counterListSize; ++i) {
const ServerInfo_CardCounter &counterInfo = _info.counter_list(i);
state->insertCounter(counterInfo.id(), counterInfo.value());
}
setId(_info.id());
setCardRef({QString::fromStdString(_info.name()), QString::fromStdString(_info.provider_id())});
setAttacking(_info.attacking());
setFaceDown(_info.face_down());
setPT(QString::fromStdString(_info.pt()));
setAnnotation(QString::fromStdString(_info.annotation()));
setColor(QString::fromStdString(_info.color()));
setTapped(_info.tapped());
setDestroyOnZoneChange(_info.destroy_on_zone_change());
setDoesntUntap(_info.doesnt_untap());
}
CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown)
{
deleteDragItem();
dragItem = new CardDragItem(this, _id, _pos, forceFaceDown);
dragItem->setVisible(false);
scene()->addItem(dragItem);
dragItem->updatePosition(_scenePos);
dragItem->setVisible(true);
return dragItem;
}
void CardItem::deleteDragItem()
{
if (dragItem) {
dragItem->deleteLater();
}
dragItem = nullptr;
}
void CardItem::drawArrow(const QColor &arrowColor)
{
if (owner->getGame()->getPlayerManager()->isSpectator()) {
return;
}
auto *game = owner->getGame();
PlayerLogic *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer());
int phase = 0; // 0 means to not set the phase
if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) {
int currentPhase = game->getGameState()->getCurrentPhase();
phase = Phases::getLastSubphase(currentPhase) + 1;
}
ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor, phase);
scene()->addItem(arrow);
arrow->grabMouse();
for (const auto &item : scene()->selectedItems()) {
CardItem *card = qgraphicsitem_cast<CardItem *>(item);
if (card == nullptr || card == this) {
continue;
}
if (card->getZone() != state->getZone()) {
continue;
}
ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor, phase);
scene()->addItem(childArrow);
arrow->addChildArrow(childArrow);
}
}
void CardItem::drawAttachArrow()
{
if (owner->getGame()->getPlayerManager()->isSpectator()) {
return;
}
auto *arrow = new ArrowAttachItem(this);
scene()->addItem(arrow);
arrow->grabMouse();
for (const auto &item : scene()->selectedItems()) {
CardItem *card = qgraphicsitem_cast<CardItem *>(item);
if (card == nullptr) {
continue;
}
if (card->getZone() != state->getZone()) {
continue;
}
ArrowAttachItem *childArrow = new ArrowAttachItem(card);
scene()->addItem(childArrow);
arrow->addChildArrow(childArrow);
}
}
void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (event->buttons().testFlag(Qt::RightButton)) {
if ((event->screenPos() - event->buttonDownScreenPos(Qt::RightButton)).manhattanLength() <
2 * QApplication::startDragDistance()) {
return;
}
QColor arrowColor = Qt::red;
if (event->modifiers().testFlag(Qt::ControlModifier)) {
arrowColor = Qt::yellow;
} else if (event->modifiers().testFlag(Qt::AltModifier)) {
arrowColor = Qt::blue;
} else if (event->modifiers().testFlag(Qt::ShiftModifier)) {
arrowColor = Qt::green;
}
drawArrow(arrowColor);
} else if (event->buttons().testFlag(Qt::LeftButton)) {
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
2 * QApplication::startDragDistance()) {
return;
}
if (const ZoneViewZoneLogic *view = qobject_cast<const ZoneViewZoneLogic *>(state->getZone())) {
if (view->getRevealZone() && !view->getWriteableRevealZone()) {
return;
}
} else if (!owner->getPlayerInfo()->getLocalOrJudge()) {
return;
}
bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier);
// Use the buttonDownPos to align the hot spot with the position when
// the user originally clicked
createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), forceFaceDown);
dragItem->grabMouse();
int childIndex = 0;
for (const auto &item : scene()->selectedItems()) {
CardItem *card = static_cast<CardItem *>(item);
if ((card == this) || (card->getZone() != state->getZone())) {
continue;
}
++childIndex;
QPointF childPos;
if (state->getZone()->getHasCardAttr()) {
childPos = card->pos() - pos();
} else {
childPos = QPointF(childIndex * CardDimensions::WIDTH_HALF_F, 0);
}
CardDragItem *drag =
new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem);
drag->setPos(dragItem->pos() + childPos);
scene()->addItem(drag);
}
}
setCursor(Qt::OpenHandCursor);
}
void CardItem::playCard(bool faceDown)
{
// Do nothing if the card belongs to another player
if (!owner->getPlayerInfo()->getLocalOrJudge()) {
return;
}
TableZoneLogic *tz = qobject_cast<TableZoneLogic *>(state->getZone());
if (tz) {
emit tz->toggleTapped();
} else {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
if (faceDown) {
emit playSelectedFaceDown(this);
} else {
emit playSelected(this);
}
} else {
state->getZone()->getPlayer()->getPlayerActions()->playCard(this, faceDown);
}
}
}
QVariantList CardItem::parsePT(const QString &pt)
{
QVariantList ptList = QVariantList();
if (!pt.isEmpty()) {
int sep = pt.indexOf('/');
if (sep == 0) {
ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string
} else {
int start = 0;
for (;;) {
QString item = pt.mid(start, sep - start);
if (item.isEmpty()) {
ptList.append(QVariant(QString()));
} else if (item[0] == '+') {
ptList.append(QVariant(item.mid(1).toInt())); // add as int
} else if (item[0] == '-') {
ptList.append(QVariant(item.toInt())); // add as int
} else {
ptList.append(QVariant(item)); // add as qstring
}
if (sep == -1) {
break;
}
start = sep + 1;
sep = pt.indexOf('/', start);
}
}
}
return ptList;
}
/**
* @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone
* is nullptr.
*/
static bool isUnwritableRevealZone(CardZoneLogic *zone)
{
if (auto *view = qobject_cast<ZoneViewZoneLogic *>(zone)) {
return view->getRevealZone() && !view->getWriteableRevealZone();
}
return false;
}
/**
* This method is called when a "click to play" is done on the card.
* This is either triggered by a single click or double click, depending on the settings.
*
* @param shiftHeld if the shift key was held during the click
*/
void CardItem::handleClickedToPlay(bool shiftHeld)
{
if (isUnwritableRevealZone(state->getZone())) {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
emit hideSelected(this);
} else {
state->getZone()->removeCard(this);
}
} else {
playCard(shiftHeld);
}
}
void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::RightButton && owner != nullptr) {
emit rightClicked(this, event->screenPos());
return;
}
if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
(!SettingsCache::instance().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
}
if (owner != nullptr) { // cards without owner will be deleted
setCursor(Qt::OpenHandCursor);
}
AbstractCardItem::mouseReleaseEvent(event);
}
void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->modifiers() != Qt::AltModifier) && (event->buttons() == Qt::LeftButton) &&
(SettingsCache::instance().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
}
event->accept();
}
bool CardItem::animationEvent()
{
int rotation = ROTATION_DEGREES_PER_FRAME;
bool animationIncomplete = true;
if (!tapped) {
rotation *= -1;
}
tapAngle += rotation;
if (tapped && (tapAngle > 90)) {
tapAngle = 90;
animationIncomplete = false;
}
if (!tapped && (tapAngle < 0)) {
tapAngle = 0;
animationIncomplete = false;
}
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(tapAngle)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
setHovered(false);
update();
return animationIncomplete;
}
QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if ((change == ItemSelectedHasChanged) && owner != nullptr) {
bool selected = value.toBool();
if (selected) {
owner->getGame()->setActiveCard(this);
}
emit selectionChanged(this, selected);
}
return AbstractCardItem::itemChange(change, value);
}

View file

@ -1,175 +0,0 @@
/**
* @file card_item.h
* @ingroup GameGraphicsCards
*/
//! \todo Document this file.
#ifndef CARDITEM_H
#define CARDITEM_H
#include "../zones/card_zone_logic.h"
#include "abstract_card_item.h"
#include "card_state.h"
#include <libcockatrice/network/server/remote/game/server_card.h>
#include <libcockatrice/utility/trice_limits.h>
class CardDatabase;
class CardDragItem;
class CardZone;
class ServerInfo_Card;
class PlayerLogic;
class QAction;
class QColor;
const int ROTATION_DEGREES_PER_FRAME = 10;
class CardItem : public AbstractCardItem
{
Q_OBJECT
private:
CardState *state;
QPoint gridPoint;
CardDragItem *dragItem;
QList<CardItem *> attachedCards;
void prepareDelete();
void handleClickedToPlay(bool shiftHeld);
public slots:
void deleteLater();
public:
enum
{
Type = typeCard
};
[[nodiscard]] int type() const override
{
return Type;
}
explicit CardItem(PlayerLogic *_owner,
QGraphicsItem *parent = nullptr,
const CardRef &cardRef = {},
int _cardid = -1,
CardZoneLogic *_zone = nullptr);
void retranslateUi();
[[nodiscard]] CardState *getState() const
{
return state;
}
[[nodiscard]] CardZoneLogic *getZone() const
{
return state->getZone();
}
void setZone(CardZoneLogic *_zone);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QPoint getGridPoint() const
{
return gridPoint;
}
void setGridPoint(const QPoint &_gridPoint)
{
gridPoint = _gridPoint;
}
[[nodiscard]] QPoint getGridPos() const
{
return gridPoint;
}
[[nodiscard]] PlayerLogic *getOwner() const
{
return owner;
}
void setOwner(PlayerLogic *_owner)
{
owner = _owner;
}
[[nodiscard]] bool getAttacking() const
{
return state->getAttacking();
}
void setAttacking(bool _attacking);
[[nodiscard]] const QMap<int, int> &getCounters() const
{
return state->getCounters();
}
void setCounter(int _id, int _value);
[[nodiscard]] QString getAnnotation() const
{
return state->getAnnotation();
}
void setAnnotation(const QString &_annotation);
[[nodiscard]] bool getDoesntUntap() const
{
return state->getDoesntUntap();
}
void setDoesntUntap(bool _doesntUntap);
[[nodiscard]] QString getPT() const
{
return state->getPT();
}
void setPT(const QString &_pt);
[[nodiscard]] bool getDestroyOnZoneChange() const
{
return state->getDestroyOnZoneChange();
}
void setDestroyOnZoneChange(bool _destroy)
{
state->setDestroyOnZoneChange(_destroy);
}
[[nodiscard]] CardItem *getAttachedTo() const
{
return state->getAttachedTo();
}
void setAttachedTo(CardItem *_attachedTo);
void addAttachedCard(CardItem *card)
{
attachedCards.append(card);
}
void removeAttachedCard(CardItem *card)
{
attachedCards.removeOne(card);
}
[[nodiscard]] const QList<CardItem *> &getAttachedCards() const
{
return attachedCards;
}
void resetState(bool keepAnnotations = false);
void processCardInfo(const ServerInfo_Card &_info);
bool animationEvent();
CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown);
void deleteDragItem();
void drawArrow(const QColor &arrowColor);
void drawAttachArrow();
void playCard(bool faceDown);
/**
* @brief Parses a string representing a p/t in order to extract the values from it.
*
* If the string contains '/', the string will be split at the '/' and each side will be parsed separately,
* which means the result list will have two elements.
*
* If '/' is not found, then the entire string is parsed together, which means the result list will
* have a single element.
*
* If either side of the split is empty, there will also only be a single element in the result list.
*
* This function will attempt to parse each substring as an int first, handling plus and minus prefixes.
* If successful, it will put the parsed value into the QVariant as an int.
* If failed, it will just put the substring into the QVariant as a QString.
*
* @param pt The p/t string
* @return A QVariantList that can contain one or two elements, where each QVariant can be either int or QString
*/
static QVariantList parsePT(const QString &pt);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
};
#endif

View file

@ -1,6 +1,6 @@
#include "card_list.h"
#include "card_item.h"
#include "../../game_graphics/board/card_item.h"
#include <QDebug>
#include <algorithm>

View file

@ -1,39 +0,0 @@
#include "counter_general.h"
#include "../../game_graphics/board/abstract_graphics_item.h"
#include "../../interface/pixel_map_generator.h"
#include <QPainter>
GeneralCounter::GeneralCounter(CounterState *state, PlayerLogic *player, bool useNameForShortcut, QGraphicsItem *parent)
: AbstractCounter(state, player, true, useNameForShortcut, parent)
{
setCacheMode(DeviceCoordinateCache);
}
QRectF GeneralCounter::boundingRect() const
{
return QRectF(0, 0, radius * 2, radius * 2);
}
void GeneralCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
QRectF mapRect = painter->combinedTransform().mapRect(boundingRect());
int translatedHeight = mapRect.size().height();
qreal scaleFactor = translatedHeight / boundingRect().height();
QPixmap pixmap = CounterPixmapGenerator::generatePixmap(translatedHeight, name, hovered);
painter->save();
resetPainterTransform(painter);
painter->drawPixmap(QPoint(0, 0), pixmap);
if (value) {
QFont f("Serif");
f.setPixelSize(qMax((int)(radius * scaleFactor), 10));
f.setWeight(QFont::Bold);
painter->setPen(Qt::black);
painter->setFont(f);
painter->drawText(mapRect, Qt::AlignCenter, QString::number(value));
}
painter->restore();
}

View file

@ -1,25 +0,0 @@
/**
* @file counter_general.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef COUNTER_GENERAL_H
#define COUNTER_GENERAL_H
#include "abstract_counter.h"
class GeneralCounter : public AbstractCounter
{
Q_OBJECT
public:
GeneralCounter(CounterState *state,
PlayerLogic *player,
bool useNameForShortcut = false,
QGraphicsItem *parent = nullptr);
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
};
#endif

View file

@ -1,11 +0,0 @@
#include "translate_counter_name.h"
const QMap<QString, QString> TranslateCounterName::translated = {
{"life", QT_TRANSLATE_NOOP("TranslateCounterName", "Life")},
{"w", QT_TRANSLATE_NOOP("TranslateCounterName", "White")},
{"u", QT_TRANSLATE_NOOP("TranslateCounterName", "Blue")},
{"b", QT_TRANSLATE_NOOP("TranslateCounterName", "Black")},
{"r", QT_TRANSLATE_NOOP("TranslateCounterName", "Red")},
{"g", QT_TRANSLATE_NOOP("TranslateCounterName", "Green")},
{"x", QT_TRANSLATE_NOOP("TranslateCounterName", "Colorless")},
{"storm", QT_TRANSLATE_NOOP("TranslateCounterName", "Other")}};

View file

@ -1,30 +0,0 @@
/**
* @file translate_counter_name.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef TRANSLATECOUNTERNAME_H
#define TRANSLATECOUNTERNAME_H
#include <QString>
#include <QtCore>
class TranslateCounterName
{
Q_DECLARE_TR_FUNCTIONS(TranslateCounterName)
static const QMap<QString, QString> translated;
public:
static QString getDisplayName(const QString &name)
{
if (translated.contains(name)) {
return tr(translated[name].toLatin1());
} else {
return name;
}
}
};
#endif // TRANSLATECOUNTERNAME_H

View file

@ -1,29 +0,0 @@
#ifndef CARD_DIMENSIONS_H
#define CARD_DIMENSIONS_H
#include <QtGlobal>
/**
* @file card_dimensions.h
* @brief Canonical card dimension constants for layout and Z-value calculations.
*
* These values represent the logical pixel dimensions of a standard card graphic.
* They are used throughout the game scene for layout, rendering, and Z-value computation.
*/
namespace CardDimensions
{
/** @brief Card width in pixels. */
constexpr int WIDTH = 72;
/** @brief Card height in pixels. */
constexpr int HEIGHT = 102;
/** @brief Pre-converted for floating-point contexts (Z-value calculations). */
constexpr qreal WIDTH_F = static_cast<qreal>(WIDTH);
constexpr qreal HEIGHT_F = static_cast<qreal>(HEIGHT);
/** @brief Half-dimensions for centering and rotation transforms. */
constexpr qreal WIDTH_HALF_F = WIDTH_F / 2;
constexpr qreal HEIGHT_HALF_F = HEIGHT_F / 2;
} // namespace CardDimensions
#endif // CARD_DIMENSIONS_H

View file

@ -1,556 +0,0 @@
#include "deck_view.h"
#include "../../client/settings/cache_settings.h"
#include "../../interface/theme_manager.h"
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QMouseEvent>
#include <QtMath>
#include <algorithm>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item,
const QPointF &_hotSpot,
AbstractCardDragItem *parentDrag)
: AbstractCardDragItem(_item, _hotSpot, parentDrag), currentZone(0)
{
}
void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos)
{
QList<QGraphicsItem *> colliding = scene()->items(cursorScenePos);
DeckViewCardContainer *cursorZone = 0;
for (int i = colliding.size() - 1; i >= 0; i--) {
if ((cursorZone = qgraphicsitem_cast<DeckViewCardContainer *>(colliding.at(i)))) {
break;
}
}
if (!cursorZone) {
return;
}
currentZone = cursorZone;
QPointF newPos = cursorScenePos;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++) {
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos);
}
}
void DeckViewCardDragItem::handleDrop(DeckViewCardContainer *target)
{
auto *card = static_cast<DeckViewCard *>(item);
auto *start = static_cast<DeckViewCardContainer *>(item->parentItem());
start->removeCard(card);
target->addCard(card);
card->setParentItem(target);
}
void DeckViewCardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
auto *sc = static_cast<DeckViewScene *>(scene());
sc->removeItem(this);
if (currentZone) {
handleDrop(currentZone);
for (int i = 0; i < childDrags.size(); i++) {
auto *c = static_cast<DeckViewCardDragItem *>(childDrags[i]);
c->handleDrop(currentZone);
sc->removeItem(c);
}
sc->updateContents();
}
event->accept();
}
DeckViewCard::DeckViewCard(QGraphicsItem *parent, const CardRef &cardRef, const QString &_originZone)
: AbstractCardItem(parent, cardRef, 0, -1), originZone(_originZone), dragItem(0)
{
setAcceptHoverEvents(true);
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
update();
});
}
DeckViewCard::~DeckViewCard()
{
delete dragItem;
}
void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
AbstractCardItem::paint(painter, option, widget);
painter->save();
QPen pen;
pen.setWidth(3);
pen.setJoinStyle(Qt::MiterJoin);
pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red);
painter->setPen(pen);
qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CardDimensions::WIDTH_F - 3) : 0.0;
painter->drawRoundedRect(QRectF(1.5, 1.5, CardDimensions::WIDTH_F - 3, CardDimensions::HEIGHT_F - 3), cardRadius,
cardRadius);
painter->restore();
}
void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
2 * QApplication::startDragDistance()) {
return;
}
if (static_cast<DeckViewScene *>(scene())->getLocked()) {
return;
}
delete dragItem;
dragItem = new DeckViewCardDragItem(this, event->pos());
scene()->addItem(dragItem);
dragItem->updatePosition(event->scenePos());
dragItem->grabMouse();
QList<QGraphicsItem *> sel = scene()->selectedItems();
int j = 0;
for (int i = 0; i < sel.size(); i++) {
auto *c = static_cast<DeckViewCard *>(sel.at(i));
if (c == this) {
continue;
}
++j;
auto childPos = QPointF(j * CardDimensions::WIDTH_HALF_F, 0);
auto *drag = new DeckViewCardDragItem(c, childPos, dragItem);
drag->setPos(dragItem->pos() + childPos);
scene()->addItem(drag);
}
setCursor(Qt::OpenHandCursor);
}
void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
{
if (static_cast<DeckViewScene *>(scene())->getLocked()) {
return;
}
if (event->button() == Qt::LeftButton) {
QList<MoveCard_ToZone> result;
QList<QGraphicsItem *> sel = scene()->selectedItems();
for (int i = 0; i < sel.size(); i++) {
auto *c = static_cast<DeckViewCard *>(sel.at(i));
auto *zone = static_cast<DeckViewCardContainer *>(c->parentItem());
MoveCard_ToZone m;
m.set_card_name(c->getName().toStdString());
m.set_start_zone(zone->getName().toStdString());
if (zone->getName() == DECK_ZONE_MAIN) {
m.set_target_zone(DECK_ZONE_SIDE);
} else if (zone->getName() == DECK_ZONE_SIDE) {
m.set_target_zone(DECK_ZONE_MAIN);
} else { // Trying to move from another zone
m.set_target_zone(zone->getName().toStdString());
}
result.append(m);
}
deckViewScene->applySideboardPlan(result);
deckViewScene->rearrangeItems();
emit deckViewScene->sideboardPlanChanged();
}
}
void DeckViewCard::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
event->accept();
processHoverEvent();
}
DeckViewCardContainer::DeckViewCardContainer(const QString &_name) : QGraphicsItem(), name(_name), width(0), height(0)
{
setCacheMode(DeviceCoordinateCache);
}
QRectF DeckViewCardContainer::boundingRect() const
{
return QRectF(0, 0, width, height);
}
void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
qreal totalTextWidth = getCardTypeTextWidth();
painter->fillRect(boundingRect(), themeManager->getBgBrush(ThemeManager::Table));
painter->setPen(QColor(255, 255, 255, 100));
painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
painter->setPen(QColor(Qt::white));
QFont f("Serif");
f.setStyleHint(QFont::Serif);
f.setPixelSize(24);
f.setWeight(QFont::Bold);
painter->setFont(f);
painter->drawText(10, 0, width - 20, separatorY, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine,
InnerDecklistNode::visibleNameFromName(name) + QString(": %1").arg(cards.size()));
f.setPixelSize(16);
painter->setFont(f);
QList<QString> cardTypeList = cardsByType.uniqueKeys();
qreal yUntilNow = separatorY + paddingY;
for (int i = 0; i < cardTypeList.size(); ++i) {
if (i != 0) {
painter->setPen(QColor(255, 255, 255, 100));
painter->drawLine(QPointF(0, yUntilNow - paddingY / 2), QPointF(width, yUntilNow - paddingY / 2));
}
qreal thisRowHeight = CardDimensions::HEIGHT_F * currentRowsAndCols[i].first;
QRectF textRect(0, yUntilNow, totalTextWidth, thisRowHeight);
yUntilNow += thisRowHeight + paddingY;
QString displayString = QString("%1\n(%2)").arg(cardTypeList[i]).arg(cardsByType.count(cardTypeList[i]));
painter->setPen(Qt::white);
painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignVCenter, displayString);
}
}
void DeckViewCardContainer::addCard(DeckViewCard *card)
{
cards.append(card);
cardsByType.insert(card->getCard().isEmpty() ? "" : card->getCardInfo().getMainCardType(), card);
}
void DeckViewCardContainer::removeCard(DeckViewCard *card)
{
cards.removeOne(card);
cardsByType.remove(card->getCard().isEmpty() ? "" : card->getCardInfo().getMainCardType(), card);
}
QList<QPair<int, int>> DeckViewCardContainer::getRowsAndCols() const
{
QList<QPair<int, int>> result;
QList<QString> cardTypeList = cardsByType.uniqueKeys();
for (int i = 0; i < cardTypeList.size(); ++i) {
result.append(QPair<int, int>(1, cardsByType.count(cardTypeList[i])));
}
return result;
}
int DeckViewCardContainer::getCardTypeTextWidth() const
{
QFont f("Serif");
f.setStyleHint(QFont::Serif);
f.setPixelSize(16);
f.setWeight(QFont::Bold);
QFontMetrics fm(f);
int maxCardTypeWidth = 0;
for (const auto &key : cardsByType.keys()) {
int cardTypeWidth = fm.size(Qt::TextSingleLine, key).width();
maxCardTypeWidth = qMax(maxCardTypeWidth, cardTypeWidth);
}
return maxCardTypeWidth + 15;
}
QSizeF DeckViewCardContainer::calculateBoundingRect(const QList<QPair<int, int>> &rowsAndCols) const
{
qreal totalHeight = 0;
qreal totalWidth = 0;
// Calculate space needed for cards
for (int i = 0; i < rowsAndCols.size(); ++i) {
totalHeight += CardDimensions::HEIGHT_F * rowsAndCols[i].first + paddingY;
if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth) {
totalWidth = CardDimensions::WIDTH_F * rowsAndCols[i].second;
}
}
return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY);
}
bool DeckViewCardContainer::sortCardsByName(DeckViewCard *c1, DeckViewCard *c2)
{
if (c1 && c2) {
return c1->getName() < c2->getName();
}
return false;
}
void DeckViewCardContainer::rearrangeItems(const QList<QPair<int, int>> &rowsAndCols)
{
currentRowsAndCols = rowsAndCols;
qreal yUntilNow = separatorY + paddingY;
qreal x = (qreal)getCardTypeTextWidth();
for (int i = 0; i < rowsAndCols.size(); ++i) {
const int tempRows = rowsAndCols[i].first;
const int tempCols = rowsAndCols[i].second;
QList<QString> cardTypeList = cardsByType.uniqueKeys();
QList<DeckViewCard *> row = cardsByType.values(cardTypeList[i]);
std::sort(row.begin(), row.end(), DeckViewCardContainer::sortCardsByName);
for (int j = 0; j < row.size(); ++j) {
DeckViewCard *card = row[j];
card->setPos(x + (j % tempCols) * CardDimensions::WIDTH_F,
yUntilNow + (j / tempCols) * CardDimensions::HEIGHT_F);
}
yUntilNow += tempRows * CardDimensions::HEIGHT_F + paddingY;
}
prepareGeometryChange();
QSizeF bRect = calculateBoundingRect(rowsAndCols);
width = bRect.width();
height = bRect.height();
}
void DeckViewCardContainer::setWidth(qreal _width)
{
prepareGeometryChange();
width = _width;
update();
}
DeckViewScene::DeckViewScene(QObject *parent) : QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0)
{
}
DeckViewScene::~DeckViewScene()
{
clearContents();
delete deck;
}
void DeckViewScene::clearContents()
{
QMapIterator<QString, DeckViewCardContainer *> i(cardContainers);
while (i.hasNext()) {
delete i.next().value();
}
cardContainers.clear();
}
void DeckViewScene::setDeck(const DeckList &_deck)
{
if (deck) {
delete deck;
}
deck = new DeckList(_deck.writeToString_Native());
rebuildTree();
applySideboardPlan(deck->getCurrentSideboardPlan());
rearrangeItems();
}
void DeckViewScene::rebuildTree()
{
clearContents();
if (!deck) {
return;
}
for (auto *currentZone : deck->getZoneNodes()) {
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
if (!container) {
container = new DeckViewCardContainer(currentZone->getName());
cardContainers.insert(currentZone->getName(), container);
addItem(container);
}
for (int j = 0; j < currentZone->size(); j++) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard) {
continue;
}
for (int k = 0; k < currentCard->getNumber(); ++k) {
auto *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName());
container->addCard(newCard);
emit newCardAdded(newCard);
}
}
}
}
void DeckViewScene::applySideboardPlan(const QList<MoveCard_ToZone> &plan)
{
for (int i = 0; i < plan.size(); ++i) {
const MoveCard_ToZone &m = plan[i];
DeckViewCardContainer *start = cardContainers.value(QString::fromStdString(m.start_zone()));
DeckViewCardContainer *target = cardContainers.value(QString::fromStdString(m.target_zone()));
if (!start || !target) {
continue;
}
DeckViewCard *card = 0;
const QList<DeckViewCard *> &cardList = start->getCards();
for (int j = 0; j < cardList.size(); ++j) {
if (cardList[j]->getName() == QString::fromStdString(m.card_name())) {
card = cardList[j];
break;
}
}
if (!card) {
continue;
}
start->removeCard(card);
target->addCard(card);
card->setParentItem(target);
}
}
void DeckViewScene::rearrangeItems()
{
const int spacing = CardDimensions::HEIGHT / 3;
QList<DeckViewCardContainer *> contList = cardContainers.values();
// Initialize space requirements
QList<QList<QPair<int, int>>> rowsAndColsList;
QList<QList<int>> cardCountList;
for (int i = 0; i < contList.size(); ++i) {
QList<QPair<int, int>> rowsAndCols = contList[i]->getRowsAndCols();
rowsAndColsList.append(rowsAndCols);
cardCountList.append(QList<int>());
for (int j = 0; j < rowsAndCols.size(); ++j) {
cardCountList[i].append(rowsAndCols[j].second);
}
}
qreal totalHeight, totalWidth;
for (;;) {
// Calculate total size before this iteration
totalHeight = -spacing;
totalWidth = 0;
for (int i = 0; i < contList.size(); ++i) {
QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]);
totalHeight += contSize.height() + spacing;
if (contSize.width() > totalWidth) {
totalWidth = contSize.width();
}
}
// We're done when the aspect ratio shifts from too high to too low.
if (totalWidth / totalHeight <= optimalAspectRatio) {
break;
}
// Find category with highest column count
int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0;
for (int i = 0; i < rowsAndColsList.size(); ++i) {
for (int j = 0; j < rowsAndColsList[i].size(); ++j) {
if (rowsAndColsList[i][j].second > maxCols) {
maxIndex1 = i;
maxIndex2 = j;
maxCols = rowsAndColsList[i][j].second;
}
}
}
// Add row to category
const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first;
const int maxCardCount = cardCountList[maxIndex1][maxIndex2];
rowsAndColsList[maxIndex1][maxIndex2] =
QPair<int, int>(maxRows + 1, (int)qCeil((qreal)maxCardCount / (qreal)(maxRows + 1)));
}
totalHeight = -spacing;
for (int i = 0; i < contList.size(); ++i) {
DeckViewCardContainer *c = contList[i];
c->rearrangeItems(rowsAndColsList[i]);
c->setPos(0, totalHeight + spacing);
totalHeight += c->boundingRect().height() + spacing;
}
totalWidth = totalHeight * optimalAspectRatio;
for (int i = 0; i < contList.size(); ++i) {
contList[i]->setWidth(totalWidth);
}
setSceneRect(QRectF(0, 0, totalWidth, totalHeight));
}
void DeckViewScene::updateContents()
{
rearrangeItems();
emit sideboardPlanChanged();
}
QList<MoveCard_ToZone> DeckViewScene::getSideboardPlan() const
{
QList<MoveCard_ToZone> result;
QMapIterator<QString, DeckViewCardContainer *> containerIterator(cardContainers);
while (containerIterator.hasNext()) {
DeckViewCardContainer *cont = containerIterator.next().value();
const QList<DeckViewCard *> cardList = cont->getCards();
for (int i = 0; i < cardList.size(); ++i) {
if (cardList[i]->getOriginZone() != cont->getName()) {
MoveCard_ToZone m;
m.set_card_name(cardList[i]->getName().toStdString());
m.set_start_zone(cardList[i]->getOriginZone().toStdString());
m.set_target_zone(cont->getName().toStdString());
result.append(m);
}
}
}
return result;
}
void DeckViewScene::resetSideboardPlan()
{
rebuildTree();
rearrangeItems();
}
DeckView::DeckView(QWidget *parent) : QGraphicsView(parent)
{
deckViewScene = new DeckViewScene(this);
setBackgroundBrush(QBrush(QColor(0, 0, 0)));
setDragMode(RubberBandDrag);
setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing /* | QPainter::SmoothPixmapTransform*/);
setScene(deckViewScene);
connect(deckViewScene, &DeckViewScene::sceneRectChanged, this, &DeckView::updateSceneRect);
connect(deckViewScene, &DeckViewScene::newCardAdded, this, &DeckView::newCardAdded);
connect(deckViewScene, &DeckViewScene::sideboardPlanChanged, this, &DeckView::sideboardPlanChanged);
}
void DeckView::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
deckViewScene->setOptimalAspectRatio((qreal)width() / (qreal)height());
deckViewScene->rearrangeItems();
}
void DeckView::updateSceneRect(const QRectF &rect)
{
fitInView(rect, Qt::KeepAspectRatio);
}
void DeckView::setDeck(const DeckList &_deck)
{
deckViewScene->setDeck(_deck);
}
void DeckView::clearDeck()
{
deckViewScene->clearContents();
}
void DeckView::resetSideboardPlan()
{
deckViewScene->resetSideboardPlan();
}

View file

@ -1,170 +0,0 @@
/**
* @file deck_view.h
* @ingroup Lobby
*/
//! \todo Document this file.
#ifndef DECKVIEW_H
#define DECKVIEW_H
#include "../board/abstract_card_drag_item.h"
#include <QGraphicsView>
#include <QMap>
#include <libcockatrice/protocol/pb/move_card_to_zone.pb.h>
class DeckList;
class InnerDecklistNode;
class CardInfo;
class DeckViewCardContainer;
class DeckViewCardDragItem;
class MoveCardToZone;
class DeckViewCard : public AbstractCardItem
{
private:
QString originZone;
DeckViewCardDragItem *dragItem;
public:
explicit DeckViewCard(QGraphicsItem *parent = nullptr,
const CardRef &cardRef = {},
const QString &_originZone = QString());
~DeckViewCard() override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] const QString &getOriginZone() const
{
return originZone;
}
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
};
class DeckViewCardDragItem : public AbstractCardDragItem
{
private:
DeckViewCardContainer *currentZone;
void handleDrop(DeckViewCardContainer *target);
public:
DeckViewCardDragItem(DeckViewCard *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0);
void updatePosition(const QPointF &cursorScenePos) override;
protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
class DeckViewCardContainer : public QGraphicsItem
{
private:
static const int separatorY = 20;
static const int paddingY = 10;
static bool sortCardsByName(DeckViewCard *c1, DeckViewCard *c2);
QString name;
QList<DeckViewCard *> cards;
QMultiMap<QString, DeckViewCard *> cardsByType;
QList<QPair<int, int>> currentRowsAndCols;
qreal width, height;
[[nodiscard]] int getCardTypeTextWidth() const;
public:
enum
{
Type = typeDeckViewCardContainer
};
[[nodiscard]] int type() const override
{
return Type;
}
explicit DeckViewCardContainer(const QString &_name);
[[nodiscard]] QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void addCard(DeckViewCard *card);
void removeCard(DeckViewCard *card);
[[nodiscard]] const QList<DeckViewCard *> &getCards() const
{
return cards;
}
[[nodiscard]] const QString &getName() const
{
return name;
}
void setWidth(qreal _width);
[[nodiscard]] QList<QPair<int, int>> getRowsAndCols() const;
[[nodiscard]] QSizeF calculateBoundingRect(const QList<QPair<int, int>> &rowsAndCols) const;
void rearrangeItems(const QList<QPair<int, int>> &rowsAndCols);
};
class DeckViewScene : public QGraphicsScene
{
Q_OBJECT
signals:
void newCardAdded(AbstractCardItem *card);
void sideboardPlanChanged();
private:
bool locked;
DeckList *deck;
QMap<QString, DeckViewCardContainer *> cardContainers;
qreal optimalAspectRatio;
void rebuildTree();
public:
explicit DeckViewScene(QObject *parent = nullptr);
~DeckViewScene() override;
void setLocked(bool _locked)
{
locked = _locked;
}
[[nodiscard]] bool getLocked() const
{
return locked;
}
void clearContents();
void setDeck(const DeckList &_deck);
void setOptimalAspectRatio(qreal _optimalAspectRatio)
{
optimalAspectRatio = _optimalAspectRatio;
}
void rearrangeItems();
void updateContents();
[[nodiscard]] QList<MoveCard_ToZone> getSideboardPlan() const;
void resetSideboardPlan();
void applySideboardPlan(const QList<MoveCard_ToZone> &plan);
};
class DeckView : public QGraphicsView
{
Q_OBJECT
private:
DeckViewScene *deckViewScene;
protected:
void resizeEvent(QResizeEvent *event) override;
public slots:
void updateSceneRect(const QRectF &rect);
signals:
void newCardAdded(AbstractCardItem *card);
void sideboardPlanChanged();
public:
explicit DeckView(QWidget *parent = nullptr);
void setDeck(const DeckList &_deck);
void clearDeck();
void setLocked(bool _locked)
{
deckViewScene->setLocked(_locked);
}
[[nodiscard]] QList<MoveCard_ToZone> getSideboardPlan() const
{
return deckViewScene->getSideboardPlan();
}
void mouseDoubleClickEvent(QMouseEvent *event) override;
void resetSideboardPlan();
};
#endif

View file

@ -1,418 +0,0 @@
#include "deck_view_container.h"
#include "../../client/settings/cache_settings.h"
#include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../../interface/deck_loader/deck_loader.h"
#include "../../interface/widgets/dialogs/dlg_load_deck.h"
#include "../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h"
#include "../../interface/widgets/dialogs/dlg_load_deck_from_website.h"
#include "../../interface/widgets/dialogs/dlg_load_remote_deck.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "deck_view.h"
#include <QMessageBox>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
#include <libcockatrice/protocol/pb/command_ready_start.pb.h>
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/trice_limits.h>
ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false)
{
}
void ToggleButton::paintEvent(QPaintEvent *event)
{
QPushButton::paintEvent(event);
QPainter painter(this);
QPen pen;
pen.setWidth(3);
pen.setJoinStyle(Qt::MiterJoin);
pen.setColor(state ? Qt::green : Qt::red);
painter.setPen(pen);
painter.drawRect(QRect(1, 1, width() - 3, height() - 3));
}
void ToggleButton::setState(bool _state)
{
state = _state;
emit stateChanged();
update();
}
DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
: QWidget(nullptr), visualDeckStorageWidget(nullptr), parentGame(parent), playerId(_playerId)
{
loadLocalButton = new QPushButton;
loadRemoteButton = new QPushButton;
loadFromClipboardButton = new QPushButton;
loadFromWebsiteButton = new QPushButton;
unloadDeckButton = new QPushButton;
readyStartButton = new ToggleButton;
forceStartGameButton = new QPushButton;
sideboardLockButton = new ToggleButton;
connect(loadLocalButton, &QPushButton::clicked, this, &DeckViewContainer::loadLocalDeck);
connect(loadRemoteButton, &QPushButton::clicked, this, &DeckViewContainer::loadRemoteDeck);
connect(loadFromClipboardButton, &QPushButton::clicked, this, &DeckViewContainer::loadFromClipboard);
connect(loadFromWebsiteButton, &QPushButton::clicked, this, &DeckViewContainer::loadFromWebsite);
connect(readyStartButton, &QPushButton::clicked, this, &DeckViewContainer::readyStart);
connect(unloadDeckButton, &QPushButton::clicked, this, &DeckViewContainer::unloadDeck);
connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart);
connect(sideboardLockButton, &QPushButton::clicked, this, &DeckViewContainer::sideboardLockButtonClicked);
connect(sideboardLockButton, &ToggleButton::stateChanged, this, &DeckViewContainer::updateSideboardLockButtonText);
auto *buttonHBox = new QHBoxLayout;
buttonHBox->addWidget(loadLocalButton);
buttonHBox->addWidget(loadRemoteButton);
buttonHBox->addWidget(loadFromClipboardButton);
buttonHBox->addWidget(loadFromWebsiteButton);
buttonHBox->addWidget(unloadDeckButton);
buttonHBox->addWidget(readyStartButton);
buttonHBox->addWidget(sideboardLockButton);
buttonHBox->addWidget(forceStartGameButton);
buttonHBox->setContentsMargins(11, 0, 11, 0);
buttonHBox->addStretch();
deckView = new DeckView;
connect(deckView, &DeckView::newCardAdded, this, &DeckViewContainer::newCardAdded);
connect(deckView, &DeckView::sideboardPlanChanged, this, &DeckViewContainer::sideboardPlanChanged);
deckViewLayout = new QVBoxLayout;
deckViewLayout->addLayout(buttonHBox);
deckViewLayout->addWidget(deckView);
deckViewLayout->setContentsMargins(0, 0, 0, 0);
setLayout(deckViewLayout);
retranslateUi();
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&DeckViewContainer::refreshShortcuts);
refreshShortcuts();
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageInGameChanged, this,
&DeckViewContainer::setVisualDeckStorageExists);
switchToDeckSelectView();
}
/**
* Creates the VDS widget and inserts it into the layout. No-ops if the widget already exists
*/
void DeckViewContainer::tryCreateVisualDeckStorageWidget()
{
if (visualDeckStorageWidget) {
return;
}
visualDeckStorageWidget = new VisualDeckStorageWidget(this);
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckLoadRequested, this,
&DeckViewContainer::loadDeckFromFile);
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::openDeckEditor, parentGame, &TabGame::openDeckEditor);
deckViewLayout->addWidget(visualDeckStorageWidget);
}
void DeckViewContainer::retranslateUi()
{
loadLocalButton->setText(tr("Load deck..."));
loadRemoteButton->setText(tr("Load remote deck..."));
loadFromClipboardButton->setText(tr("Load from clipboard..."));
loadFromWebsiteButton->setText(tr("Load from website..."));
unloadDeckButton->setText(tr("Unload deck"));
readyStartButton->setText(tr("Ready to start"));
forceStartGameButton->setText(tr("Force start"));
updateSideboardLockButtonText();
}
static void setVisibility(QPushButton *button, bool visible)
{
button->setHidden(!visible);
button->setEnabled(visible);
}
void DeckViewContainer::switchToDeckSelectView()
{
if (SettingsCache::instance().getVisualDeckStorageInGame()) {
deckView->setHidden(true);
tryCreateVisualDeckStorageWidget();
visualDeckStorageWidget->setHidden(false);
} else {
deckView->setHidden(false);
if (visualDeckStorageWidget) {
visualDeckStorageWidget->setHidden(true);
}
}
deckViewLayout->update();
setVisibility(loadLocalButton, true);
setVisibility(loadRemoteButton, !parentGame->getGame()->getGameState()->getIsLocalGame());
setVisibility(loadFromClipboardButton, true);
setVisibility(loadFromWebsiteButton, true);
setVisibility(unloadDeckButton, false);
setVisibility(readyStartButton, false);
setVisibility(sideboardLockButton, false);
setVisibility(forceStartGameButton, false);
readyStartButton->setState(false);
sideboardLockButton->setState(false);
deckView->setLocked(true);
sendReadyStartCommand(false);
}
void DeckViewContainer::switchToDeckLoadedView()
{
deckView->setHidden(false);
if (visualDeckStorageWidget) {
visualDeckStorageWidget->setHidden(true);
}
deckViewLayout->update();
setVisibility(loadLocalButton, false);
setVisibility(loadRemoteButton, false);
setVisibility(loadFromClipboardButton, false);
setVisibility(loadFromWebsiteButton, false);
setVisibility(unloadDeckButton, true);
setVisibility(readyStartButton, true);
setVisibility(sideboardLockButton, true);
if (parentGame->getGame()->isHost()) {
setVisibility(forceStartGameButton, true);
}
}
void DeckViewContainer::updateSideboardLockButtonText()
{
if (sideboardLockButton->getState()) {
sideboardLockButton->setText(tr("Sideboard unlocked"));
} else {
sideboardLockButton->setText(tr("Sideboard locked"));
}
// setting text on a button removes its shortcut
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton"));
}
void DeckViewContainer::refreshShortcuts()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton"));
loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton"));
loadFromClipboardButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromClipboardButton"));
unloadDeckButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/unloadDeckButton"));
readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton"));
sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton"));
forceStartGameButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/forceStartGameButton"));
}
/**
* Updates the existence of the embedded Visual Deck Storage, destroying or creating it if needed.
* Note that this change is temporary; the VDS may get recreated when the view transitions to the deck select state,
* depending on current settings.
*/
void DeckViewContainer::setVisualDeckStorageExists(bool exists)
{
if (exists) {
// view mode state isn't stored in a field, so we determine state by checking the button
if (loadLocalButton->isEnabled()) {
// We only need to handle the setting changing while in deck select state; tryCreate already gets called
// when switching from deck loaded to deck select state
tryCreateVisualDeckStorageWidget();
visualDeckStorageWidget->setHidden(false);
deckView->setHidden(true);
}
} else {
if (visualDeckStorageWidget) {
visualDeckStorageWidget->deleteLater();
visualDeckStorageWidget = nullptr;
}
deckView->setHidden(false);
}
deckViewLayout->update();
}
void DeckViewContainer::unloadDeck()
{
deckView->clearDeck();
switchToDeckSelectView();
}
void DeckViewContainer::loadLocalDeck()
{
DlgLoadDeck dialog(this);
if (!dialog.exec()) {
return;
}
loadDeckFromFile(dialog.selectedFiles().at(0));
}
void DeckViewContainer::loadDeckFromFile(const QString &filePath)
{
DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(filePath);
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromFile(filePath, fmt, true);
if (!deckOpt) {
QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded."));
return;
}
loadDeckFromDeckList(deckOpt.value().deckList);
}
void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck)
{
QString deckString = deck.writeToString_Native();
if (deckString.length() > MAX_FILE_LENGTH) {
QMessageBox::critical(this, tr("Error"), tr("Deck is greater than maximum file size."));
return;
}
Command_DeckSelect cmd;
cmd.set_deck(deckString.toStdString());
PendingCommand *pend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(cmd);
connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(pend, playerId);
}
void DeckViewContainer::loadRemoteDeck()
{
DlgLoadRemoteDeck dlg(parentGame->getGame()->getClientForPlayer(playerId), this);
if (dlg.exec()) {
Command_DeckSelect cmd;
cmd.set_deck_id(dlg.getDeckId());
PendingCommand *pend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(cmd);
connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(pend, playerId);
}
}
void DeckViewContainer::loadFromClipboard()
{
auto dlg = DlgLoadDeckFromClipboard(this);
if (!dlg.exec()) {
return;
}
DeckList deck = dlg.getDeckList();
loadDeckFromDeckList(deck);
}
void DeckViewContainer::loadFromWebsite()
{
auto dlg = DlgLoadDeckFromWebsite(this);
if (!dlg.exec()) {
return;
}
DeckList deck = dlg.getDeck();
loadDeckFromDeckList(deck);
}
void DeckViewContainer::deckSelectFinished(const Response &r)
{
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
DeckList newDeck = DeckList(QString::fromStdString(resp.deck()));
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(newDeck.getCardRefList()));
setDeck(newDeck);
switchToDeckLoadedView();
}
void DeckViewContainer::readyStart()
{
sendReadyStartCommand(!readyStartButton->getState());
}
void DeckViewContainer::forceStart()
{
const auto msg = tr("Are you sure you want to force start?\nThis will kick all non-ready players from the game.");
const auto res =
QMessageBox::question(this, tr("Cockatrice"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (res == QMessageBox::No) {
return;
}
Command_ReadyStart cmd;
cmd.set_force_start(true);
cmd.set_ready(true);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
}
void DeckViewContainer::sideboardLockButtonClicked()
{
Command_SetSideboardLock cmd;
cmd.set_locked(sideboardLockButton->getState());
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
}
void DeckViewContainer::sideboardPlanChanged()
{
Command_SetSideboardPlan cmd;
const QList<MoveCard_ToZone> &newPlan = deckView->getSideboardPlan();
for (const auto &i : newPlan) {
cmd.add_move_list()->CopyFrom(i);
}
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
}
/**
* Sends the basic ReadyStart command.
*/
void DeckViewContainer::sendReadyStartCommand(bool ready)
{
Command_ReadyStart cmd;
cmd.set_ready(ready);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
}
/**
* Updates the buttons to make the client-side ready state match the given state.
*
* Notably, this method only updates the client and *does not* send a ReadyStart command to the server.
* This method is intended to be called upon receiving the response from a ReadyStart command.
*/
void DeckViewContainer::setReadyStart(bool ready)
{
readyStartButton->setState(ready);
deckView->setLocked(ready || !sideboardLockButton->getState());
sideboardLockButton->setEnabled(!readyStartButton->getState() && readyStartButton->isEnabled());
}
/**
* Sends a ReadyStart command with ready=true to the server
*/
void DeckViewContainer::readyAndUpdate()
{
sendReadyStartCommand(true);
}
void DeckViewContainer::setSideboardLocked(bool locked)
{
sideboardLockButton->setState(!locked);
deckView->setLocked(readyStartButton->getState() || !sideboardLockButton->getState());
if (locked) {
deckView->resetSideboardPlan();
}
}
void DeckViewContainer::setDeck(const DeckList &deck)
{
deckView->setDeck(deck);
switchToDeckLoadedView();
}

View file

@ -1,96 +0,0 @@
/**
* @file deck_view_container.h
* @ingroup Lobby
*/
//! \todo Document this file.
#ifndef DECK_VIEW_CONTAINER_H
#define DECK_VIEW_CONTAINER_H
#include "../../interface/deck_loader/deck_loader.h"
#include <QPushButton>
class QVBoxLayout;
class AbstractCardItem;
class VisualDeckStorageWidget;
class DeckPreviewWidget;
class Response;
class TabGame;
class DeckView;
/**
* Custom QButton implementation in order to have the red/green toggling square around the button
*/
class ToggleButton : public QPushButton
{
Q_OBJECT
private:
bool state;
signals:
void stateChanged();
public:
explicit ToggleButton(QWidget *parent = nullptr);
[[nodiscard]] bool getState() const
{
return state;
}
void setState(bool _state);
protected:
void paintEvent(QPaintEvent *event) override;
};
/**
* This widget contains the deck selection view that is used before a game begins.
*/
class DeckViewContainer : public QWidget
{
Q_OBJECT
private:
QVBoxLayout *deckViewLayout;
QPushButton *loadLocalButton, *loadRemoteButton, *loadFromClipboardButton, *loadFromWebsiteButton;
QPushButton *unloadDeckButton, *forceStartGameButton;
ToggleButton *readyStartButton, *sideboardLockButton;
DeckView *deckView;
VisualDeckStorageWidget *visualDeckStorageWidget;
TabGame *parentGame;
int playerId;
void tryCreateVisualDeckStorageWidget();
void sendReadyStartCommand(bool ready);
private slots:
void switchToDeckSelectView();
void switchToDeckLoadedView();
void loadLocalDeck();
void loadRemoteDeck();
void loadFromClipboard();
void loadFromWebsite();
void unloadDeck();
void readyStart();
void forceStart();
void deckSelectFinished(const Response &r);
void sideboardPlanChanged();
void sideboardLockButtonClicked();
void updateSideboardLockButtonText();
void refreshShortcuts();
signals:
void newCardAdded(AbstractCardItem *card);
void notIdle();
public:
DeckViewContainer(int _playerId, TabGame *parent);
void retranslateUi();
void setReadyStart(bool ready);
void readyAndUpdate();
void setSideboardLocked(bool locked);
void setDeck(const DeckList &deck);
void setVisualDeckStorageExists(bool exists);
public slots:
void loadDeckFromFile(const QString &filePath);
void loadDeckFromDeckList(const DeckList &deck);
};
#endif // DECK_VIEW_CONTAINER_H

View file

@ -1,66 +0,0 @@
#include "tabbed_deck_view_container.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "deck_view.h"
TabbedDeckViewContainer::TabbedDeckViewContainer(int _playerId, TabGame *parent)
: QTabWidget(nullptr), playerId(_playerId), parentGame(parent)
{
setTabsClosable(true);
connect(this, &QTabWidget::tabCloseRequested, this, &TabbedDeckViewContainer::closeTab);
playerDeckView = new DeckViewContainer(playerId, parentGame);
int playerTabIndex = addTab(playerDeckView, "Your Deck");
tabBar()->setTabButton(playerTabIndex, QTabBar::RightSide, nullptr);
updateTabBarVisibility();
}
void TabbedDeckViewContainer::addOpponentDeckView(const DeckList &opponentDeck, int opponentId, QString opponentName)
{
if (opponentDeckViews.contains(opponentId)) {
opponentDeckViews[opponentId]->setDeck(opponentDeck);
} else {
auto *opponentDeckView = new DeckView(this);
connect(opponentDeckView, &DeckView::newCardAdded, playerDeckView, &DeckViewContainer::newCardAdded);
opponentDeckView->setDeck(opponentDeck);
addTab(opponentDeckView, QString("%1's Deck").arg(opponentName));
opponentDeckViews.insert(opponentId, opponentDeckView);
}
updateTabBarVisibility();
}
void TabbedDeckViewContainer::closeTab(int index)
{
QWidget *widgetToClose = widget(index);
// Prevent removing the player tab
if (widgetToClose == playerDeckView) {
return;
}
// Remove it from map if it's an opponent
auto it = opponentDeckViews.begin();
while (it != opponentDeckViews.end()) {
if (it.value() == widgetToClose) {
it = opponentDeckViews.erase(it);
} else {
++it;
}
}
removeTab(index);
widgetToClose->deleteLater();
updateTabBarVisibility();
}
void TabbedDeckViewContainer::updateTabBarVisibility()
{
if (tabBar()->count() <= 1) {
tabBar()->hide();
} else {
tabBar()->show();
}
}

View file

@ -1,29 +0,0 @@
/**
* @file tabbed_deck_view_container.h
* @ingroup Lobby
*/
//! \todo Document this file.
#ifndef TABBED_DECK_VIEW_CONTAINER_H
#define TABBED_DECK_VIEW_CONTAINER_H
#include "deck_view_container.h"
#include <QTabWidget>
class TabbedDeckViewContainer : public QTabWidget
{
Q_OBJECT
public:
explicit TabbedDeckViewContainer(int _playerId, TabGame *parent);
void closeTab(int index);
void updateTabBarVisibility();
void addOpponentDeckView(const DeckList &opponentDeck, int opponentId, QString opponentName);
int playerId;
TabGame *parentGame;
DeckViewContainer *playerDeckView;
QMap<int, DeckView *> opponentDeckViews;
};
#endif // TABBED_DECK_VIEW_CONTAINER_H

View file

@ -1,249 +0,0 @@
#include "dlg_create_token.h"
#include "../../client/settings/cache_settings.h"
#include "../../interface/widgets/cards/card_info_picture_widget.h"
#include "../../main.h"
#include <QCheckBox>
#include <QCloseEvent>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QRadioButton>
#include <QTreeView>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/models/database/card_database_model.h>
#include <libcockatrice/models/database/token/token_display_model.h>
#include <libcockatrice/utility/trice_limits.h>
DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent)
: QDialog(parent), predefinedTokens(_predefinedTokens)
{
pic = new CardInfoPictureWidget();
pic->setObjectName("pic");
nameLabel = new QLabel(tr("&Name:"));
nameEdit = new QLineEdit(tr("Token"));
nameEdit->setMaxLength(MAX_NAME_LENGTH);
QTimer::singleShot(100, this, [=, this]() {
nameEdit->selectAll();
nameEdit->setFocus();
});
connect(nameEdit, &QLineEdit::textChanged, this, &DlgCreateToken::updateSearch);
nameLabel->setBuddy(nameEdit);
colorLabel = new QLabel(tr("C&olor:"));
colorEdit = new QComboBox;
colorEdit->addItem(tr("white"), QChar('w'));
colorEdit->addItem(tr("blue"), QChar('u'));
colorEdit->addItem(tr("black"), QChar('b'));
colorEdit->addItem(tr("red"), QChar('r'));
colorEdit->addItem(tr("green"), QChar('g'));
colorEdit->addItem(tr("multicolor"), QChar('m'));
colorEdit->addItem(tr("colorless"), QChar());
colorLabel->setBuddy(colorEdit);
ptLabel = new QLabel(tr("&P/T:"));
ptEdit = new QLineEdit;
ptEdit->setMaxLength(MAX_NAME_LENGTH);
ptLabel->setBuddy(ptEdit);
annotationLabel = new QLabel(tr("&Annotation:"));
annotationEdit = new QLineEdit;
annotationEdit->setMaxLength(MAX_NAME_LENGTH);
annotationLabel->setBuddy(annotationEdit);
destroyCheckBox = new QCheckBox(tr("&Destroy token when it leaves the table"));
destroyCheckBox->setChecked(true);
faceDownCheckBox = new QCheckBox(tr("Create face-down (Only hides name)"));
connect(faceDownCheckBox, &QCheckBox::toggled, this, &DlgCreateToken::faceDownCheckBoxToggled);
auto *grid = new QGridLayout;
grid->addWidget(nameLabel, 0, 0);
grid->addWidget(nameEdit, 0, 1);
grid->addWidget(colorLabel, 1, 0);
grid->addWidget(colorEdit, 1, 1);
grid->addWidget(ptLabel, 2, 0);
grid->addWidget(ptEdit, 2, 1);
grid->addWidget(annotationLabel, 3, 0);
grid->addWidget(annotationEdit, 3, 1);
grid->addWidget(destroyCheckBox, 4, 0, 1, 2);
grid->addWidget(faceDownCheckBox, 5, 0, 1, 2);
auto *tokenDataGroupBox = new QGroupBox(tr("Token data"));
tokenDataGroupBox->setLayout(grid);
cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
cardDatabaseDisplayModel = new TokenDisplayModel(this);
cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel);
chooseTokenFromAllRadioButton = new QRadioButton(tr("Show &all tokens"));
connect(chooseTokenFromAllRadioButton, &QRadioButton::toggled, this, &DlgCreateToken::actChooseTokenFromAll);
chooseTokenFromDeckRadioButton = new QRadioButton(tr("Show tokens from this &deck"));
connect(chooseTokenFromDeckRadioButton, &QRadioButton::toggled, this, &DlgCreateToken::actChooseTokenFromDeck);
QByteArray deckHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState();
chooseTokenView = new QTreeView;
chooseTokenView->setModel(cardDatabaseDisplayModel);
chooseTokenView->setUniformRowHeights(true);
chooseTokenView->setRootIsDecorated(false);
chooseTokenView->setAlternatingRowColors(true);
chooseTokenView->setSortingEnabled(true);
chooseTokenView->sortByColumn(0, Qt::AscendingOrder);
chooseTokenView->resizeColumnToContents(0);
chooseTokenView->setWordWrap(true);
if (!deckHeaderState.isNull()) {
chooseTokenView->header()->restoreState(deckHeaderState);
}
chooseTokenView->header()->setStretchLastSection(false);
chooseTokenView->header()->hideSection(1); // Sets
chooseTokenView->header()->hideSection(2); // Mana Cost
chooseTokenView->header()->setSectionResizeMode(5, QHeaderView::ResizeToContents); // Color(s)
connect(chooseTokenView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
&DlgCreateToken::tokenSelectionChanged);
connect(chooseTokenView, &QTreeView::doubleClicked, this, &DlgCreateToken::actOk);
if (predefinedTokens.isEmpty()) {
chooseTokenFromAllRadioButton->setChecked(true);
chooseTokenFromDeckRadioButton->setDisabled(true); // No tokens in deck = no need for option
} else {
chooseTokenFromDeckRadioButton->setChecked(true);
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
}
auto *tokenChooseLayout = new QVBoxLayout;
tokenChooseLayout->addWidget(chooseTokenFromAllRadioButton);
tokenChooseLayout->addWidget(chooseTokenFromDeckRadioButton);
tokenChooseLayout->addWidget(chooseTokenView);
auto *tokenChooseGroupBox = new QGroupBox(tr("Choose token from list"));
tokenChooseGroupBox->setLayout(tokenChooseLayout);
auto *hbox = new QGridLayout;
hbox->addWidget(pic, 0, 0, 1, 1);
hbox->addWidget(tokenDataGroupBox, 1, 0, 1, 1);
hbox->addWidget(tokenChooseGroupBox, 0, 1, 2, 1);
hbox->setColumnStretch(1, 1);
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateToken::actOk);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateToken::actReject);
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(hbox);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Create token"));
resize(600, 500);
restoreGeometry(SettingsCache::instance().layouts().getTokenDialogGeometry());
}
void DlgCreateToken::closeEvent(QCloseEvent *event)
{
event->accept();
SettingsCache::instance().layouts().setTokenDialogGeometry(saveGeometry());
}
void DlgCreateToken::faceDownCheckBoxToggled(bool checked)
{
if (checked) {
colorEdit->setCurrentIndex(6);
colorEdit->setEnabled(false);
ptEdit->clear();
ptEdit->clearFocus();
ptEdit->setEnabled(false);
} else {
colorEdit->setEnabled(true);
ptEdit->setEnabled(true);
annotationEdit->setEnabled(true);
}
}
void DlgCreateToken::tokenSelectionChanged(const QModelIndex &current, const QModelIndex & /*previous*/)
{
const QModelIndex realIndex = cardDatabaseDisplayModel->mapToSource(current);
CardInfoPtr cardInfo;
if (current.row() >= 0) {
cardInfo = cardDatabaseModel->getCard(realIndex.row());
}
if (cardInfo) {
updateSearchFieldWithoutUpdatingFilter(cardInfo->getName());
const QChar cardColor = cardInfo->getColorChar();
colorEdit->setCurrentIndex(colorEdit->findData(cardColor, Qt::UserRole, Qt::MatchFixedString));
ptEdit->setText(cardInfo->getPowTough());
if (SettingsCache::instance().getAnnotateTokens()) {
annotationEdit->setText(cardInfo->getText());
}
} else {
nameEdit->setText("");
colorEdit->setCurrentIndex(colorEdit->findData(QString(), Qt::UserRole, Qt::MatchFixedString));
ptEdit->setText("");
annotationEdit->setText("");
}
pic->setCard(CardDatabaseManager::query()->getPreferredCard(cardInfo));
}
void DlgCreateToken::updateSearchFieldWithoutUpdatingFilter(const QString &newValue) const
{
nameEdit->blockSignals(true);
nameEdit->setText(newValue);
nameEdit->blockSignals(false);
}
void DlgCreateToken::updateSearch(const QString &search)
{
cardDatabaseDisplayModel->setCardName(search);
}
void DlgCreateToken::actChooseTokenFromAll(bool checked)
{
if (checked) {
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>());
}
}
void DlgCreateToken::actChooseTokenFromDeck(bool checked)
{
if (checked) {
cardDatabaseDisplayModel->setCardNameSet(QSet<QString>(predefinedTokens.begin(), predefinedTokens.end()));
}
}
void DlgCreateToken::actOk()
{
SettingsCache::instance().layouts().setTokenDialogGeometry(saveGeometry());
accept();
}
void DlgCreateToken::actReject()
{
SettingsCache::instance().layouts().setTokenDialogGeometry(saveGeometry());
reject();
}
TokenInfo DlgCreateToken::getTokenInfo() const
{
return {.name = nameEdit->text(),
.color = colorEdit->itemData(colorEdit->currentIndex()).toString(),
.pt = ptEdit->text(),
.annotation = annotationEdit->text(),
.destroy = destroyCheckBox->isChecked(),
.faceDown = faceDownCheckBox->isChecked(),
.providerId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(nameEdit->text())};
}

View file

@ -1,70 +0,0 @@
/**
* @file dlg_create_token.h
* @ingroup GameDialogs
*/
//! \todo Document this file.
#ifndef DLG_CREATETOKEN_H
#define DLG_CREATETOKEN_H
#include <QDialog>
class QLabel;
class QLineEdit;
class QComboBox;
class QCheckBox;
class QPushButton;
class QRadioButton;
class QCloseEvent;
class QTreeView;
class DeckList;
class CardDatabaseModel;
class TokenDisplayModel;
class CardInfoPictureWidget;
struct TokenInfo
{
QString name;
QString color;
QString pt;
QString annotation;
bool destroy = true;
bool faceDown = false;
QString providerId;
};
class DlgCreateToken : public QDialog
{
Q_OBJECT
public:
explicit DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent = nullptr);
[[nodiscard]] TokenInfo getTokenInfo() const;
protected:
void closeEvent(QCloseEvent *event) override;
private slots:
void faceDownCheckBoxToggled(bool checked);
void tokenSelectionChanged(const QModelIndex &current, const QModelIndex &previous);
void updateSearch(const QString &search);
void actChooseTokenFromAll(bool checked);
void actChooseTokenFromDeck(bool checked);
void actOk();
void actReject();
private:
CardDatabaseModel *cardDatabaseModel;
TokenDisplayModel *cardDatabaseDisplayModel;
QStringList predefinedTokens;
QLabel *nameLabel, *colorLabel, *ptLabel, *annotationLabel;
QComboBox *colorEdit;
QLineEdit *nameEdit, *ptEdit, *annotationEdit;
QCheckBox *destroyCheckBox;
QCheckBox *faceDownCheckBox;
QRadioButton *chooseTokenFromAllRadioButton, *chooseTokenFromDeckRadioButton;
CardInfoPictureWidget *pic;
QTreeView *chooseTokenView;
void updateSearchFieldWithoutUpdatingFilter(const QString &newValue) const;
};
#endif

View file

@ -1,134 +0,0 @@
#include "dlg_move_top_cards_until.h"
#include <QDialogButtonBox>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QSpinBox>
#include <QString>
#include <QVBoxLayout>
#include <QWidget>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/filters/filter_string.h>
DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, const MoveTopCardsUntilOptions &options) : QDialog(parent)
{
exprLabel = new QLabel(tr("Card name (or search expressions):"));
exprComboBox = new QComboBox(this);
exprComboBox->setFocus();
exprComboBox->setEditable(true);
exprComboBox->setInsertPolicy(QComboBox::InsertAtTop);
exprComboBox->insertItems(0, options.exprs);
exprLabel->setBuddy(exprComboBox);
numberOfHitsLabel = new QLabel(tr("Number of hits:"));
numberOfHitsEdit = new QSpinBox(this);
numberOfHitsEdit->setRange(1, 99);
numberOfHitsEdit->setValue(options.numberOfHits);
numberOfHitsLabel->setBuddy(numberOfHitsEdit);
auto *grid = new QGridLayout;
grid->addWidget(numberOfHitsLabel, 0, 0);
grid->addWidget(numberOfHitsEdit, 0, 1);
autoPlayCheckBox = new QCheckBox(tr("Auto play hits"));
autoPlayCheckBox->setChecked(options.autoPlay);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgMoveTopCardsUntil::validateAndAccept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(exprLabel);
mainLayout->addWidget(exprComboBox);
mainLayout->addItem(grid);
mainLayout->addWidget(autoPlayCheckBox);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Put top cards on stack until..."));
}
/**
* @brief Checks if a card matching the expr exists in the card database.
*
* @returns true if a card matching the expression exists.
*/
static bool matchExistsInDb(const FilterString &filterString)
{
const auto *cardDatabase = CardDatabaseManager::getInstance();
const auto &allCards = cardDatabase->getCardList();
const auto it = std::find_if(allCards.begin(), allCards.end(),
[&filterString](const CardInfoPtr &card) { return filterString.check(card); });
return it != allCards.end();
}
/**
* @brief Validates that a card matching the expr exists in the card database.
* If no match is found, then pop up a window to warn the user, giving them a chance to back out.
*
* @returns whether to proceed with the action
*/
bool DlgMoveTopCardsUntil::validateMatchExists(const FilterString &filterString)
{
if (matchExistsInDb(filterString)) {
return true;
}
const auto msg = tr("No cards matching the search expression exists in the card database. Proceed anyways?");
const auto res =
QMessageBox::warning(this, tr("Cockatrice"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (res == QMessageBox::No) {
return false;
}
return true;
}
void DlgMoveTopCardsUntil::validateAndAccept()
{
auto movingCardsUntilFilter = FilterString(exprComboBox->currentText());
if (!movingCardsUntilFilter.valid()) {
QMessageBox::warning(this, tr("Invalid filter"), movingCardsUntilFilter.error(), QMessageBox::Ok);
return;
}
if (!validateMatchExists(movingCardsUntilFilter)) {
return;
}
// move currently selected text to top of history list
if (exprComboBox->currentIndex() != 0) {
QString currentExpr = exprComboBox->currentText();
exprComboBox->removeItem(exprComboBox->currentIndex());
exprComboBox->insertItem(0, currentExpr);
exprComboBox->setCurrentIndex(0);
}
accept();
}
QString DlgMoveTopCardsUntil::getExpr() const
{
return exprComboBox->currentText();
}
MoveTopCardsUntilOptions DlgMoveTopCardsUntil::getOptions() const
{
return {.exprs = getExprs(),
.numberOfHits = numberOfHitsEdit->text().toInt(),
.autoPlay = autoPlayCheckBox->isChecked()};
}
QStringList DlgMoveTopCardsUntil::getExprs() const
{
QStringList exprs;
for (int i = 0; i < exprComboBox->count(); ++i) {
exprs.append(exprComboBox->itemText(i));
}
return exprs;
}

View file

@ -1,47 +0,0 @@
/**
* @file dlg_move_top_cards_until.h
* @ingroup GameDialogs
*/
//! \todo Document this file.
#ifndef DLG_MOVE_TOP_CARDS_UNTIL_H
#define DLG_MOVE_TOP_CARDS_UNTIL_H
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLabel>
#include <QSpinBox>
class FilterString;
struct MoveTopCardsUntilOptions
{
QStringList exprs = {};
int numberOfHits = 1;
bool autoPlay = false;
};
class DlgMoveTopCardsUntil : public QDialog
{
Q_OBJECT
QLabel *exprLabel, *numberOfHitsLabel;
QComboBox *exprComboBox;
QSpinBox *numberOfHitsEdit;
QDialogButtonBox *buttonBox;
QCheckBox *autoPlayCheckBox;
void validateAndAccept();
bool validateMatchExists(const FilterString &filterString);
[[nodiscard]] QStringList getExprs() const;
public:
explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr, const MoveTopCardsUntilOptions &options = {});
[[nodiscard]] QString getExpr() const;
[[nodiscard]] MoveTopCardsUntilOptions getOptions() const;
};
#endif // DLG_MOVE_TOP_CARDS_UNTIL_H

View file

@ -1,51 +0,0 @@
#include "dlg_roll_dice.h"
#include <QDialogButtonBox>
#include <QLabel>
#include <QSpinBox>
#include <QVBoxLayout>
#include <QWidget>
#include <libcockatrice/utility/trice_limits.h>
DlgRollDice::DlgRollDice(QWidget *parent) : QDialog(parent)
{
numberOfSidesLabel = new QLabel(tr("Number of sides:"));
numberOfSidesEdit = new QSpinBox(this);
numberOfSidesEdit->setValue(DEFAULT_NUMBER_SIDES_DIE);
numberOfSidesEdit->setRange(MINIMUM_DIE_SIDES, MAXIMUM_DIE_SIDES);
numberOfSidesEdit->setFocus();
numberOfSidesLabel->setBuddy(numberOfSidesEdit);
numberOfDiceLabel = new QLabel(tr("Number of dice:"));
numberOfDiceEdit = new QSpinBox(this);
numberOfDiceEdit->setValue(DEFAULT_NUMBER_DICE_TO_ROLL);
numberOfDiceEdit->setRange(MINIMUM_DICE_TO_ROLL, MAXIMUM_DICE_TO_ROLL);
numberOfDiceLabel->setBuddy(numberOfDiceEdit);
auto *grid = new QGridLayout;
grid->addWidget(numberOfSidesLabel, 0, 0);
grid->addWidget(numberOfSidesEdit, 0, 1);
grid->addWidget(numberOfDiceLabel, 1, 0);
grid->addWidget(numberOfDiceEdit, 1, 1);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto *mainLayout = new QVBoxLayout;
mainLayout->addItem(grid);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Roll Dice"));
}
uint DlgRollDice::getDieSideCount() const
{
return numberOfSidesEdit->text().toUInt();
}
uint DlgRollDice::getDiceToRollCount() const
{
return numberOfDiceEdit->text().toUInt();
}

View file

@ -1,32 +0,0 @@
/**
* @file dlg_roll_dice.h
* @ingroup GameDialogs
*/
//! \todo Document this file.
#ifndef DLG_ROLL_DICE_H
#define DLG_ROLL_DICE_H
#include <QDialog>
#include <QDialogButtonBox>
#include <QLabel>
#include <QSpinBox>
class DlgRollDice : public QDialog
{
Q_OBJECT
static constexpr uint DEFAULT_NUMBER_SIDES_DIE = 20;
static constexpr uint DEFAULT_NUMBER_DICE_TO_ROLL = 1;
QLabel *numberOfSidesLabel, *numberOfDiceLabel;
QSpinBox *numberOfSidesEdit, *numberOfDiceEdit;
QDialogButtonBox *buttonBox;
public:
explicit DlgRollDice(QWidget *parent = nullptr);
[[nodiscard]] uint getDieSideCount() const;
[[nodiscard]] uint getDiceToRollCount() const;
};
#endif // DLG_ROLL_DICE_H

View file

@ -4,16 +4,16 @@
#include <libcockatrice/protocol/pb/event_game_joined.pb.h>
Game::Game(TabGame *_tab,
Game::Game(QObject *_parent,
bool isLocalGame,
QList<AbstractClient *> &_clients,
const Event_GameJoined &event,
const QMap<int, QString> &_roomGameTypes)
: AbstractGame(_tab)
: AbstractGame(_parent)
{
gameMetaInfo->setFromProto(event.game_info());
gameMetaInfo->setRoomGameTypes(_roomGameTypes);
gameState = new GameState(this, 0, event.host_id(), tab->getTabSupervisor()->getIsLocalGame(), _clients, false,
event.resuming(), -1, false);
gameState = new GameState(this, 0, event.host_id(), isLocalGame, _clients, false, event.resuming(), -1, false);
connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged);
playerManager = new PlayerManager(this, event.player_id(), event.judge(), event.spectator());
gameMetaInfo->setStarted(false);

View file

@ -16,7 +16,8 @@ class Game : public AbstractGame
Q_OBJECT
public:
Game(TabGame *tab,
Game(QObject *parent,
bool isLocalGame,
QList<AbstractClient *> &_clients,
const Event_GameJoined &event,
const QMap<int, QString> &_roomGameTypes);

View file

@ -1,8 +1,8 @@
#include "game_event_handler.h"
#include "../game_graphics/log/message_log_widget.h"
#include "../interface/widgets/tabs/tab_game.h"
#include "abstract_game.h"
#include "log/message_log_widget.h"
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/get_pb_extension.h>

View file

@ -1,766 +0,0 @@
#include "game_scene.h"
#include "../client/settings/cache_settings.h"
#include "../game_graphics/zones/select_zone.h"
#include "../game_graphics/zones/view_zone.h"
#include "../game_graphics/zones/view_zone_widget.h"
#include "abstract_game.h"
#include "board/card_item.h"
#include "phases_toolbar.h"
#include "player/player_actions.h"
#include "player/player_graphics_item.h"
#include "player/player_logic.h"
#include <QBasicTimer>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QSet>
#include <QtMath>
#include <libcockatrice/utility/zone_names.h>
#include <numeric>
/**
* @brief Constructs the GameScene.
* @param _phasesToolbar Toolbar widget for phases.
* @param parent Optional parent QObject.
*
* Initializes the animation timer, adds the phases toolbar to the scene,
* and connects to settings changes for multi-column layout.
* Finally, calls rearrange() to layout players initially.
*/
GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
: QGraphicsScene(parent), phasesToolbar(_phasesToolbar), viewSize(QSize()), playerRotation(0)
{
animationTimer = new QBasicTimer;
addItem(phasesToolbar);
connect(&SettingsCache::instance(), &SettingsCache::minPlayersForMultiColumnLayoutChanged, this,
&GameScene::rearrange);
rearrange();
}
GameScene::~GameScene()
{
delete animationTimer;
// DO NOT call clearViews() here
// clearViews calls close() on the zoneViews, which sends signals; sending signals in destructors leads to segfaults
// deleteLater() deletes the zoneView without allowing it to send signals
for (const auto &zoneView : zoneViews) {
zoneView->deleteLater();
}
}
/**
* @brief Updates localized text in all zone views.
*/
void GameScene::retranslateUi()
{
for (ZoneViewWidget *view : zoneViews) {
view->retranslateUi();
}
}
QList<CardItem *> GameScene::selectedCards() const
{
QList<CardItem *> selectedCards;
for (auto item : selectedItems()) {
if (auto card = qgraphicsitem_cast<CardItem *>(item)) {
selectedCards.append(card);
}
}
return selectedCards;
}
void GameScene::onCardSelectionChanged(AbstractCardItem *abstractCard, bool selected)
{
CardItem *card = qobject_cast<CardItem *>(abstractCard);
if (!card || !card->getOwner()) {
return;
}
auto *owner = card->getOwner();
if (selected) {
owner->requestCardMenuUpdate(card);
return;
}
if (selectedItems().isEmpty()) {
owner->getGame()->setActiveCard(nullptr);
owner->requestCardMenuUpdate(nullptr);
}
}
void GameScene::onCardRightClicked(AbstractCardItem *abstractCard, QPoint screenPos)
{
auto *card = qobject_cast<CardItem *>(abstractCard);
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
auto *view = playerViews.value(card->getOwner()->getPlayerInfo()->getId());
if (!view) {
return;
}
card->getOwner()->getGame()->setActiveCard(card);
if (auto *menu = view->getPlayerMenu()->updateCardMenu(card)) {
menu->popup(screenPos);
}
}
void GameScene::playSelected(AbstractCardItem *card)
{
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actPlay(selectedCards());
}
void GameScene::playSelectedFaceDown(AbstractCardItem *card)
{
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actPlayFacedown(selectedCards());
}
void GameScene::hideSelected(AbstractCardItem *card)
{
if (!card) {
return;
}
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actHide(selectedCards());
}
/**
* @brief Adds a player to the scene and stores their graphics item.
* @param player Player to add.
*
* Connects to the player's sizeChanged signal to recompute layout on resize.
*/
void GameScene::addPlayer(PlayerLogic *player)
{
qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::addPlayer name=" << player->getPlayerInfo()->getName();
auto *view = new PlayerGraphicsItem(player);
playerViews.insert(player->getPlayerInfo()->getId(), view);
addItem(view);
connect(view, &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange);
connect(player, &PlayerLogic::concededChanged, this, [this](int id, bool conceded) {
if (conceded) {
clearArrowsForPlayer(id);
}
rearrange();
});
connect(player, &PlayerLogic::requestZoneViewToggle, this, &GameScene::toggleZoneView);
connect(player, &PlayerLogic::requestRevealedZoneView, this, &GameScene::addRevealedZoneView);
connect(player, &PlayerLogic::arrowDeleted, this, &GameScene::deleteArrow);
connect(player, &PlayerLogic::arrowCreateRequested, this, &GameScene::addArrow);
connect(player, &PlayerLogic::arrowDeleteRequested, this, &GameScene::requestArrowDeletion);
connect(player, &PlayerLogic::arrowsClearedLocally, this,
[this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayerLocally(id); });
connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardZoneChanged, this, &GameScene::onCardZoneChanged);
rearrange();
}
/**
* @brief Removes a player from the scene.
* @param player Player to remove.
*
* Closes any zone views associated with the player and recomputes layout.
*/
void GameScene::removePlayer(PlayerLogic *player)
{
qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::removePlayer name=" << player->getPlayerInfo()->getName();
clearArrowsForPlayer(player->getPlayerInfo()->getId());
for (ZoneViewWidget *zone : zoneViews) {
if (zone->getPlayer() == player) {
zone->close();
}
}
auto *view = playerViews.take(player->getPlayerInfo()->getId());
removeItem(view);
view->deleteLater();
rearrange();
}
/**
* @brief Adjusts the global rotation offset for player layout.
* @param rotationAdjustment Number of positions to rotate.
*
* Recomputes player layout after applying rotation.
*/
void GameScene::adjustPlayerRotation(int rotationAdjustment)
{
playerRotation += rotationAdjustment;
rearrange();
}
/**
* @brief Recomputes the layout of players and the scene size.
*
* Steps:
* 1. Collect active players who haven't conceded.
* 2. Rotate player list based on first local player and rotation offset.
* 3. Determine number of columns.
* 4. Compute scene size and layout.
* 5. Update toolbar height and scene rectangle.
* 6. Adjust columns and player positions to match view size.
*/
void GameScene::rearrange()
{
int firstPlayerIndex = 0;
auto playersPlaying = collectActivePlayers(firstPlayerIndex);
playersPlaying = rotatePlayers(playersPlaying, firstPlayerIndex);
int columns = determineColumnCount(playersPlaying.size());
QSizeF sceneSize = computeSceneSizeAndPlayerLayout(playersPlaying, columns);
phasesToolbar->setHeight(sceneSize.height());
setSceneRect(0, 0, sceneSize.width(), sceneSize.height());
processViewSizeChange(viewSize);
}
// ---------- View Size ----------
/**
* @brief Handles view resize and redistributes player positions.
* @param newSize New view size.
*
* Steps:
* 1. Compute minimum width per column from player items.
* 2. Determine new scene width respecting aspect ratio.
* 3. Resize columns and reposition players proportionally.
*/
void GameScene::processViewSizeChange(const QSize &newSize)
{
viewSize = newSize;
QList<qreal> minWidthByColumn = calculateMinWidthByColumn();
qreal minWidth = std::accumulate(minWidthByColumn.begin(), minWidthByColumn.end(), phasesToolbar->getWidth());
qreal newWidth = calculateNewSceneWidth(newSize, minWidth);
setSceneRect(0, 0, newWidth, sceneRect().height());
resizeColumnsAndPlayers(minWidthByColumn, newWidth);
}
// ---------- Player Layout Helpers ----------
/**
* @brief Collects all active (non-conceded) players.
* @param firstPlayerIndex Output index of first local player.
* @return List of active players.
*
* Used to determine rotation and layout order.
*/
QList<PlayerLogic *> GameScene::collectActivePlayers(int &firstPlayerIndex) const
{
QList<PlayerLogic *> activePlayers;
firstPlayerIndex = 0;
bool firstPlayerFound = false;
for (auto *pgItem : playerViews.values()) {
PlayerLogic *p = pgItem->getLogic();
if (p && !p->getConceded()) {
activePlayers.append(p);
if (!firstPlayerFound && p->getPlayerInfo()->getLocal()) {
firstPlayerIndex = activePlayers.size() - 1;
firstPlayerFound = true;
}
}
}
return activePlayers;
}
/**
* @brief Rotates the list of players for layout.
* @param players Original list of players.
* @param firstPlayerIndex Index of first local player.
* @return Rotated list.
*
* Applies rotation offset and ensures the list wraps correctly.
*/
QList<PlayerLogic *> GameScene::rotatePlayers(const QList<PlayerLogic *> &activePlayers, int firstPlayerIndex) const
{
QList<PlayerLogic *> rotated = activePlayers;
if (!rotated.isEmpty()) {
int totalRotation = firstPlayerIndex + playerRotation;
while (totalRotation < 0) {
totalRotation += rotated.size();
}
for (int i = 0; i < totalRotation; ++i) {
rotated.append(rotated.takeFirst());
}
}
return rotated;
}
int GameScene::determineColumnCount(int playerCount)
{
return playerCount < SettingsCache::instance().getMinPlayersForMultiColumnLayout() ? 1 : 2;
}
/**
* @brief Computes layout positions and scene size based on players and columns.
* @param playersPlaying List of active players.
* @param columns Number of columns to split into.
* @return Calculated scene size.
*
* Logic:
* - Determine rows per column (rounding up).
* - Calculate column widths based on widest player item.
* - Accumulate scene width and height.
* - Position players in columns with spacing.
* - Mirror graphics for visual balance.
*/
QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList<PlayerLogic *> &playersPlaying, int columns)
{
playersByColumn.clear();
int rows = qCeil((qreal)playersPlaying.size() / columns);
qreal sceneHeight = 0, sceneWidth = -playerAreaSpacing;
QList<int> columnWidth;
QListIterator<PlayerLogic *> playersIter(playersPlaying);
for (int col = 0; col < columns; ++col) {
playersByColumn.append(QList<PlayerGraphicsItem *>());
columnWidth.append(0);
qreal thisColumnHeight = -playerAreaSpacing;
int rowsInColumn = rows - (playersPlaying.size() % columns) * col; // Adjust rows for uneven columns
for (int j = 0; j < rowsInColumn; ++j) {
PlayerLogic *player = playersIter.next();
if (col == 0) {
playersByColumn[col].prepend(playerViews.value(player->getPlayerInfo()->getId()));
} else {
playersByColumn[col].append(playerViews.value(player->getPlayerInfo()->getId()));
}
auto *pgItem = playerViews.value(player->getPlayerInfo()->getId());
thisColumnHeight += pgItem->boundingRect().height() + playerAreaSpacing;
columnWidth[col] = std::max(columnWidth[col], (int)pgItem->boundingRect().width());
}
sceneHeight = std::max(sceneHeight, thisColumnHeight);
sceneWidth += columnWidth[col] + playerAreaSpacing;
}
qreal phasesWidth = phasesToolbar->getWidth();
sceneWidth += phasesWidth;
// Position players horizontally and vertically
qreal x = phasesWidth;
for (int col = 0; col < columns; ++col) {
qreal y = 0;
for (int row = 0; row < playersByColumn[col].size(); ++row) {
PlayerGraphicsItem *player = playersByColumn[col][row];
player->setPos(x, y);
player->setMirrored(row != rows - 1); // Mirror all except bottom-most
y += player->boundingRect().height() + playerAreaSpacing;
}
x += columnWidth[col] + playerAreaSpacing;
}
return QSizeF(sceneWidth, sceneHeight);
}
/**
* @brief Computes the minimum width for each column based on player minimum widths.
* @return List of minimum widths per column.
*/
QList<qreal> GameScene::calculateMinWidthByColumn() const
{
QList<qreal> minWidthByColumn;
for (const auto &col : playersByColumn) {
qreal maxWidth = 0;
for (PlayerGraphicsItem *player : col) {
maxWidth = std::max(maxWidth, player->getMinimumWidth());
}
minWidthByColumn.append(maxWidth);
}
return minWidthByColumn;
}
/**
* @brief Calculates new scene width considering window aspect ratio.
* @param newSize View size.
* @param minWidth Minimum width needed to fit all players.
* @return Scene width respecting window and content.
*/
qreal GameScene::calculateNewSceneWidth(const QSize &newSize, qreal minWidth) const
{
qreal newRatio = (qreal)newSize.width() / newSize.height();
qreal minRatio = minWidth / sceneRect().height();
if (minRatio > newRatio) {
return minWidth; // Table dominates width
} else {
return newRatio * sceneRect().height(); // Window ratio dominates
}
}
/**
* @brief Resizes columns and distributes extra width to players.
* @param minWidthByColumn Minimum widths per column.
* @param newWidth Total scene width.
*
* Extra width is distributed evenly across columns. Each player item is
* notified to adjust internal layout for the new column width.
*/
void GameScene::resizeColumnsAndPlayers(const QList<qreal> &minWidthByColumn, qreal newWidth)
{
qreal minWidth = std::accumulate(minWidthByColumn.begin(), minWidthByColumn.end(), phasesToolbar->getWidth());
qreal extraWidthPerColumn = (newWidth - minWidth) / playersByColumn.size();
qreal newx = phasesToolbar->getWidth();
for (int col = 0; col < playersByColumn.size(); ++col) {
for (PlayerGraphicsItem *player : playersByColumn[col]) {
player->processSceneSizeChange(minWidthByColumn[col] + extraWidthPerColumn);
player->setPos(newx, player->y());
}
newx += minWidthByColumn[col] + extraWidthPerColumn;
}
}
void GameScene::addArrow(QSharedPointer<ArrowData> data)
{
auto *startView = playerViews.value(data->startPlayerId);
auto *targetView = playerViews.value(data->targetPlayerId);
if (!startView || !targetView) {
return;
}
PlayerLogic *startLogic = startView->getLogic();
auto *startZone = startLogic->getZones().value(data->startZone);
if (!startZone) {
return;
}
CardItem *startCard = startZone->getCard(data->startCardId);
if (!startCard) {
return;
}
ArrowTarget *targetItem = nullptr;
if (data->isPlayerTargeted()) {
targetItem = targetView->getPlayerTarget();
} else {
auto *zone = targetView->getLogic()->getZones().value(data->targetZone);
if (zone) {
targetItem = zone->getCard(data->targetCardId);
}
}
if (!targetItem) {
return;
}
auto *arrow = new ArrowItem(data, startCard, targetItem);
addItem(arrow);
arrowRegistry.insert(data, arrow);
connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion);
}
void GameScene::deleteArrow(int playerId, int arrowId)
{
if (auto *arrow = arrowRegistry.take(playerId, arrowId)) {
arrow->delArrow();
}
}
void GameScene::requestArrowDeletion(int playerId, int arrowId)
{
if (arrowRegistry.contains(playerId, arrowId)) {
emit arrowDeletionRequested(playerId, arrowId);
}
}
void GameScene::onCardZoneChanged(CardItem *card, bool sameZone)
{
QList<ArrowItem *> toDelete;
for (auto *arrow : arrowRegistry.all()) {
if (arrow->getStartItem() == card || arrow->getTargetItem() == card) {
if (sameZone) {
arrow->updatePath();
} else {
toDelete.append(arrow);
}
}
}
for (auto *arrow : toDelete) {
deleteArrow(arrow->getCreatorId(), arrow->getId());
}
}
void GameScene::clearArrowsForPlayer(int playerId)
{
for (int arrowId : arrowRegistry.idsForPlayer(playerId)) {
emit requestArrowDeletion(playerId, arrowId);
}
}
void GameScene::clearArrowsForPlayerLocally(int playerId)
{
for (int arrowId : arrowRegistry.idsForPlayer(playerId)) {
arrowRegistry.take(playerId, arrowId)->delArrow();
}
}
// ---------- Hover Handling ----------
void GameScene::updateHover(const QPointF &scenePos)
{
auto itemList = items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform());
CardZone *zone = findTopmostZone(itemList);
CardItem *topCard = zone ? findTopmostCardInZone(itemList, zone) : nullptr;
updateHoveredCard(topCard);
}
void GameScene::updateHoveredCard(CardItem *newCard)
{
if (hoveredCard && (newCard != hoveredCard)) {
endCardHover(hoveredCard);
}
if (newCard && (newCard != hoveredCard)) {
beginCardHover(newCard);
}
hoveredCard = newCard;
}
void GameScene::beginCardHover(CardItem *card)
{
card->setHovered(true);
if (auto *zone = SelectZone::findOwningSelectZone(card)) {
zone->escapeClipForHover(card);
}
}
void GameScene::endCardHover(CardItem *card)
{
if (auto *zone = SelectZone::findOwningSelectZone(card)) {
zone->restoreClipAfterHover(card);
}
card->setHovered(false);
}
CardZone *GameScene::findTopmostZone(const QList<QGraphicsItem *> &items)
{
for (QGraphicsItem *item : items) {
if (auto *zone = qgraphicsitem_cast<CardZone *>(item)) {
return zone;
}
}
return nullptr;
}
CardItem *GameScene::findTopmostCardInZone(const QList<QGraphicsItem *> &items, CardZone *zone)
{
CardItem *maxZCard = nullptr;
qreal maxZ = -1;
for (QGraphicsItem *item : items) {
CardItem *card = qgraphicsitem_cast<CardItem *>(item);
if (!card) {
continue;
}
if (card->getAttachedTo()) {
if (card->getAttachedTo()->getZone() != zone->getLogic()) {
continue;
}
} else if (card->getZone() != zone->getLogic()) {
continue;
}
if (card->getRealZValue() > maxZ) {
maxZ = card->getRealZValue();
maxZCard = card;
}
}
return maxZCard;
}
// ---------- Zone Views ----------
/**
* @brief Toggles a zone view for a player.
* @param player Player owning the zone.
* @param zoneName Name of the zone.
* @param numberCards Number of cards visible in the view.
* @param isReversed Whether the zone view is reversed.
*
* If an identical view exists, it is closed. Otherwise, a new ZoneViewWidget is created
* and positioned based on zone type.
*/
void GameScene::toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed)
{
for (auto &view : zoneViews) {
ZoneViewZone *temp = view->getZone();
if (temp->getLogic()->getName() == zoneName && temp->getLogic()->getPlayer() == player &&
qobject_cast<ZoneViewZoneLogic *>(temp->getLogic())->getNumberCards() == numberCards) {
view->close();
}
}
ZoneViewWidget *item =
new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false, false, {}, isReversed);
zoneViews.append(item);
connect(item, &ZoneViewWidget::closePressed, this, &GameScene::removeZoneView);
addItem(item);
if (zoneName == ZoneNames::GRAVE) {
item->setPos(360, 100);
} else if (zoneName == ZoneNames::EXILE) {
item->setPos(380, 120);
} else {
item->setPos(340, 80);
}
}
/**
* @brief Adds a revealed zone view (for shown cards).
* @param player Owning player.
* @param zone Zone logic.
* @param cardList List of cards to show.
* @param withWritePermission Whether edits are allowed.
*/
void GameScene::addRevealedZoneView(PlayerLogic *player,
CardZoneLogic *zone,
const QList<const ServerInfo_Card *> &cardList,
bool withWritePermission)
{
ZoneViewWidget *item = new ZoneViewWidget(player, zone, -2, true, withWritePermission, cardList);
zoneViews.append(item);
connect(item, &ZoneViewWidget::closePressed, this, &GameScene::removeZoneView);
addItem(item);
item->setPos(600, 80);
}
/**
* @brief Removes a zone view widget from the scene.
* @param item Zone view to remove.
*/
void GameScene::removeZoneView(ZoneViewWidget *item)
{
zoneViews.removeOne(item);
removeItem(item);
}
/**
* @brief Closes all zone views.
*/
void GameScene::clearViews()
{
while (!zoneViews.isEmpty()) {
zoneViews.first()->close();
}
}
/**
* @brief Closes the most recently added zone view.
*/
void GameScene::closeMostRecentZoneView()
{
if (!zoneViews.isEmpty()) {
zoneViews.last()->close();
}
}
// ---------- View Transforms ----------
QTransform GameScene::getViewTransform() const
{
return views().at(0)->transform();
}
QTransform GameScene::getViewportTransform() const
{
return views().at(0)->viewportTransform();
}
// ---------- Event Handling ----------
bool GameScene::event(QEvent *event)
{
if (event->type() == QEvent::GraphicsSceneMouseMove) {
updateHover(static_cast<QGraphicsSceneMouseEvent *>(event)->scenePos());
} else if (event->type() == QEvent::Leave) {
updateHoveredCard(nullptr);
}
return QGraphicsScene::event(event);
}
void GameScene::timerEvent(QTimerEvent * /*event*/)
{
QMutableSetIterator<CardItem *> i(cardsToAnimate);
while (i.hasNext()) {
i.next();
if (!i.value()->animationEvent()) {
i.remove();
}
}
if (cardsToAnimate.isEmpty()) {
animationTimer->stop();
}
}
void GameScene::registerAnimationItem(AbstractCardItem *card)
{
cardsToAnimate.insert(static_cast<CardItem *>(card));
if (!animationTimer->isActive()) {
animationTimer->start(10, this);
}
}
void GameScene::unregisterAnimationItem(AbstractCardItem *card)
{
cardsToAnimate.remove(static_cast<CardItem *>(card));
if (cardsToAnimate.isEmpty()) {
animationTimer->stop();
}
}
// ---------- Rubber Band ----------
void GameScene::startRubberBand(const QPointF &selectionOrigin)
{
emit sigStartRubberBand(selectionOrigin);
}
void GameScene::resizeRubberBand(const QPointF &cursorPoint, int selectedCount)
{
emit sigResizeRubberBand(cursorPoint, selectedCount);
}
void GameScene::stopRubberBand()
{
emit sigStopRubberBand();
}

View file

@ -1,245 +0,0 @@
#ifndef GAMESCENE_H
#define GAMESCENE_H
#include "arrow_registry.h"
#include "board/arrow_data.h"
#include "board/arrow_item.h"
#include "zones/card_zone_logic.h"
#include <QGraphicsScene>
#include <QList>
#include <QLoggingCategory>
#include <QPointer>
#include <QSet>
inline Q_LOGGING_CATEGORY(GameSceneLog, "game_scene");
inline Q_LOGGING_CATEGORY(GameScenePlayerAdditionRemovalLog, "game_scene.player_addition_removal");
class PlayerLogic;
class PlayerGraphicsItem;
class ZoneViewWidget;
class CardZone;
class AbstractCardItem;
class CardItem;
class ServerInfo_Card;
class PhasesToolbar;
class QBasicTimer;
/**
* @class GameScene
* @ingroup GameGraphics
* @brief Manages the game board display including players, zones, cards, and animations.
*
* GameScene handles:
* - Dynamic arrangement of players in columns/rows.
* - Player rotation adjustments.
* - Zone views for cards (e.g., graveyard, library, revealed zones).
* - Hover and animation handling for cards.
* - Scene resizing and responsive layout.
*/
class GameScene : public QGraphicsScene
{
Q_OBJECT
private:
static const int playerAreaSpacing = 5; ///< Space between player areas
PhasesToolbar *phasesToolbar; ///< Toolbar showing game phases
QMap<int, PlayerGraphicsItem *> playerViews; ///< ID lookup for player graphics items
QList<QList<PlayerGraphicsItem *>> playersByColumn; ///< Players organized by column
ArrowRegistry arrowRegistry; ///< ID registry for arrow graphics items
QList<ZoneViewWidget *> zoneViews; ///< Active zone view widgets
QSize viewSize; ///< Current view size
QPointer<CardItem> hoveredCard; ///< Currently hovered card
QBasicTimer *animationTimer; ///< Timer for card animations
QSet<CardItem *> cardsToAnimate; ///< Cards currently animating
int playerRotation; ///< Rotation offset for player layout
/**
* @brief Updates which card is currently hovered based on scene coordinates.
* @param scenePos Scene position of the cursor.
*/
void updateHover(const QPointF &scenePos);
/**
* @brief Activates hover state and escapes the card from its clip container
* so hover scaling is visible beyond zone bounds.
*/
void beginCardHover(CardItem *card);
/** @brief Deactivates hover state and restores the card to its clip container. */
void endCardHover(CardItem *card);
public:
/**
* @brief Constructs the GameScene.
* @param _phasesToolbar Toolbar widget for phases.
* @param parent Optional parent QObject.
*/
explicit GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr);
/** @brief Destructor, cleans up timer and zone views. */
~GameScene() override;
/** @brief Updates UI text for all zone views. */
void retranslateUi();
/** @brief Gets all selected CardItems. */
QList<CardItem *> selectedCards() const;
/**
* @brief Adds a player to the scene and stores their graphics item.
* @param player Player to add.
*/
void addPlayer(PlayerLogic *player);
/**
* @brief Removes a player from the scene.
* @param player Player to remove.
*/
void removePlayer(PlayerLogic *player);
QMap<int, PlayerGraphicsItem *> getPlayers() const
{
return playerViews;
}
PlayerGraphicsItem *viewForPlayer(int playerId)
{
return playerViews.value(playerId);
}
/**
* @brief Adjusts the global rotation offset for player layout.
* @param rotationAdjustment Number of positions to rotate.
*/
void adjustPlayerRotation(int rotationAdjustment);
/** @brief Recomputes the layout of players and the scene size. */
void rearrange();
/**
* @brief Handles view resize and redistributes player positions.
* @param newSize New view size.
*/
void processViewSizeChange(const QSize &newSize);
/**
* @brief Collects all active (non-conceded) players.
* @param firstPlayerIndex Output index of first local player.
* @return List of active players.
*/
QList<PlayerLogic *> collectActivePlayers(int &firstPlayerIndex) const;
/**
* @brief Rotates the list of players for layout.
* @param players Original list of players.
* @param firstPlayerIndex Index of first local player.
* @return Rotated list.
*/
QList<PlayerLogic *> rotatePlayers(const QList<PlayerLogic *> &players, int firstPlayerIndex) const;
/**
* @brief Determines the number of columns to display players in.
* @param playerCount Total number of active players.
* @return Number of columns (1 or 2).
*/
static int determineColumnCount(int playerCount);
/**
* @brief Computes layout positions and scene size based on players and columns.
* @param playersPlaying List of active players.
* @param columns Number of columns to split into.
* @return Calculated scene size.
*/
QSizeF computeSceneSizeAndPlayerLayout(const QList<PlayerLogic *> &playersPlaying, int columns);
/**
* @brief Computes the minimum width for each column based on player minimum widths.
* @return List of minimum widths per column.
*/
QList<qreal> calculateMinWidthByColumn() const;
/**
* @brief Calculates new scene width considering window aspect ratio.
* @param newSize View size.
* @param minWidth Minimum width needed to fit all players.
* @return Scene width respecting window and content.
*/
qreal calculateNewSceneWidth(const QSize &newSize, qreal minWidth) const;
/**
* @brief Resizes columns and distributes extra width to players.
* @param minWidthByColumn Minimum widths per column.
* @param newWidth Total scene width.
*/
void resizeColumnsAndPlayers(const QList<qreal> &minWidthByColumn, qreal newWidth);
/** @brief Finds the topmost card zone under the cursor. */
static CardZone *findTopmostZone(const QList<QGraphicsItem *> &items);
/** @brief Finds the topmost card in a given zone, considering attachments and Z-order. */
static CardItem *findTopmostCardInZone(const QList<QGraphicsItem *> &items, CardZone *zone);
/** @brief Updates hovered card highlighting. */
void updateHoveredCard(CardItem *newCard);
/** @brief Registers a card for animation updates. */
void registerAnimationItem(AbstractCardItem *card);
/** @brief Unregisters a card from animation updates. */
void unregisterAnimationItem(AbstractCardItem *card);
void startRubberBand(const QPointF &selectionOrigin);
void resizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void stopRubberBand();
public slots:
void onCardSelectionChanged(AbstractCardItem *card, bool selected);
void onCardRightClicked(AbstractCardItem *card, QPoint screenPos);
void playSelected(AbstractCardItem *card);
void playSelectedFaceDown(AbstractCardItem *card);
void hideSelected(AbstractCardItem *card);
/** @brief Toggles a zone view for a player. */
void toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed = false);
/** @brief Adds a revealed zone view (for shown cards). */
void addRevealedZoneView(PlayerLogic *player,
CardZoneLogic *zone,
const QList<const ServerInfo_Card *> &cardList,
bool withWritePermission);
/** @brief Removes a zone view widget from the scene. */
void removeZoneView(ZoneViewWidget *item);
/** @brief Closes all zone views. */
void clearViews();
/** @brief Closes the most recently added zone view. */
void closeMostRecentZoneView();
QTransform getViewTransform() const;
QTransform getViewportTransform() const;
/// Directly modifies the scene
void addArrow(QSharedPointer<ArrowData> data);
void deleteArrow(int playerId, int arrowId);
void clearArrowsForPlayer(int playerId);
void clearArrowsForPlayerLocally(int playerId);
/// Queues up arrow deletion but doesn't directly modify the scene
void requestArrowDeletion(int playerId, int arrowId);
void onCardZoneChanged(CardItem *card, bool sameZone);
protected:
/** @brief Handles hover updates. */
bool event(QEvent *event) override;
/** @brief Handles animation timer updates. */
void timerEvent(QTimerEvent *event) override;
signals:
void sigStartRubberBand(const QPointF &selectionOrigin);
void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void sigStopRubberBand();
void arrowDeletionRequested(int creatorId, int arrowId);
};
#endif

View file

@ -1,188 +0,0 @@
#include "game_view.h"
#include "../client/settings/cache_settings.h"
#include "game_scene.h"
#include <QAction>
#include <QLabel>
#include <QResizeEvent>
#include <QRubberBand>
// QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings.
// This subclass disables that behavior so dragCountLabel can appear above it.
class SelectionRubberBand : public QRubberBand
{
public:
using QRubberBand::QRubberBand;
protected:
void showEvent(QShowEvent *event) override
{
QWidget::showEvent(event); // Skip QRubberBand's raise()
}
void changeEvent(QEvent *event) override
{
if (event->type() == QEvent::ZOrderChange) {
return; // Skip QRubberBand's raise() on z-order changes
}
QRubberBand::changeEvent(event);
}
};
GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, parent), rubberBand(0)
{
setBackgroundBrush(QBrush(QColor(0, 0, 0)));
setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing);
setFocusPolicy(Qt::ClickFocus);
setViewportUpdateMode(BoundingRectViewportUpdate);
connect(scene, &GameScene::sceneRectChanged, this, &GameView::updateSceneRect);
connect(scene, &GameScene::sigStartRubberBand, this, &GameView::startRubberBand);
connect(scene, &GameScene::sigResizeRubberBand, this, &GameView::resizeRubberBand);
connect(scene, &GameScene::sigStopRubberBand, this, &GameView::stopRubberBand);
connect(scene, &QGraphicsScene::selectionChanged, this, [this]() { updateTotalSelectionCount(); });
aCloseMostRecentZoneView = new QAction(this);
connect(aCloseMostRecentZoneView, &QAction::triggered, scene, &GameScene::closeMostRecentZoneView);
addAction(aCloseMostRecentZoneView);
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&GameView::refreshShortcuts);
refreshShortcuts();
rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this);
const QString countLabelStyle = "color: white; "
"font-size: 14px; "
"font-weight: bold; "
"background-color: rgba(0, 0, 0, 160); "
"border-radius: 3px; "
"padding: 1px 2px;";
dragCountLabel = new QLabel(this);
dragCountLabel->setStyleSheet(countLabelStyle);
dragCountLabel->hide();
dragCountLabel->raise();
totalCountLabel = new QLabel(this);
totalCountLabel->setStyleSheet(countLabelStyle);
totalCountLabel->hide();
}
void GameView::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
GameScene *s = dynamic_cast<GameScene *>(scene());
if (s) {
s->processViewSizeChange(event->size());
}
updateSceneRect(scene()->sceneRect());
updateTotalSelectionCount(event->size());
}
void GameView::updateSceneRect(const QRectF &rect)
{
fitInView(rect, Qt::KeepAspectRatio);
}
void GameView::startRubberBand(const QPointF &_selectionOrigin)
{
if (!rubberBand) {
return;
}
selectionOrigin = _selectionOrigin;
rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0)));
rubberBand->show();
}
void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount)
{
if (!rubberBand) {
return;
}
constexpr int kLabelPaddingInPixels = 4;
QPoint cursor = cursorPoint.toPoint();
QRect rect = QRect(mapFromScene(selectionOrigin), cursor).normalized();
rubberBand->setGeometry(rect);
if (!SettingsCache::instance().getShowDragSelectionCount()) {
dragCountLabel->hide();
return;
}
if (selectedCount > 0) {
dragCountLabel->setText(QString::number(selectedCount));
dragCountLabel->adjustSize();
QSize labelSize = dragCountLabel->size();
if (rect.width() < labelSize.width() + 2 * kLabelPaddingInPixels ||
rect.height() < labelSize.height() + 2 * kLabelPaddingInPixels) {
dragCountLabel->hide();
return;
}
const int minX = rect.left() + kLabelPaddingInPixels;
const int minY = rect.top() + kLabelPaddingInPixels;
int x = qMax(minX, cursor.x() - labelSize.width() - kLabelPaddingInPixels);
int y = qMax(minY, cursor.y() - labelSize.height() - kLabelPaddingInPixels);
bool isAtTopLeftCorner = (x == minX) && (y == minY);
if (isAtTopLeftCorner) {
constexpr int kCursorClearanceInPixels = 16;
x = qMin(cursor.x() + kCursorClearanceInPixels, rect.right() - labelSize.width() - kLabelPaddingInPixels);
}
dragCountLabel->move(x, y);
dragCountLabel->show();
} else {
dragCountLabel->hide();
}
}
void GameView::stopRubberBand()
{
if (!rubberBand) {
return;
}
rubberBand->hide();
dragCountLabel->hide();
}
void GameView::refreshShortcuts()
{
aCloseMostRecentZoneView->setShortcuts(
SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView"));
}
void GameView::updateTotalSelectionCount(const QSize &viewSize)
{
if (!SettingsCache::instance().getShowTotalSelectionCount()) {
totalCountLabel->hide();
return;
}
int count = scene()->selectedItems().count();
if (count > 1) {
totalCountLabel->setText(QString::number(count));
totalCountLabel->adjustSize();
constexpr int kMarginInPixels = 10;
int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width();
int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height();
int x = availableWidth - totalCountLabel->width() - kMarginInPixels;
int y = availableHeight - totalCountLabel->height() - kMarginInPixels;
totalCountLabel->move(x, y);
totalCountLabel->show();
} else {
totalCountLabel->hide();
}
}

View file

@ -1,41 +0,0 @@
/**
* @file game_view.h
* @ingroup GameGraphics
*/
//! \todo Document this file.
#ifndef GAMEVIEW_H
#define GAMEVIEW_H
#include <QGraphicsView>
class GameScene;
class QLabel;
class QRubberBand;
class GameView : public QGraphicsView
{
Q_OBJECT
private:
QAction *aCloseMostRecentZoneView;
QRubberBand *rubberBand;
QLabel *dragCountLabel;
QLabel *totalCountLabel;
QPointF selectionOrigin;
protected:
void resizeEvent(QResizeEvent *event) override;
private slots:
void startRubberBand(const QPointF &selectionOrigin);
void resizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void stopRubberBand();
void refreshShortcuts();
void updateTotalSelectionCount(const QSize &viewSize = QSize());
public slots:
void updateSceneRect(const QRectF &rect);
public:
explicit GameView(GameScene *scene, QWidget *parent = nullptr);
};
#endif

View file

@ -1,51 +0,0 @@
#include "hand_counter.h"
#include "../game_graphics/zones/card_zone.h"
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QPixmapCache>
HandCounter::HandCounter(QGraphicsItem *parent) : AbstractGraphicsItem(parent), number(0)
{
setCacheMode(DeviceCoordinateCache);
}
HandCounter::~HandCounter()
{
}
void HandCounter::updateNumber()
{
number = static_cast<CardZoneLogic *>(sender())->getCards().size();
update();
}
QRectF HandCounter::boundingRect() const
{
return QRectF(0, 0, 72, 72);
}
void HandCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
painter->save();
QSize translatedSize = painter->combinedTransform().mapRect(boundingRect()).size().toSize();
QPixmap cachedPixmap;
if (!QPixmapCache::find("handCounter" + QString::number(translatedSize.width()), &cachedPixmap)) {
cachedPixmap = QPixmap("theme:hand").scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPixmapCache::insert("handCounter" + QString::number(translatedSize.width()), cachedPixmap);
}
resetPainterTransform(painter);
painter->drawPixmap(cachedPixmap.rect(), cachedPixmap, cachedPixmap.rect());
painter->restore();
paintNumberEllipse(number, 24, Qt::white, -1, -1, painter);
}
void HandCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
emit showContextMenu(event->screenPos());
event->accept();
}
}

View file

@ -1,46 +0,0 @@
/**
* @file hand_counter.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef HANDCOUNTER_H
#define HANDCOUNTER_H
#include "../game_graphics/board/abstract_graphics_item.h"
#include "../game_graphics/board/graphics_item_type.h"
#include <QString>
class QPainter;
class QPixmap;
class HandCounter : public AbstractGraphicsItem
{
Q_OBJECT
private:
int number;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
public slots:
void updateNumber();
signals:
void showContextMenu(const QPoint &screenPos);
public:
enum
{
Type = typeOther
};
int type() const override
{
return Type;
}
explicit HandCounter(QGraphicsItem *parent = nullptr);
~HandCounter() override;
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
};
#endif

View file

@ -1,861 +0,0 @@
#include "message_log_widget.h"
#include "../../client/sound_engine.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../board/card_item.h"
#include "../board/translate_counter_name.h"
#include "../phase.h"
#include "../player/player_logic.h"
#include <../../client/settings/card_counter_settings.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/protocol/pb/context_mulligan.pb.h>
#include <libcockatrice/utility/zone_names.h>
#include <utility>
static QString sanitizeHtml(QString dirty)
{
return dirty.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;");
}
static QString cardLink(const QString &cardName)
{
return QString("<i><a href=\"card://%1\">%2</a></i>").arg(cardName).arg(cardName);
}
QPair<QString, QString>
MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position, bool ownerChange)
{
bool cardNameContainsStartZone = false;
QString fromStr;
QString zoneName = zone->getName();
if (zoneName == ZoneNames::TABLE) {
fromStr = tr(" from play");
} else if (zoneName == ZoneNames::GRAVE) {
fromStr = tr(" from their graveyard");
} else if (zoneName == ZoneNames::EXILE) {
fromStr = tr(" from exile");
} else if (zoneName == ZoneNames::HAND) {
fromStr = tr(" from their hand");
} else if (zoneName == ZoneNames::DECK) {
if (position == 0) {
if (cardName.isEmpty()) {
if (ownerChange) {
cardName = tr("the top card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
} else {
cardName = tr("the top card of their library");
}
cardNameContainsStartZone = true;
} else {
if (ownerChange) {
fromStr = tr(" from the top of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
} else {
fromStr = tr(" from the top of their library");
}
}
} else if (position == zone->getCards().size()) {
if (cardName.isEmpty()) {
if (ownerChange) {
cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
} else {
cardName = tr("the bottom card of their library");
}
cardNameContainsStartZone = true;
} else {
if (ownerChange) {
fromStr = tr(" from the bottom of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
} else {
fromStr = tr(" from the bottom of their library");
}
}
} else {
if (ownerChange) {
fromStr = tr(" from %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
} else {
fromStr = tr(" from their library");
}
}
} else if (zoneName == ZoneNames::SIDEBOARD) {
fromStr = tr(" from sideboard");
} else if (zoneName == ZoneNames::STACK) {
fromStr = tr(" from the stack");
} else {
fromStr = tr(" from custom zone '%1'").arg(zoneName);
}
if (!cardNameContainsStartZone) {
cardName.clear();
}
return {cardName, fromStr};
}
void MessageLogWidget::containerProcessingDone()
{
currentContext = MessageContext_None;
messageSuffix = messagePrefix = QString();
}
void MessageLogWidget::containerProcessingStarted(const GameEventContext &context)
{
if (context.HasExtension(Context_MoveCard::ext)) {
currentContext = MessageContext_MoveCard;
} else if (context.HasExtension(Context_Mulligan::ext)) {
currentContext = MessageContext_Mulligan;
}
}
void MessageLogWidget::logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal)
{
appendHtmlServerMessage((reveal ? tr("%1 is now keeping the top card %2 revealed.")
: tr("%1 is not revealing the top card %2 any longer."))
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(true, CaseTopCardsOfZone)));
}
void MessageLogWidget::logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal)
{
appendHtmlServerMessage((reveal ? tr("%1 can now look at top card %2 at any time.")
: tr("%1 no longer can look at top card %2 at any time."))
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(true, CaseTopCardsOfZone)));
}
void MessageLogWidget::logAttachCard(PlayerLogic *player,
QString cardName,
PlayerLogic *targetPlayer,
QString targetCardName)
{
appendHtmlServerMessage(tr("%1 attaches %2 to %3's %4.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardLink(std::move(cardName)))
.arg(sanitizeHtml(targetPlayer->getPlayerInfo()->getName()))
.arg(cardLink(std::move(targetCardName))));
}
void MessageLogWidget::logConcede(int playerId)
{
soundEngine->playSound("player_concede");
appendHtmlServerMessage(
tr("%1 has conceded the game.")
.arg(sanitizeHtml(game->getPlayerManager()->getPlayer(playerId)->getPlayerInfo()->getName())),
true);
}
void MessageLogWidget::logUnconcede(int playerId)
{
soundEngine->playSound("player_concede");
appendHtmlServerMessage(
tr("%1 has unconceded the game.")
.arg(sanitizeHtml(game->getPlayerManager()->getPlayer(playerId)->getPlayerInfo()->getName())),
true);
}
void MessageLogWidget::logConnectionStateChanged(PlayerLogic *player, bool connectionState)
{
if (connectionState) {
soundEngine->playSound("player_reconnect");
appendHtmlServerMessage(
tr("%1 has restored connection to the game.").arg(sanitizeHtml(player->getPlayerInfo()->getName())), true);
} else {
soundEngine->playSound("player_disconnect");
appendHtmlServerMessage(
tr("%1 has lost connection to the game.").arg(sanitizeHtml(player->getPlayerInfo()->getName())), true);
}
}
void MessageLogWidget::logCreateArrow(PlayerLogic *player,
PlayerLogic *startPlayer,
QString startCard,
PlayerLogic *targetPlayer,
QString targetCard,
bool playerTarget)
{
startCard = cardLink(startCard);
targetCard = cardLink(targetCard);
QString str;
if (playerTarget) {
if (player == startPlayer && player == targetPlayer) {
str = tr("%1 points from their %2 to themselves.");
appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(startCard));
} else if (player == startPlayer) {
str = tr("%1 points from their %2 to %3.");
appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(startCard)
.arg(sanitizeHtml(targetPlayer->getPlayerInfo()->getName())));
} else if (player == targetPlayer) {
str = tr("%1 points from %2's %3 to themselves.");
appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(sanitizeHtml(startPlayer->getPlayerInfo()->getName()))
.arg(startCard));
} else {
str = tr("%1 points from %2's %3 to %4.");
appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(sanitizeHtml(startPlayer->getPlayerInfo()->getName()))
.arg(startCard)
.arg(sanitizeHtml(targetPlayer->getPlayerInfo()->getName())));
}
} else {
if (player == startPlayer && player == targetPlayer) {
str = tr("%1 points from their %2 to their %3.");
appendHtmlServerMessage(
str.arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(startCard).arg(targetCard));
} else if (player == startPlayer) {
str = tr("%1 points from their %2 to %3's %4.");
appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(startCard)
.arg(sanitizeHtml(targetPlayer->getPlayerInfo()->getName()))
.arg(targetCard));
} else if (player == targetPlayer) {
str = tr("%1 points from %2's %3 to their own %4.");
appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(sanitizeHtml(startPlayer->getPlayerInfo()->getName()))
.arg(startCard)
.arg(targetCard));
} else {
str = tr("%1 points from %2's %3 to %4's %5.");
appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(sanitizeHtml(startPlayer->getPlayerInfo()->getName()))
.arg(startCard)
.arg(sanitizeHtml(targetPlayer->getPlayerInfo()->getName()))
.arg(targetCard));
}
}
}
void MessageLogWidget::logCreateToken(PlayerLogic *player, QString cardName, QString pt, bool faceDown)
{
if (faceDown) {
appendHtmlServerMessage(
tr("%1 creates a face down token.").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
} else {
appendHtmlServerMessage(tr("%1 creates token: %2%3.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardLink(std::move(cardName)))
.arg(pt.isEmpty() ? QString() : QString(" (%1)").arg(sanitizeHtml(pt))));
}
}
void MessageLogWidget::logDeckSelect(PlayerLogic *player, QString deckHash, int sideboardSize)
{
if (sideboardSize < 0) {
appendHtmlServerMessage(
tr("%1 has loaded a deck (%2).").arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(deckHash));
} else {
appendHtmlServerMessage(tr("%1 has loaded a deck with %2 sideboard cards (%3).")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg("<font class=\"blue\">" + QString::number(sideboardSize) + "</font>")
.arg(deckHash));
}
}
void MessageLogWidget::logDestroyCard(PlayerLogic *player, QString cardName)
{
appendHtmlServerMessage(
tr("%1 destroys %2.").arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardLink(std::move(cardName))));
}
void MessageLogWidget::logMoveCard(PlayerLogic *player,
CardItem *card,
CardZoneLogic *startZone,
int oldX,
CardZoneLogic *targetZone,
int newX)
{
if (currentContext == MessageContext_Mulligan) {
return;
}
QString startZoneName = startZone->getName();
QString targetZoneName = targetZone->getName();
bool ownerChanged = startZone->getPlayer() != targetZone->getPlayer();
// do not log if moved within the same zone
if ((startZoneName == ZoneNames::TABLE && targetZoneName == ZoneNames::TABLE && !ownerChanged) ||
(startZoneName == ZoneNames::HAND && targetZoneName == ZoneNames::HAND) ||
(startZoneName == ZoneNames::EXILE && targetZoneName == ZoneNames::EXILE)) {
return;
}
QString cardName = card->getName();
QPair<QString, QString> nameFrom = getFromStr(startZone, cardName, oldX, ownerChanged);
if (!nameFrom.first.isEmpty()) {
cardName = nameFrom.first;
}
QString cardStr;
if (!nameFrom.first.isEmpty()) {
cardStr = cardName;
} else if (cardName.isEmpty()) {
cardStr = tr("a card");
} else {
cardStr = cardLink(cardName);
}
if (ownerChanged && (startZone->getPlayer() == player)) {
appendHtmlServerMessage(tr("%1 gives %2 control over %3.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(sanitizeHtml(targetZone->getPlayer()->getPlayerInfo()->getName()))
.arg(cardStr));
return;
}
QString finalStr;
std::optional<QString> fourthArg;
if (targetZoneName == ZoneNames::TABLE) {
soundEngine->playSound("play_card");
if (card->getFaceDown()) {
finalStr = tr("%1 puts %2 into play%3 face down.");
} else {
finalStr = tr("%1 puts %2 into play%3.");
}
} else if (targetZoneName == ZoneNames::GRAVE) {
if (card->getFaceDown()) {
finalStr = tr("%1 puts %2%3 into their graveyard face down.");
} else {
finalStr = tr("%1 puts %2%3 into their graveyard.");
}
} else if (targetZoneName == ZoneNames::EXILE) {
if (card->getFaceDown()) {
finalStr = tr("%1 exiles %2%3 face down.");
} else {
finalStr = tr("%1 exiles %2%3.");
}
} else if (targetZoneName == ZoneNames::HAND) {
finalStr = tr("%1 moves %2%3 to their hand.");
} else if (targetZoneName == ZoneNames::DECK) {
if (newX == -1) {
finalStr = tr("%1 puts %2%3 into their library.");
} else if (newX >= targetZone->getCards().size()) {
finalStr = tr("%1 puts %2%3 onto the bottom of their library.");
} else if (newX == 0) {
finalStr = tr("%1 puts %2%3 on top of their library.");
} else {
++newX;
fourthArg = QString::number(newX);
finalStr = tr("%1 puts %2%3 into their library %4 cards from the top.");
}
} else if (targetZoneName == ZoneNames::SIDEBOARD) {
finalStr = tr("%1 moves %2%3 to sideboard.");
} else if (targetZoneName == ZoneNames::STACK) {
soundEngine->playSound("play_card");
if (card->getFaceDown()) {
finalStr = tr("%1 plays %2%3 face down.");
} else {
finalStr = tr("%1 plays %2%3.");
}
} else {
fourthArg = targetZoneName;
if (card->getFaceDown()) {
finalStr = tr("%1 moves %2%3 to custom zone '%4' face down.");
} else {
finalStr = tr("%1 moves %2%3 to custom zone '%4'.");
}
}
QString message = finalStr.arg(sanitizeHtml(player->getPlayerInfo()->getName()), cardStr, nameFrom.second);
if (fourthArg.has_value()) {
message = message.arg(fourthArg.value());
}
appendHtmlServerMessage(message);
}
void MessageLogWidget::logDrawCards(PlayerLogic *player, int number, bool deckIsEmpty)
{
soundEngine->playSound("draw_card");
if (currentContext == MessageContext_Mulligan) {
logMulligan(player, number);
} else {
if (deckIsEmpty && number == 0) {
appendHtmlServerMessage(
tr("%1 tries to draw from an empty library").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
} else {
appendHtmlServerMessage(tr("%1 draws %2 card(s).", "", number)
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg("<font class=\"blue\">" + QString::number(number) + "</font>"));
}
}
}
void MessageLogWidget::logDumpZone(PlayerLogic *player, CardZoneLogic *zone, int numberCards, bool isReversed)
{
if (numberCards == -1) {
appendHtmlServerMessage(tr("%1 is looking at %2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(zone->getPlayer() == player, CaseLookAtZone)));
} else {
appendHtmlServerMessage(
tr("%1 is looking at the %4 %3 card(s) %2.", "top card for singular, top %3 cards for plural", numberCards)
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone))
.arg("<font class=\"blue\">" + QString::number(numberCards) + "</font>")
.arg(isReversed ? tr("bottom") : tr("top")));
}
}
void MessageLogWidget::logFlipCard(PlayerLogic *player, QString cardName, bool faceDown)
{
if (faceDown) {
appendHtmlServerMessage(
tr("%1 turns %2 face-down.").arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardLink(cardName)));
} else {
appendHtmlServerMessage(
tr("%1 turns %2 face-up.").arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardLink(cardName)));
}
}
void MessageLogWidget::logGameClosed()
{
appendHtmlServerMessage(tr("The game has been closed."));
}
void MessageLogWidget::logGameStart()
{
appendHtmlServerMessage(tr("The game has started."));
}
void MessageLogWidget::logGameFlooded()
{
appendMessage(tr("You are flooding the game. Please wait a couple of seconds."));
}
void MessageLogWidget::logJoin(PlayerLogic *player)
{
soundEngine->playSound("player_join");
appendHtmlServerMessage(tr("%1 has joined the game.").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
}
void MessageLogWidget::logJoinSpectator(QString name)
{
soundEngine->playSound("spectator_join");
appendHtmlServerMessage(tr("%1 is now watching the game.").arg(sanitizeHtml(std::move(name))));
}
void MessageLogWidget::logKicked()
{
appendHtmlServerMessage(tr("You have been kicked out of the game."), true);
}
void MessageLogWidget::logLeave(PlayerLogic *player, QString reason)
{
soundEngine->playSound("player_leave");
appendHtmlServerMessage(tr("%1 has left the game (%2).")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()), sanitizeHtml(std::move(reason))),
true);
}
void MessageLogWidget::logLeaveSpectator(QString name, QString reason)
{
soundEngine->playSound("spectator_leave");
appendHtmlServerMessage(tr("%1 is not watching the game any more (%2).")
.arg(sanitizeHtml(std::move(name)), sanitizeHtml(std::move(reason))));
}
void MessageLogWidget::logNotReadyStart(PlayerLogic *player)
{
appendHtmlServerMessage(
tr("%1 is not ready to start the game any more.").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
}
void MessageLogWidget::logMulligan(PlayerLogic *player, int number)
{
if (!player) {
return;
}
if (number > 0) {
appendHtmlServerMessage(tr("%1 shuffles their deck and draws a new hand of %2 card(s).", "", number)
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(number));
} else {
appendHtmlServerMessage(
tr("%1 shuffles their deck and draws a new hand.").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
}
}
void MessageLogWidget::logReplayStarted(int gameId)
{
appendHtmlServerMessage(tr("You are watching a replay of game #%1.").arg(gameId));
}
void MessageLogWidget::logReadyStart(PlayerLogic *player)
{
appendHtmlServerMessage(tr("%1 is ready to start the game.").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
}
void MessageLogWidget::logRevealCards(PlayerLogic *player,
CardZoneLogic *zone,
int cardId,
QString cardName,
PlayerLogic *otherPlayer,
bool faceDown,
int amount,
bool isLentToAnotherPlayer)
{
// getFromStr uses cardname.empty() to check if it should contain the start zone, it's not actually used
QPair<QString, QString> temp = getFromStr(zone, amount == 1 ? cardName : QString::number(amount), cardId, false);
bool cardNameContainsStartZone = false;
if (!temp.first.isEmpty()) {
cardNameContainsStartZone = true;
cardName = temp.first;
}
QString fromStr = temp.second;
QString cardStr;
if (cardNameContainsStartZone) {
cardStr = cardName;
} else if (cardName.isEmpty()) {
if (amount == 0) {
cardStr = tr("cards", "an unknown amount of cards");
} else {
cardStr = tr("%1 card(s)", "a card for singular, %1 cards for plural", amount)
.arg("<font class=\"blue\">" + QString::number(amount) + "</font>");
}
} else {
cardStr = cardLink(cardName);
}
if (cardId == -1) {
if (otherPlayer) {
if (isLentToAnotherPlayer) {
appendHtmlServerMessage(tr("%1 lends %2 to %3.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(true, CaseRevealZone))
.arg(sanitizeHtml(otherPlayer->getPlayerInfo()->getName())));
} else {
appendHtmlServerMessage(tr("%1 reveals %2 to %3.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(true, CaseRevealZone))
.arg(sanitizeHtml(otherPlayer->getPlayerInfo()->getName())));
}
} else {
appendHtmlServerMessage(tr("%1 reveals %2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(true, CaseRevealZone)));
}
} else if (cardId == -2) {
if (otherPlayer) {
appendHtmlServerMessage(tr("%1 randomly reveals %2%3 to %4.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardStr)
.arg(fromStr)
.arg(sanitizeHtml(otherPlayer->getPlayerInfo()->getName())));
} else {
appendHtmlServerMessage(tr("%1 randomly reveals %2%3.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardStr)
.arg(fromStr));
}
} else {
if (faceDown && player == otherPlayer) {
if (cardName.isEmpty()) {
appendHtmlServerMessage(tr("%1 peeks at face down card #%2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardId));
} else {
appendHtmlServerMessage(tr("%1 peeks at face down card #%2: %3.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardId)
.arg(cardStr));
}
} else if (otherPlayer) {
appendHtmlServerMessage(tr("%1 reveals %2%3 to %4.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardStr)
.arg(fromStr)
.arg(sanitizeHtml(otherPlayer->getPlayerInfo()->getName())));
} else {
appendHtmlServerMessage(
tr("%1 reveals %2%3.").arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardStr).arg(fromStr));
}
}
}
void MessageLogWidget::logReverseTurn(PlayerLogic *player, bool reversed)
{
appendHtmlServerMessage(tr("%1 reversed turn order, now it's %2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(reversed ? tr("reversed") : tr("normal")));
}
void MessageLogWidget::logRollDie(PlayerLogic *player, int sides, const QList<uint> &rolls)
{
if (rolls.length() == 1) {
const auto roll = rolls.at(0);
if (sides == 2) {
QString coinOptions[2] = {tr("Heads") + " (1)", tr("Tails") + " (2)"};
appendHtmlServerMessage(tr("%1 flipped a coin. It landed as %2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg("<font class=\"blue\">" + coinOptions[roll - 1] + "</font>"));
} else {
appendHtmlServerMessage(tr("%1 rolls a %2 with a %3-sided die.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg("<font class=\"blue\">" + QString::number(roll) + "</font>")
.arg("<font class=\"blue\">" + QString::number(sides) + "</font>"));
}
} else {
if (sides == 2) {
appendHtmlServerMessage(tr("%1 flips %2 coins. There are %3 heads and %4 tails.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg("<font class=\"blue\">" + QString::number(rolls.length()) + "</font>")
.arg("<font class=\"blue\">" + QString::number(rolls.count(1)) + "</font>")
.arg("<font class=\"blue\">" + QString::number(rolls.count(2)) + "</font>"));
} else {
QStringList rollsStrings;
for (const auto &roll : rolls) {
rollsStrings.append(QString::number(roll));
}
appendHtmlServerMessage(tr("%1 rolls a %2-sided dice %3 times: %4.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg("<font class=\"blue\">" + QString::number(sides) + "</font>")
.arg("<font class=\"blue\">" + QString::number(rolls.length()) + "</font>")
.arg("<font class=\"blue\">" + rollsStrings.join(", ") + "</font>"));
}
}
soundEngine->playSound("roll_dice");
}
void MessageLogWidget::logSay(PlayerLogic *player, QString message)
{
appendMessage(std::move(message), {}, *player->getPlayerInfo()->getUserInfo(), true);
}
void MessageLogWidget::logSetActivePhase(int phaseNumber)
{
Phase phase = Phases::getPhase(phaseNumber);
soundEngine->playSound(phase.soundFileName);
appendHtml("<font color=\"" + phase.color + "\"><b>" + QDateTime::currentDateTime().toString("[hh:mm:ss] ") +
phase.getName() + "</b></font>");
}
void MessageLogWidget::logSetActivePlayer(PlayerLogic *player)
{
appendHtml("<br><font color=\"green\"><b>" + QDateTime::currentDateTime().toString("[hh:mm:ss] ") +
QString(tr("%1's turn.")).arg(player->getPlayerInfo()->getName()) + "</b></font><br>");
}
void MessageLogWidget::logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation)
{
appendHtmlServerMessage(
QString(tr("%1 sets annotation of %2 to %3."))
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardLink(card->getName()))
.arg(QString("&quot;<font class=\"blue\">%1</font>&quot;").arg(sanitizeHtml(std::move(newAnnotation)))));
}
void MessageLogWidget::logSetCardCounter(PlayerLogic *player, QString cardName, int counterId, int value, int oldValue)
{
QString finalStr;
int delta = abs(oldValue - value);
if (value > oldValue) {
finalStr = tr("%1 places %2 %3%4 counter(s) on %5 (now %6).", "", delta);
} else {
finalStr = tr("%1 removes %2 %3%4 counter(s) from %5 (now %6).", "", delta);
}
auto &cardCounterSettings = SettingsCache::instance().cardCounters();
QString hex = cardCounterSettings.color(counterId).name();
appendHtmlServerMessage(finalStr.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg("<font class=\"blue\">" + QString::number(delta) + "</font>")
.arg("<font color=\"" + hex + "\">●</font>")
.arg(cardCounterSettings.displayName(counterId))
.arg(cardLink(std::move(cardName)))
.arg(value));
}
void MessageLogWidget::logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue)
{
if (counterName == "life") {
soundEngine->playSound("life_change");
}
QString counterDisplayName = TranslateCounterName::getDisplayName(counterName);
appendHtmlServerMessage(tr("%1 sets counter %2 to %3 (%4%5).")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(QString("<font class=\"blue\">%1</font>").arg(sanitizeHtml(counterDisplayName)))
.arg(QString("<font class=\"blue\">%1</font>").arg(value))
.arg(value > oldValue ? "+" : "")
.arg(value - oldValue));
}
void MessageLogWidget::logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap)
{
QString str;
if (doesntUntap) {
str = tr("%1 sets %2 to not untap normally.");
} else {
str = tr("%1 sets %2 to untap normally.");
}
appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardLink(card->getName())));
}
void MessageLogWidget::logSetPT(PlayerLogic *player, CardItem *card, QString newPT)
{
if (currentContext == MessageContext_MoveCard) {
return;
}
QString name = card->getName();
if (name.isEmpty()) {
name = QString("<font class=\"blue\">card #%1</font>").arg(sanitizeHtml(QString::number(card->getId())));
} else {
name = cardLink(name);
}
QString playerName = sanitizeHtml(player->getPlayerInfo()->getName());
if (newPT.isEmpty()) {
appendHtmlServerMessage(tr("%1 removes the PT of %2.").arg(playerName).arg(name));
} else {
QString oldPT = card->getPT();
if (oldPT.isEmpty()) {
appendHtmlServerMessage(
tr("%1 changes the PT of %2 from nothing to %4.").arg(playerName).arg(name).arg(newPT));
} else {
appendHtmlServerMessage(
tr("%1 changes the PT of %2 from %3 to %4.").arg(playerName).arg(name).arg(oldPT).arg(newPT));
}
}
}
void MessageLogWidget::logSetSideboardLock(PlayerLogic *player, bool locked)
{
if (locked) {
appendHtmlServerMessage(
tr("%1 has locked their sideboard.").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
} else {
appendHtmlServerMessage(
tr("%1 has unlocked their sideboard.").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
}
}
void MessageLogWidget::logSetTapped(PlayerLogic *player, CardItem *card, bool tapped)
{
if (currentContext == MessageContext_MoveCard) {
return;
}
if (tapped) {
soundEngine->playSound("tap_card");
} else {
soundEngine->playSound("untap_card");
}
QString str;
if (!card) {
appendHtmlServerMessage((tapped ? tr("%1 taps their permanents.") : tr("%1 untaps their permanents."))
.arg(sanitizeHtml(player->getPlayerInfo()->getName())));
} else {
appendHtmlServerMessage((tapped ? tr("%1 taps %2.") : tr("%1 untaps %2."))
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardLink(card->getName())));
}
}
void MessageLogWidget::logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end)
{
if (currentContext == MessageContext_Mulligan) {
return;
}
soundEngine->playSound("shuffle");
// start and end are indexes into the portion of the deck that was shuffled
// with negitive numbers counging from the bottom up.
if (start == 0 && end == -1) {
appendHtmlServerMessage(tr("%1 shuffles %2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(true, CaseShuffleZone)));
} else if (start < 0 && end == -1) {
appendHtmlServerMessage(tr("%1 shuffles the bottom %3 cards of %2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(true, CaseShuffleZone))
.arg(-start));
} else if (start < 0 && end > 0) {
appendHtmlServerMessage(tr("%1 shuffles the top %3 cards of %2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(true, CaseShuffleZone))
.arg(end + 1));
} else {
appendHtmlServerMessage(tr("%1 shuffles cards %3 - %4 of %2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(zone->getTranslatedName(true, CaseShuffleZone))
.arg(start)
.arg(end));
}
}
void MessageLogWidget::logSpectatorSay(const ServerInfo_User &spectator, QString message)
{
appendMessage(std::move(message), {}, spectator, false);
}
void MessageLogWidget::logUnattachCard(PlayerLogic *player, QString cardName)
{
appendHtmlServerMessage(tr("%1 unattaches %2.")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(cardLink(std::move(cardName))));
}
void MessageLogWidget::logUndoDraw(PlayerLogic *player, QString cardName)
{
if (cardName.isEmpty()) {
appendHtmlServerMessage(tr("%1 undoes their last draw.").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
} else {
appendHtmlServerMessage(
tr("%1 undoes their last draw (%2).")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
.arg(QString("<a href=\"card://%1\">%2</a>").arg(sanitizeHtml(cardName)).arg(sanitizeHtml(cardName))));
}
}
void MessageLogWidget::logUndoDrawFailed(PlayerLogic *player)
{
appendHtmlServerMessage(
tr("%1 failed to undo their last draw.").arg(sanitizeHtml(player->getPlayerInfo()->getName())));
}
void MessageLogWidget::setContextJudgeName(QString name)
{
messagePrefix = QString("<span style=\"color:black\">");
messageSuffix = QString("</span> [<img height=12 src=\"theme:icons/scales\"> %1]").arg(sanitizeHtml(name));
}
void MessageLogWidget::appendHtmlServerMessage(const QString &html, bool optionalIsBold, QString optionalFontColor)
{
ChatView::appendHtmlServerMessage(messagePrefix + html + messageSuffix, optionalIsBold, optionalFontColor);
}
void MessageLogWidget::connectToPlayerEventHandler(PlayerEventHandler *playerEventHandler)
{
connect(playerEventHandler, &PlayerEventHandler::logSay, this, &MessageLogWidget::logSay);
connect(playerEventHandler, &PlayerEventHandler::logShuffle, this, &MessageLogWidget::logShuffle);
connect(playerEventHandler, &PlayerEventHandler::logRollDie, this, &MessageLogWidget::logRollDie);
connect(playerEventHandler, &PlayerEventHandler::logCreateArrow, this, &MessageLogWidget::logCreateArrow);
connect(playerEventHandler, &PlayerEventHandler::logCreateToken, this, &MessageLogWidget::logCreateToken);
connect(playerEventHandler, &PlayerEventHandler::logSetCounter, this, &MessageLogWidget::logSetCounter);
connect(playerEventHandler, &PlayerEventHandler::logSetCardCounter, this, &MessageLogWidget::logSetCardCounter);
connect(playerEventHandler, &PlayerEventHandler::logSetTapped, this, &MessageLogWidget::logSetTapped);
connect(playerEventHandler, &PlayerEventHandler::logSetDoesntUntap, this, &MessageLogWidget::logSetDoesntUntap);
connect(playerEventHandler, &PlayerEventHandler::logSetPT, this, &MessageLogWidget::logSetPT);
connect(playerEventHandler, &PlayerEventHandler::logSetAnnotation, this, &MessageLogWidget::logSetAnnotation);
connect(playerEventHandler, &PlayerEventHandler::logMoveCard, this, &MessageLogWidget::logMoveCard);
connect(playerEventHandler, &PlayerEventHandler::logFlipCard, this, &MessageLogWidget::logFlipCard);
connect(playerEventHandler, &PlayerEventHandler::logDestroyCard, this, &MessageLogWidget::logDestroyCard);
connect(playerEventHandler, &PlayerEventHandler::logAttachCard, this, &MessageLogWidget::logAttachCard);
connect(playerEventHandler, &PlayerEventHandler::logUnattachCard, this, &MessageLogWidget::logUnattachCard);
connect(playerEventHandler, &PlayerEventHandler::logDumpZone, this, &MessageLogWidget::logDumpZone);
connect(playerEventHandler, &PlayerEventHandler::logDrawCards, this, &MessageLogWidget::logDrawCards);
connect(playerEventHandler, &PlayerEventHandler::logUndoDraw, this, &MessageLogWidget::logUndoDraw);
connect(playerEventHandler, &PlayerEventHandler::logUndoDrawFailed, this, &MessageLogWidget::logUndoDrawFailed);
connect(playerEventHandler, &PlayerEventHandler::logRevealCards, this, &MessageLogWidget::logRevealCards);
connect(playerEventHandler, &PlayerEventHandler::logAlwaysRevealTopCard, this,
&MessageLogWidget::logAlwaysRevealTopCard);
connect(playerEventHandler, &PlayerEventHandler::logAlwaysLookAtTopCard, this,
&MessageLogWidget::logAlwaysLookAtTopCard);
}
MessageLogWidget::MessageLogWidget(TabSupervisor *_tabSupervisor, AbstractGame *_game, QWidget *parent)
: ChatView(_tabSupervisor, _game, true, parent), currentContext(MessageContext_None)
{
}

View file

@ -1,109 +0,0 @@
/**
* @file message_log_widget.h
* @ingroup GameWidgets
*/
//! \todo Document this file.
#ifndef MESSAGELOGWIDGET_H
#define MESSAGELOGWIDGET_H
#include "../../interface/widgets/server/chat_view/chat_view.h"
#include "../zones/card_zone_logic.h"
class AbstractGame;
class CardItem;
class GameEventContext;
class PlayerLogic;
class PlayerEventHandler;
class MessageLogWidget : public ChatView
{
Q_OBJECT
private:
enum MessageContext
{
MessageContext_None,
MessageContext_MoveCard,
MessageContext_Mulligan
};
MessageContext currentContext;
QString messagePrefix, messageSuffix;
static QPair<QString, QString> getFromStr(CardZoneLogic *zone, QString cardName, int position, bool ownerChange);
public:
void connectToPlayerEventHandler(PlayerEventHandler *player);
MessageLogWidget(TabSupervisor *_tabSupervisor, AbstractGame *_game, QWidget *parent = nullptr);
public slots:
void containerProcessingDone();
void containerProcessingStarted(const GameEventContext &context);
void logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal);
void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal);
void logAttachCard(PlayerLogic *player, QString cardName, PlayerLogic *targetPlayer, QString targetCardName);
void logConcede(int playerId);
void logUnconcede(int playerId);
void logConnectionStateChanged(PlayerLogic *player, bool connectionState);
void logCreateArrow(PlayerLogic *player,
PlayerLogic *startPlayer,
QString startCard,
PlayerLogic *targetPlayer,
QString targetCard,
bool playerTarget);
void logCreateToken(PlayerLogic *player, QString cardName, QString pt, bool faceDown);
void logDeckSelect(PlayerLogic *player, QString deckHash, int sideboardSize);
void logDestroyCard(PlayerLogic *player, QString cardName);
void logDrawCards(PlayerLogic *player, int number, bool deckIsEmpty);
void logDumpZone(PlayerLogic *player, CardZoneLogic *zone, int numberCards, bool isReversed = false);
void logFlipCard(PlayerLogic *player, QString cardName, bool faceDown);
void logGameClosed();
void logGameStart();
void logGameFlooded();
void logJoin(PlayerLogic *player);
void logJoinSpectator(QString name);
void logKicked();
void logLeave(PlayerLogic *player, QString reason);
void logLeaveSpectator(QString name, QString reason);
void logNotReadyStart(PlayerLogic *player);
void logMoveCard(PlayerLogic *player,
CardItem *card,
CardZoneLogic *startZone,
int oldX,
CardZoneLogic *targetZone,
int newX);
void logMulligan(PlayerLogic *player, int number);
void logReplayStarted(int gameId);
void logReadyStart(PlayerLogic *player);
void logRevealCards(PlayerLogic *player,
CardZoneLogic *zone,
int cardId,
QString cardName,
PlayerLogic *otherPlayer,
bool faceDown,
int amount,
bool isLentToAnotherPlayer);
void logReverseTurn(PlayerLogic *player, bool reversed);
void logRollDie(PlayerLogic *player, int sides, const QList<uint> &rolls);
void logSay(PlayerLogic *player, QString message);
void logSetActivePhase(int phase);
void logSetActivePlayer(PlayerLogic *player);
void logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation);
void logSetCardCounter(PlayerLogic *player, QString cardName, int counterId, int value, int oldValue);
void logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue);
void logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap);
void logSetPT(PlayerLogic *player, CardItem *card, QString newPT);
void logSetSideboardLock(PlayerLogic *player, bool locked);
void logSetTapped(PlayerLogic *player, CardItem *card, bool tapped);
void logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end);
void logSpectatorSay(const ServerInfo_User &spectator, QString message);
void logUnattachCard(PlayerLogic *player, QString cardName);
void logUndoDraw(PlayerLogic *player, QString cardName);
void logUndoDrawFailed(PlayerLogic *player);
void setContextJudgeName(QString player);
void appendHtmlServerMessage(const QString &html,
bool optionalIsBold = false,
QString optionalFontColor = QString()) override;
};
#endif

View file

@ -1,286 +0,0 @@
#include "phases_toolbar.h"
#include "../interface/pixel_map_generator.h"
#include <QAction>
#include <QDebug>
#include <QPainter>
#include <QPen>
#include <QTimer>
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
#include <libcockatrice/protocol/pb/command_next_turn.pb.h>
#include <libcockatrice/protocol/pb/command_set_active_phase.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
#include <libcockatrice/utility/zone_names.h>
PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_doubleClickAction, bool _highlightable)
: QObject(), QGraphicsItem(parent), name(_name), active(false), highlightable(_highlightable),
activeAnimationCounter(0), doubleClickAction(_doubleClickAction), width(50)
{
if (highlightable) {
activeAnimationTimer = new QTimer(this);
connect(activeAnimationTimer, &QTimer::timeout, this, &PhaseButton::updateAnimation);
activeAnimationTimer->setSingleShot(false);
} else {
activeAnimationCounter = 9;
}
setCacheMode(DeviceCoordinateCache);
}
QRectF PhaseButton::boundingRect() const
{
return {0, 0, width, width};
}
void PhaseButton::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
QRectF iconRect = boundingRect().adjusted(3, 3, -3, -3);
QRectF translatedIconRect = painter->combinedTransform().mapRect(iconRect);
qreal scaleFactor = translatedIconRect.width() / iconRect.width();
QPixmap iconPixmap = PhasePixmapGenerator::generatePixmap(qRound(translatedIconRect.height()), name);
painter->setBrush(QColor(static_cast<int>(220 * (activeAnimationCounter / 10.0)),
static_cast<int>(220 * (activeAnimationCounter / 10.0)),
static_cast<int>(220 * (activeAnimationCounter / 10.0))));
painter->setPen(Qt::gray);
painter->drawRect(0, 0, static_cast<int>(width - 1), static_cast<int>(width - 1));
painter->save();
resetPainterTransform(painter);
painter->drawPixmap(iconPixmap.rect().translated(qRound(3 * scaleFactor), qRound(3 * scaleFactor)), iconPixmap,
iconPixmap.rect());
painter->restore();
painter->setBrush(QColor(0, 0, 0, static_cast<int>(255 * ((10 - activeAnimationCounter) / 15.0))));
painter->setPen(Qt::gray);
painter->drawRect(0, 0, static_cast<int>(width - 1), static_cast<int>(width - 1));
}
void PhaseButton::setWidth(double _width)
{
prepareGeometryChange();
width = _width;
}
void PhaseButton::setActive(bool _active)
{
if ((active == _active) || !highlightable) {
return;
}
active = _active;
activeAnimationTimer->start(25);
}
void PhaseButton::updateAnimation()
{
if (!highlightable) {
return;
}
// the counter ticks up to 10 when active and down to 0 when inactive
if (active && activeAnimationCounter < 10) {
++activeAnimationCounter;
} else if (!active && activeAnimationCounter > 0) {
--activeAnimationCounter;
} else {
activeAnimationTimer->stop();
}
update();
}
void PhaseButton::mousePressEvent(QGraphicsSceneMouseEvent * /*event*/)
{
emit clicked();
}
void PhaseButton::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * /*event*/)
{
triggerDoubleClickAction();
}
void PhaseButton::triggerDoubleClickAction()
{
if (doubleClickAction) {
doubleClickAction->trigger();
}
}
PhasesToolbar::PhasesToolbar(QGraphicsItem *parent)
: QGraphicsItem(parent), width(100), height(100), ySpacing(1), symbolSize(8)
{
auto *aUntapAll = new QAction(this);
connect(aUntapAll, &QAction::triggered, this, &PhasesToolbar::actUntapAll);
auto *aDrawCard = new QAction(this);
connect(aDrawCard, &QAction::triggered, this, &PhasesToolbar::actDrawCard);
PhaseButton *untapButton = new PhaseButton("untap", this, aUntapAll);
PhaseButton *upkeepButton = new PhaseButton("upkeep", this);
PhaseButton *drawButton = new PhaseButton("draw", this, aDrawCard);
PhaseButton *main1Button = new PhaseButton("main1", this);
PhaseButton *combatStartButton = new PhaseButton("combat_start", this);
PhaseButton *combatAttackersButton = new PhaseButton("combat_attackers", this);
PhaseButton *combatBlockersButton = new PhaseButton("combat_blockers", this);
PhaseButton *combatDamageButton = new PhaseButton("combat_damage", this);
PhaseButton *combatEndButton = new PhaseButton("combat_end", this);
PhaseButton *main2Button = new PhaseButton("main2", this);
PhaseButton *cleanupButton = new PhaseButton("cleanup", this);
buttonList << untapButton << upkeepButton << drawButton << main1Button << combatStartButton << combatAttackersButton
<< combatBlockersButton << combatDamageButton << combatEndButton << main2Button << cleanupButton;
for (auto &i : buttonList) {
connect(i, &PhaseButton::clicked, this, &PhasesToolbar::phaseButtonClicked);
}
nextTurnButton = new PhaseButton("nextturn", this, nullptr, false);
connect(nextTurnButton, &PhaseButton::clicked, this, &PhasesToolbar::actNextTurn);
rearrangeButtons();
retranslateUi();
}
QRectF PhasesToolbar::boundingRect() const
{
return {0, 0, width, height};
}
void PhasesToolbar::retranslateUi()
{
for (int i = 0; i < buttonList.size(); ++i) {
buttonList[i]->setToolTip(getLongPhaseName(i));
}
}
QString PhasesToolbar::getLongPhaseName(int phase) const
{
switch (phase) {
case 0:
return tr("Untap step");
case 1:
return tr("Upkeep step");
case 2:
return tr("Draw step");
case 3:
return tr("First main phase");
case 4:
return tr("Beginning of combat step");
case 5:
return tr("Declare attackers step");
case 6:
return tr("Declare blockers step");
case 7:
return tr("Combat damage step");
case 8:
return tr("End of combat step");
case 9:
return tr("Second main phase");
case 10:
return tr("End of turn step");
default:
return QString();
}
}
void PhasesToolbar::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
painter->fillRect(boundingRect(), QColor(50, 50, 50));
}
const double PhasesToolbar::marginSize = 3;
void PhasesToolbar::rearrangeButtons()
{
for (auto &i : buttonList) {
i->setWidth(symbolSize);
}
nextTurnButton->setWidth(symbolSize);
double y = marginSize;
buttonList[0]->setPos(marginSize, y);
buttonList[1]->setPos(marginSize, y += symbolSize);
buttonList[2]->setPos(marginSize, y += symbolSize);
y += ySpacing;
buttonList[3]->setPos(marginSize, y += symbolSize);
y += ySpacing;
buttonList[4]->setPos(marginSize, y += symbolSize);
buttonList[5]->setPos(marginSize, y += symbolSize);
buttonList[6]->setPos(marginSize, y += symbolSize);
buttonList[7]->setPos(marginSize, y += symbolSize);
buttonList[8]->setPos(marginSize, y += symbolSize);
y += ySpacing;
buttonList[9]->setPos(marginSize, y += symbolSize);
y += ySpacing;
buttonList[10]->setPos(marginSize, y += symbolSize);
y += ySpacing;
y += ySpacing;
nextTurnButton->setPos(marginSize, y + symbolSize);
}
void PhasesToolbar::setHeight(double _height)
{
prepareGeometryChange();
height = _height;
ySpacing = (height - 2 * marginSize) / (buttonCount * 5 + spaceCount);
symbolSize = ySpacing * 5;
width = symbolSize + 2 * marginSize;
rearrangeButtons();
}
void PhasesToolbar::setActivePhase(int phase)
{
if (phase >= buttonList.size()) {
return;
}
for (int i = 0; i < buttonList.size(); ++i) {
buttonList[i]->setActive(i == phase);
}
}
void PhasesToolbar::triggerPhaseAction(int phase)
{
if (0 <= phase && phase < buttonList.size()) {
buttonList[phase]->triggerDoubleClickAction();
}
}
void PhasesToolbar::phaseButtonClicked()
{
auto *button = qobject_cast<PhaseButton *>(sender());
if (button->getActive()) {
button->triggerDoubleClickAction();
}
Command_SetActivePhase cmd;
cmd.set_phase(static_cast<google::protobuf::uint32>(buttonList.indexOf(button)));
emit sendGameCommand(cmd, -1);
}
void PhasesToolbar::actNextTurn()
{
emit sendGameCommand(Command_NextTurn(), -1);
}
void PhasesToolbar::actUntapAll()
{
Command_SetCardAttr cmd;
cmd.set_zone(ZoneNames::TABLE);
cmd.set_attribute(AttrTapped);
cmd.set_attr_value("0");
emit sendGameCommand(cmd, -1);
}
void PhasesToolbar::actDrawCard()
{
Command_DrawCards cmd;
cmd.set_number(1);
emit sendGameCommand(cmd, -1);
}

View file

@ -1,107 +0,0 @@
/**
* @file phases_toolbar.h
* @ingroup GameGraphics
* @ingroup GameWidgets
*/
//! \todo Document this file.
#ifndef PHASESTOOLBAR_H
#define PHASESTOOLBAR_H
#include "../game_graphics/board/abstract_graphics_item.h"
#include <QFrame>
#include <QGraphicsObject>
#include <QList>
namespace google
{
namespace protobuf
{
class Message;
}
} // namespace google
class PlayerLogic;
class GameCommand;
class PhaseButton : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
private:
QString name;
bool active, highlightable;
int activeAnimationCounter;
QTimer *activeAnimationTimer;
QAction *doubleClickAction;
double width;
// void updatePixmap(QPixmap &pixmap);
private slots:
void updateAnimation();
public:
explicit PhaseButton(const QString &_name,
QGraphicsItem *parent = nullptr,
QAction *_doubleClickAction = nullptr,
bool _highlightable = true);
[[nodiscard]] QRectF boundingRect() const override;
void setWidth(double _width);
void setActive(bool _active);
[[nodiscard]] bool getActive() const
{
return active;
}
void triggerDoubleClickAction();
signals:
void clicked();
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
};
class PhasesToolbar : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
private:
QList<PhaseButton *> buttonList;
PhaseButton *nextTurnButton;
double width, height, ySpacing, symbolSize;
static const int buttonCount = 12;
static const int spaceCount = 6;
static const double marginSize;
void rearrangeButtons();
public:
explicit PhasesToolbar(QGraphicsItem *parent = nullptr);
[[nodiscard]] QRectF boundingRect() const override;
void retranslateUi();
void setHeight(double _height);
[[nodiscard]] double getWidth() const
{
return width;
}
[[nodiscard]] int phaseCount() const
{
return buttonList.size();
}
[[nodiscard]] QString getLongPhaseName(int phase) const;
public slots:
void setActivePhase(int phase);
void triggerPhaseAction(int phase);
private slots:
void phaseButtonClicked();
void actNextTurn();
void actUntapAll();
void actDrawCard();
signals:
void sendGameCommand(const ::google::protobuf::Message &command, int playerId);
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) override;
};
#endif

View file

@ -1,28 +0,0 @@
/**
* @file card_menu_action_type.h
* @ingroup GameMenusPlayers
*/
//! \todo Document this file.
#ifndef COCKATRICE_CARD_MENU_ACTION_TYPE_H
#define COCKATRICE_CARD_MENU_ACTION_TYPE_H
enum CardMenuActionType
{
// Per-card attribute actions (must be <= cmClone for cardMenuAction() dispatch)
cmTap,
cmUntap,
cmDoesntUntap,
cmFlip,
cmPeek,
cmClone,
// Move actions (must be > cmClone for cardMenuAction() dispatch)
cmMoveToTopLibrary,
cmMoveToBottomLibrary,
cmMoveToHand,
cmMoveToGraveyard,
cmMoveToExile,
cmMoveToTable
};
#endif // COCKATRICE_CARD_MENU_ACTION_TYPE_H

View file

@ -1,32 +0,0 @@
/**
* @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;
/** @brief Bind keyboard shortcuts. Called when this player gains focus. */
virtual void setShortcutsActive() = 0;
/** @brief Unbind keyboard shortcuts. Called when this player loses focus. */
virtual void setShortcutsInactive() = 0;
/** @brief Retranslate all user-visible strings. Called on language change. */
virtual void retranslateUi() = 0;
};
#endif // COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H

View file

@ -1,551 +0,0 @@
#include "card_menu.h"
#include "../../../client/settings/card_counter_settings.h"
#include "../../../interface/widgets/tabs/tab_game.h"
#include "../../board/card_item.h"
#include "../../zones/view_zone_logic.h"
#include "../card_menu_action_type.h"
#include "../player_actions.h"
#include "../player_graphics_item.h"
#include "../player_logic.h"
#include "move_menu.h"
#include "pt_menu.h"
#include <QPainter>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/utility/zone_names.h>
/**
* @brief Creates a circular icon filled with the specified color.
*/
static QIcon createCircleIcon(const QColor &color)
{
QPixmap pixmap(32, 32);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
painter.setBrush(color);
painter.drawEllipse(pixmap.rect());
return QIcon(pixmap);
}
template <typename Slot>
static QAction *makeAction(QObject *parent, Slot &&slot, bool checkable = false, bool checked = false)
{
auto *a = new QAction(parent);
a->setCheckable(checkable);
if (checkable) {
a->setChecked(checked);
}
QObject::connect(a, &QAction::triggered, parent, std::forward<Slot>(slot));
return a;
}
CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _shortcutsActive)
: player(_player), card(_card), shortcutsActive(_shortcutsActive)
{
const QList<PlayerLogic *> &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto playerToAdd : players) {
if (playerToAdd == player->getLogic()) {
continue;
}
playersInfo.append(qMakePair(playerToAdd->getPlayerInfo()->getName(), playerToAdd->getPlayerInfo()->getId()));
}
connect(player->getLogic()->getGame()->getPlayerManager(), &PlayerManager::playerRemoved, this,
&CardMenu::removePlayer);
auto *actions = player->getLogic()->getPlayerActions();
auto *gameScene = player->getGameScene();
// Single selection resolver used by all lambdas — called at trigger time
auto sel = [gameScene]() { return gameScene->selectedCards(); };
// Unified dispatcher for card menu actions
auto invoke = [actions, sel](CardMenuActionType type) {
return [actions, sel, type]() { actions->cardMenuAction(sel(), type); };
};
// Actions using invoke (type dispatch, need selection)
aTap = makeAction(this, invoke(cmTap));
aDoesntUntap = makeAction(this, invoke(cmDoesntUntap), /*checkable=*/true, card && card->getDoesntUntap());
aFlip = makeAction(this, invoke(cmFlip));
aPeek = makeAction(this, invoke(cmPeek));
aClone = makeAction(this, invoke(cmClone));
// Actions using selection directly
aUnattach = makeAction(this, [actions, sel]() { actions->actUnattach(sel()); });
aSetAnnotation = makeAction(this, [actions, sel]() { actions->actRequestSetAnnotationDialog(sel()); });
aPlay = makeAction(this, [actions, sel]() { actions->actPlay(sel()); });
aPlayFacedown = makeAction(this, [actions, sel]() { actions->actPlayFacedown(sel()); });
aHide = makeAction(this, [actions, sel]() { actions->actHide(sel()); });
aReduceLifeByPower = makeAction(this, [actions, sel]() { actions->actReduceLifeByPower(sel()); });
// Actions that use activeCard, not selection — direct connection
aAttach = new QAction(this);
aDrawArrow = new QAction(this);
aSelectAll = new QAction(this);
aSelectRow = new QAction(this);
aSelectColumn = new QAction(this);
connect(aAttach, &QAction::triggered, actions, &PlayerActions::actAttach);
connect(aDrawArrow, &QAction::triggered, actions, &PlayerActions::actDrawArrow);
connect(aSelectAll, &QAction::triggered, actions, &PlayerActions::actSelectAll);
connect(aSelectRow, &QAction::triggered, actions, &PlayerActions::actSelectRow);
connect(aSelectColumn, &QAction::triggered, actions, &PlayerActions::actSelectColumn);
aRevealToAll = new QAction(this);
mCardCounters = new QMenu;
// Card counters
for (int i = 0; i < 6; ++i) {
QColor color = SettingsCache::instance().cardCounters().color(i);
QIcon circleIcon = createCircleIcon(color);
auto *addAction = makeAction(this, [actions, sel, i]() { actions->actAddCardCounter(sel(), i); });
addAction->setIcon(circleIcon);
aAddCounter.append(addAction);
auto *removeAction = makeAction(this, [actions, sel, i]() { actions->actRemoveCardCounter(sel(), i); });
removeAction->setIcon(circleIcon);
aRemoveCounter.append(removeAction);
auto *setAction = makeAction(this, [actions, sel, i]() { actions->actRequestSetCardCounterDialog(sel(), i); });
setAction->setIcon(circleIcon);
aSetCounter.append(setAction);
}
setShortcutsActive();
retranslateUi();
if (card == nullptr) {
return;
}
bool revealedCard = false;
bool writeableCard = player->getLogic()->getPlayerInfo()->getLocalOrJudge();
if (auto *view = qobject_cast<ZoneViewZoneLogic *>(card->getZone())) {
if (view->getRevealZone()) {
if (view->getWriteableRevealZone()) {
writeableCard = true;
} else {
revealedCard = true;
}
}
}
if (revealedCard) {
addAction(aHide);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectColumn);
addRelatedCardView();
} else {
if (card->getZone()) {
if (card->getZone()->getName() == ZoneNames::TABLE) {
createTableMenu(writeableCard);
} else if (card->getZone()->getName() == ZoneNames::STACK) {
createStackMenu(writeableCard);
} else if (card->getZone()->getName() == ZoneNames::EXILE ||
card->getZone()->getName() == ZoneNames::GRAVE) {
createGraveyardOrExileMenu(writeableCard);
} else {
createHandOrCustomZoneMenu(writeableCard);
}
} else {
createZonelessMenu(writeableCard);
}
}
}
void CardMenu::removePlayer(PlayerLogic *playerToRemove)
{
for (auto it = playersInfo.begin(); it != playersInfo.end();) {
if (it->second == playerToRemove->getPlayerInfo()->getId()) {
it = playersInfo.erase(it);
} else {
++it;
}
}
}
void CardMenu::createTableMenu(bool canModifyCard)
{
// Card is on the battlefield
if (!canModifyCard) {
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aReduceLifeByPower);
addSeparator();
addAction(aSelectAll);
addAction(aSelectRow);
addRelatedCardView();
addRelatedCardActions();
return;
}
addAction(aTap);
addAction(aDoesntUntap);
addAction(aFlip);
if (card->getFaceDown()) {
addAction(aPeek);
}
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
addSeparator();
addAction(aAttach);
if (card->getAttachedTo()) {
addAction(aUnattach);
}
addAction(aDrawArrow);
addSeparator();
addMenu(new PtMenu(player));
addAction(aSetAnnotation);
addSeparator();
addAction(aReduceLifeByPower);
addSeparator();
addAction(aSelectAll);
addAction(aSelectRow);
addSeparator();
mCardCounters->clear();
for (int i = 0; i < aAddCounter.size(); ++i) {
mCardCounters->addSeparator();
mCardCounters->addAction(aAddCounter[i]);
if (card->getCounters().contains(i)) {
mCardCounters->addAction(aRemoveCounter[i]);
}
mCardCounters->addAction(aSetCounter[i]);
}
addSeparator();
addMenu(mCardCounters);
addRelatedCardView();
addRelatedCardActions();
}
void CardMenu::createStackMenu(bool canModifyCard)
{
// Card is on the stack
if (!canModifyCard) {
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addRelatedCardView();
addRelatedCardActions();
return;
}
addAction(aPlay);
addAction(aPlayFacedown);
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
addSeparator();
addAction(aAttach);
addAction(aDrawArrow);
addSeparator();
addAction(aSelectAll);
addRelatedCardView();
addRelatedCardActions();
}
void CardMenu::createGraveyardOrExileMenu(bool canModifyCard)
{
// Card is in the graveyard or exile
if (!canModifyCard) {
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectColumn);
addRelatedCardView();
addRelatedCardActions();
return;
}
addAction(aPlay);
addAction(aPlayFacedown);
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
addSeparator();
addAction(aAttach);
addAction(aDrawArrow);
addSeparator();
addAction(aSelectAll);
addAction(aSelectColumn);
addRelatedCardView();
addRelatedCardActions();
}
void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard)
{
if (!canModifyCard) {
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addRelatedCardView();
addRelatedCardActions();
return;
}
// Card is in hand or a custom zone specified by server
addAction(aPlay);
addAction(aPlayFacedown);
QMenu *revealMenu = addMenu(tr("Re&veal to..."));
initContextualPlayersMenu(revealMenu, aRevealToAll);
connect(revealMenu, &QMenu::triggered, this, [this](QAction *action) {
player->getLogic()->getPlayerActions()->actReveal(player->getGameScene()->selectedCards(), action);
});
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
// actions that are really wonky when done from deck or sideboard
if (card->getZone()->getName() == ZoneNames::HAND) {
addSeparator();
addAction(aAttach);
addAction(aDrawArrow);
}
addSeparator();
addAction(aSelectAll);
if (qobject_cast<ZoneViewZoneLogic *>(card->getZone())) {
addAction(aSelectColumn);
}
addRelatedCardView();
if (card->getZone()->getName() == ZoneNames::HAND) {
addRelatedCardActions();
}
}
void CardMenu::createZonelessMenu(bool canModifyCard)
{
if (canModifyCard) {
addMenu(new MoveMenu(player));
}
}
/**
* @brief Populates the menu with an action for each active player.
*
* The "all players" action is created separately, so it has to be passed into this function.
* It will be put at the top of the menu.
*
* @param menu The menu to add the player actions to.
* @param allPlayersAction The action for "all players".
*/
void CardMenu::initContextualPlayersMenu(QMenu *menu, QAction *allPlayersAction)
{
allPlayersAction->setData(-1);
menu->addAction(allPlayersAction);
menu->addSeparator();
for (const auto &playerInfo : playersInfo) {
menu->addAction(playerInfo.first)->setData(playerInfo.second);
}
}
void CardMenu::addRelatedCardView()
{
if (!card) {
return;
}
auto exactCard = card->getCard();
if (!exactCard) {
return;
}
bool atLeastOneGoodRelationFound = false;
QList<CardRelation *> relatedCards = exactCard.getInfo().getAllRelatedCards();
for (const CardRelation *cardRelation : relatedCards) {
CardInfoPtr relatedCard = CardDatabaseManager::query()->getCardInfo(cardRelation->getName());
if (relatedCard != nullptr) {
atLeastOneGoodRelationFound = true;
break;
}
}
if (!atLeastOneGoodRelationFound) {
return;
}
addSeparator();
auto viewRelatedCards = new QMenu(tr("View related cards"));
addMenu(viewRelatedCards);
for (const CardRelation *relatedCard : relatedCards) {
QString relatedCardName = relatedCard->getName();
CardRef cardRef = {relatedCardName, exactCard.getPrinting().getUuid()};
QAction *viewCard = viewRelatedCards->addAction(relatedCardName);
Q_UNUSED(viewCard);
connect(viewCard, &QAction::triggered, this, [this, cardRef] { emit cardInfoRequested(cardRef); });
}
}
void CardMenu::addRelatedCardActions()
{
if (!card) {
return;
}
auto exactCard = card->getCard();
if (!exactCard) {
return;
}
QList<CardRelation *> relatedCards = exactCard.getInfo().getAllRelatedCards();
if (relatedCards.isEmpty()) {
return;
}
addSeparator();
int index = 0;
QAction *createRelatedCards = nullptr;
for (const CardRelation *cardRelation : relatedCards) {
ExactCard relatedCard =
CardDatabaseManager::query()->getCardFromSameSet(cardRelation->getName(), card->getCard().getPrinting());
if (!relatedCard) {
relatedCard = CardDatabaseManager::query()->getCard({cardRelation->getName()});
}
if (!relatedCard) {
continue;
}
QString relatedCardName;
if (relatedCard.getInfo().getPowTough().size() > 0) {
relatedCardName = relatedCard.getInfo().getPowTough() + " " + relatedCard.getName(); // "n/n name"
} else {
relatedCardName = relatedCard.getName(); // "name"
}
QString text = tr("Token: ");
if (cardRelation->getDoesAttach()) {
text +=
tr(cardRelation->getDoesTransform() ? "Transform into " : "Attach to ") + "\"" + relatedCardName + "\"";
} else if (cardRelation->getIsVariable()) {
text += "X " + relatedCardName;
} else if (cardRelation->getDefaultCount() != 1) {
text += QString::number(cardRelation->getDefaultCount()) + "x " + relatedCardName;
} else {
text += relatedCardName;
}
if (createRelatedCards == nullptr) {
if (relatedCards.length() == 1) {
createRelatedCards = new QAction(text, this); // set actCreateAllRelatedCards with this text
break; // do not set an individual entry as there is only one entry
} else {
createRelatedCards = new QAction(tr("All tokens"), this);
}
}
auto *createRelated = new QAction(text, this);
createRelated->setData(QVariant(index++));
connect(createRelated, &QAction::triggered, player->getLogic()->getPlayerActions(),
&PlayerActions::actCreateRelatedCard);
addAction(createRelated);
}
if (createRelatedCards) {
if (shortcutsActive) {
createRelatedCards->setShortcuts(
SettingsCache::instance().shortcuts().getShortcut("Player/aCreateRelatedTokens"));
}
connect(createRelatedCards, &QAction::triggered, player->getLogic()->getPlayerActions(),
&PlayerActions::actCreateAllRelatedCards);
addAction(createRelatedCards);
}
}
void CardMenu::retranslateUi()
{
aSelectAll->setText(tr("&Select All"));
aSelectRow->setText(tr("S&elect Row"));
aSelectColumn->setText(tr("S&elect Column"));
aPlay->setText(tr("&Play"));
aHide->setText(tr("&Hide"));
aPlayFacedown->setText(tr("Play &Face Down"));
aRevealToAll->setText(tr("&All players"));
//: Turn sideways or back again
aTap->setText(tr("&Tap / Untap"));
aDoesntUntap->setText(tr("Skip &untapping"));
//: Turn face up/face down
aFlip->setText(tr("T&urn Over")); // Only the user facing names in client got renamed to "turn over"
// All code and proto bits are still unchanged (flip) for compatibility reasons
// A protocol rewrite with v3 could incorporate that, see #3100
aPeek->setText(tr("&Peek at card face"));
aClone->setText(tr("&Clone"));
aAttach->setText(tr("Attac&h to card..."));
aUnattach->setText(tr("Unattac&h"));
aDrawArrow->setText(tr("&Draw arrow..."));
aSetAnnotation->setText(tr("&Set annotation..."));
aReduceLifeByPower->setText(tr("Reduce life by power"));
mCardCounters->setTitle(tr("Ca&rd counters"));
auto &cardCounterSettings = SettingsCache::instance().cardCounters();
for (int i = 0; i < aAddCounter.size(); ++i) {
aAddCounter[i]->setText(tr("&Add counter (%1)").arg(cardCounterSettings.displayName(i)));
}
for (int i = 0; i < aRemoveCounter.size(); ++i) {
aRemoveCounter[i]->setText(tr("&Remove counter (%1)").arg(cardCounterSettings.displayName(i)));
}
for (int i = 0; i < aSetCounter.size(); ++i) {
aSetCounter[i]->setText(tr("&Set counters (%1)...").arg(cardCounterSettings.displayName(i)));
}
}
void CardMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aHide->setShortcuts(shortcuts.getShortcut("Player/aHide"));
aPlay->setShortcuts(shortcuts.getShortcut("Player/aPlay"));
aPlayFacedown->setShortcuts(shortcuts.getShortcut("Player/aPlayFacedown"));
aRevealToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealToAll"));
aTap->setShortcuts(shortcuts.getShortcut("Player/aTap"));
aDoesntUntap->setShortcuts(shortcuts.getShortcut("Player/aDoesntUntap"));
aFlip->setShortcuts(shortcuts.getShortcut("Player/aFlip"));
aPeek->setShortcuts(shortcuts.getShortcut("Player/aPeek"));
aClone->setShortcuts(shortcuts.getShortcut("Player/aClone"));
aAttach->setShortcuts(shortcuts.getShortcut("Player/aAttach"));
aUnattach->setShortcuts(shortcuts.getShortcut("Player/aUnattach"));
aDrawArrow->setShortcuts(shortcuts.getShortcut("Player/aDrawArrow"));
aSetAnnotation->setShortcuts(shortcuts.getShortcut("Player/aSetAnnotation"));
aReduceLifeByPower->setShortcuts(shortcuts.getShortcut("Player/aReduceLifeByPower"));
aSelectAll->setShortcuts(shortcuts.getShortcut("Player/aSelectAll"));
aSelectRow->setShortcuts(shortcuts.getShortcut("Player/aSelectRow"));
aSelectColumn->setShortcuts(shortcuts.getShortcut("Player/aSelectColumn"));
static const QStringList colorWords = {"Red", "Yellow", "Green", "Cyan", "Purple", "Magenta"};
for (int i = 0; i < aAddCounter.size(); i++) {
aAddCounter[i]->setShortcuts(shortcuts.getShortcut("Player/aCC" + colorWords[i]));
aRemoveCounter[i]->setShortcuts(shortcuts.getShortcut("Player/aRC" + colorWords[i]));
aSetCounter[i]->setShortcuts(shortcuts.getShortcut("Player/aSC" + colorWords[i]));
}
}

View file

@ -1,61 +0,0 @@
/**
* @file card_menu.h
* @ingroup GameMenusCards
*/
//! \todo Document this file.
#ifndef COCKATRICE_CARD_MENU_H
#define COCKATRICE_CARD_MENU_H
#include <QMenu>
#include <libcockatrice/utility/card_ref.h>
class CardItem;
class PlayerGraphicsItem;
class PlayerLogic;
class CardMenu : public QMenu
{
Q_OBJECT
signals:
void cardInfoRequested(const CardRef &cardRef);
public:
explicit CardMenu(PlayerGraphicsItem *player, const CardItem *card, bool shortcutsActive);
void removePlayer(PlayerLogic *playerToRemove);
void createTableMenu(bool canModifyCard);
void createStackMenu(bool canModifyCard);
void createGraveyardOrExileMenu(bool canModifyCard);
void createHandOrCustomZoneMenu(bool canModifyCard);
void createZonelessMenu(bool canModifyCard);
QMenu *mCardCounters;
QAction *aPlay, *aPlayFacedown;
QAction *aRevealToAll;
QAction *aHide;
QAction *aClone;
QAction *aSelectAll, *aSelectRow, *aSelectColumn;
QAction *aDrawArrow;
QAction *aTap, *aDoesntUntap;
QAction *aFlip, *aPeek;
QAction *aAttach, *aUnattach;
QAction *aSetAnnotation;
QAction *aReduceLifeByPower;
QList<QAction *> aAddCounter, aSetCounter, aRemoveCounter;
private:
PlayerGraphicsItem *player;
const CardItem *card;
QList<QPair<QString, int>> playersInfo;
bool shortcutsActive;
void addRelatedCardActions();
void retranslateUi();
void initContextualPlayersMenu(QMenu *menu, QAction *allPlayersAction);
void setShortcutsActive();
void addRelatedCardView();
};
#endif // COCKATRICE_CARD_MENU_H

View file

@ -1,41 +0,0 @@
#include "custom_zone_menu.h"
#include "../player_logic.h"
CustomZoneMenu::CustomZoneMenu(PlayerGraphicsItem *_player) : player(_player)
{
menuAction()->setVisible(false);
connect(player->getLogic(), &PlayerLogic::clearCustomZonesMenu, this, &CustomZoneMenu::clearCustomZonesMenu);
connect(player->getLogic(), &PlayerLogic::addViewCustomZoneActionToCustomZoneMenu, this,
&CustomZoneMenu::addViewCustomZoneActionToCustomZoneMenu);
retranslateUi();
}
void CustomZoneMenu::retranslateUi()
{
setTitle(tr("C&ustom Zones"));
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
for (auto aViewZone : actions()) {
aViewZone->setText(tr("View custom zone '%1'").arg(aViewZone->data().toString()));
}
}
}
void CustomZoneMenu::clearCustomZonesMenu()
{
clear();
menuAction()->setVisible(false);
}
void CustomZoneMenu::addViewCustomZoneActionToCustomZoneMenu(QString zoneName)
{
menuAction()->setVisible(true);
QAction *aViewZone = addAction(tr("View custom zone '%1'").arg(zoneName));
aViewZone->setData(zoneName);
connect(aViewZone, &QAction::triggered, this,
[zoneName, this]() { player->getGameScene()->toggleZoneView(player->getLogic(), zoneName, -1); });
}

View file

@ -1,35 +0,0 @@
/**
* @file custom_zone_menu.h
* @ingroup GameMenusZones
*/
//! \todo Document this file.
#ifndef COCKATRICE_CUSTOM_ZONE_MENU_H
#define COCKATRICE_CUSTOM_ZONE_MENU_H
#include "abstract_player_component.h"
#include <QMenu>
class PlayerGraphicsItem;
class CustomZoneMenu : public QMenu, public AbstractPlayerComponent
{
Q_OBJECT
public:
explicit CustomZoneMenu(PlayerGraphicsItem *player);
void retranslateUi() override;
void setShortcutsActive() override
{
}
void setShortcutsInactive() override
{
}
private:
PlayerGraphicsItem *player;
private slots:
void clearCustomZonesMenu();
void addViewCustomZoneActionToCustomZoneMenu(QString zoneName);
};
#endif // COCKATRICE_CUSTOM_ZONE_MENU_H

View file

@ -1,123 +0,0 @@
#include "grave_menu.h"
#include "../../abstract_game.h"
#include "../player_actions.h"
#include "../player_logic.h"
#include <QAction>
#include <QMenu>
#include <libcockatrice/utility/zone_names.h>
GraveyardMenu::GraveyardMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{
createMoveActions();
createViewActions();
addAction(aViewGraveyard);
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
mRevealRandomGraveyardCard = addMenu(QString());
connect(mRevealRandomGraveyardCard, &QMenu::aboutToShow, this,
&GraveyardMenu::populateRevealRandomMenuWithActivePlayers);
addSeparator();
moveGraveMenu = addTearOffMenu(QString());
moveGraveMenu->addAction(aMoveGraveToTopLibrary);
moveGraveMenu->addAction(aMoveGraveToBottomLibrary);
moveGraveMenu->addSeparator();
moveGraveMenu->addAction(aMoveGraveToHand);
moveGraveMenu->addSeparator();
moveGraveMenu->addAction(aMoveGraveToRfg);
}
retranslateUi();
}
void GraveyardMenu::createMoveActions()
{
auto grave = player->getLogic()->getGraveZone();
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aMoveGraveToTopLibrary = new QAction(this);
aMoveGraveToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0);
aMoveGraveToBottomLibrary = new QAction(this);
aMoveGraveToBottomLibrary->setData(QList<QVariant>() << ZoneNames::DECK << -1);
aMoveGraveToHand = new QAction(this);
aMoveGraveToHand->setData(QList<QVariant>() << ZoneNames::HAND << 0);
aMoveGraveToRfg = new QAction(this);
aMoveGraveToRfg->setData(QList<QVariant>() << ZoneNames::EXILE << 0);
connect(aMoveGraveToTopLibrary, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone);
connect(aMoveGraveToBottomLibrary, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone);
connect(aMoveGraveToHand, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone);
connect(aMoveGraveToRfg, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone);
}
}
void GraveyardMenu::createViewActions()
{
PlayerActions *playerActions = player->getLogic()->getPlayerActions();
aViewGraveyard = new QAction(this);
connect(aViewGraveyard, &QAction::triggered, playerActions, &PlayerActions::actViewGraveyard);
}
void GraveyardMenu::populateRevealRandomMenuWithActivePlayers()
{
mRevealRandomGraveyardCard->clear();
QAction *allPlayers = mRevealRandomGraveyardCard->addAction(tr("&All players"));
allPlayers->setData(-1);
connect(allPlayers, &QAction::triggered, this, &GraveyardMenu::onRevealRandomTriggered);
mRevealRandomGraveyardCard->addSeparator();
const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) {
if (other == player->getLogic()) {
continue;
}
QAction *a = mRevealRandomGraveyardCard->addAction(other->getPlayerInfo()->getName());
a->setData(other->getPlayerInfo()->getId());
connect(a, &QAction::triggered, this, &GraveyardMenu::onRevealRandomTriggered);
}
}
void GraveyardMenu::onRevealRandomTriggered()
{
if (auto *a = qobject_cast<QAction *>(sender())) {
player->getLogic()->getPlayerActions()->actRevealRandomGraveyardCard(a->data().toInt());
}
}
void GraveyardMenu::retranslateUi()
{
setTitle(tr("&Graveyard"));
aViewGraveyard->setText(tr("&View graveyard"));
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
moveGraveMenu->setTitle(tr("&Move graveyard to..."));
aMoveGraveToTopLibrary->setText(tr("&Top of library"));
aMoveGraveToBottomLibrary->setText(tr("&Bottom of library"));
aMoveGraveToHand->setText(tr("&Hand"));
aMoveGraveToRfg->setText(tr("&Exile"));
mRevealRandomGraveyardCard->setTitle(tr("Reveal random card to..."));
}
}
void GraveyardMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aViewGraveyard->setShortcuts(shortcuts.getShortcut("Player/aViewGraveyard"));
}
void GraveyardMenu::setShortcutsInactive()
{
aViewGraveyard->setShortcut(QKeySequence());
}

View file

@ -1,46 +0,0 @@
/**
* @file grave_menu.h
* @ingroup GameMenusZones
*/
//! \todo Document this file.
#ifndef COCKATRICE_GRAVE_MENU_H
#define COCKATRICE_GRAVE_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction>
#include <QMenu>
class PlayerGraphicsItem;
class GraveyardMenu : public TearOffMenu, public AbstractPlayerComponent
{
Q_OBJECT
signals:
void newPlayerActionCreated(QAction *action);
public:
explicit GraveyardMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr);
void createMoveActions();
void createViewActions();
void populateRevealRandomMenuWithActivePlayers();
void onRevealRandomTriggered();
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
QMenu *mRevealRandomGraveyardCard = nullptr;
QMenu *moveGraveMenu = nullptr;
QAction *aViewGraveyard = nullptr;
QAction *aMoveGraveToTopLibrary = nullptr;
QAction *aMoveGraveToBottomLibrary = nullptr;
QAction *aMoveGraveToHand = nullptr;
QAction *aMoveGraveToRfg = nullptr;
private:
PlayerGraphicsItem *player;
};
#endif // COCKATRICE_GRAVE_MENU_H

View file

@ -1,223 +0,0 @@
#include "hand_menu.h"
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../../game_graphics/zones/hand_zone.h"
#include "../../abstract_game.h"
#include "../player_actions.h"
#include "../player_graphics_item.h"
#include "../player_logic.h"
#include <QAction>
#include <QMenu>
#include <libcockatrice/utility/zone_names.h>
HandMenu::HandMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{
auto *actions = player->getLogic()->getPlayerActions();
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aViewHand = new QAction(this);
connect(aViewHand, &QAction::triggered, actions, &PlayerActions::actViewHand);
addAction(aViewHand);
mSortHand = addMenu(QString());
aSortHandByName = new QAction(this);
aSortHandByName->setData(CardList::SortByName);
aSortHandByType = new QAction(this);
aSortHandByType->setData(CardList::SortByMainType);
aSortHandByManaValue = new QAction(this);
aSortHandByManaValue->setData(CardList::SortByManaValue);
connect(aSortHandByType, &QAction::triggered, actions, &PlayerActions::actSortHand);
connect(aSortHandByName, &QAction::triggered, actions, &PlayerActions::actSortHand);
connect(aSortHandByManaValue, &QAction::triggered, actions, &PlayerActions::actSortHand);
mSortHand->addAction(aSortHandByName);
mSortHand->addAction(aSortHandByType);
mSortHand->addAction(aSortHandByManaValue);
}
mRevealHand = addMenu(QString());
connect(mRevealHand, &QMenu::aboutToShow, this, &HandMenu::populateRevealHandMenuWithActivePlayers);
aRevealHandToAll = new QAction(this);
aRevealHandToAll->setData(-1);
connect(aRevealHandToAll, &QAction::triggered, this, &HandMenu::onRevealHandTriggered);
mRevealRandomHandCard = addMenu(QString());
connect(mRevealRandomHandCard, &QMenu::aboutToShow, this,
&HandMenu::populateRevealRandomHandCardMenuWithActivePlayers);
aRevealRandomHandCardToAll = new QAction(this);
aRevealRandomHandCardToAll->setData(-1);
connect(aRevealRandomHandCardToAll, &QAction::triggered, this, &HandMenu::onRevealRandomHandCardTriggered);
// We still need to add these actions to menu here so that the shortcuts are active right away
mRevealHand->addAction(aRevealHandToAll);
mRevealRandomHandCard->addAction(aRevealRandomHandCardToAll);
addSeparator();
aMulligan = new QAction(this);
connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actRequestMulliganDialog);
addAction(aMulligan);
// Mulligan same size
aMulliganSame = new QAction(this);
connect(aMulliganSame, &QAction::triggered, actions, &PlayerActions::actMulliganSameSize);
addAction(aMulliganSame);
// Mulligan -1
aMulliganMinusOne = new QAction(this);
connect(aMulliganMinusOne, &QAction::triggered, actions, &PlayerActions::actMulliganMinusOne);
addAction(aMulliganMinusOne);
addSeparator();
mMoveHandMenu = addTearOffMenu(QString());
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aMoveHandToTopLibrary = new QAction(this);
aMoveHandToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0);
aMoveHandToBottomLibrary = new QAction(this);
aMoveHandToBottomLibrary->setData(QList<QVariant>() << ZoneNames::DECK << -1);
aMoveHandToGrave = new QAction(this);
aMoveHandToGrave->setData(QList<QVariant>() << ZoneNames::GRAVE << 0);
aMoveHandToRfg = new QAction(this);
aMoveHandToRfg->setData(QList<QVariant>() << ZoneNames::EXILE << 0);
auto hand = player->getLogic()->getHandZone();
connect(aMoveHandToTopLibrary, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone);
connect(aMoveHandToBottomLibrary, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone);
connect(aMoveHandToGrave, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone);
connect(aMoveHandToRfg, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone);
mMoveHandMenu->addAction(aMoveHandToTopLibrary);
mMoveHandMenu->addAction(aMoveHandToBottomLibrary);
mMoveHandMenu->addSeparator();
mMoveHandMenu->addAction(aMoveHandToGrave);
mMoveHandMenu->addSeparator();
mMoveHandMenu->addAction(aMoveHandToRfg);
}
retranslateUi();
}
void HandMenu::retranslateUi()
{
setTitle(tr("&Hand"));
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
aViewHand->setText(tr("&View hand"));
mSortHand->setTitle(tr("Sort hand by..."));
aSortHandByName->setText(tr("Name"));
aSortHandByType->setText(tr("Type"));
aSortHandByManaValue->setText(tr("Mana Value"));
aMulligan->setText(tr("Take &mulligan (Choose hand size)"));
aMulliganSame->setText(tr("Take mulligan (Same hand size)"));
aMulliganMinusOne->setText(tr("Take mulligan (Hand size - 1)"));
mMoveHandMenu->setTitle(tr("&Move hand to..."));
aMoveHandToTopLibrary->setText(tr("&Top of library"));
aMoveHandToBottomLibrary->setText(tr("&Bottom of library"));
aMoveHandToGrave->setText(tr("&Graveyard"));
aMoveHandToRfg->setText(tr("&Exile"));
mRevealHand->setTitle(tr("&Reveal hand to..."));
aRevealHandToAll->setText(tr("All players"));
mRevealRandomHandCard->setTitle(tr("Reveal r&andom card to..."));
aRevealRandomHandCardToAll->setText(tr("All players"));
}
}
void HandMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aViewHand->setShortcuts(shortcuts.getShortcut("Player/aViewHand"));
aSortHandByName->setShortcuts(shortcuts.getShortcut("Player/aSortHandByName"));
aSortHandByType->setShortcuts(shortcuts.getShortcut("Player/aSortHandByType"));
aSortHandByManaValue->setShortcuts(shortcuts.getShortcut("Player/aSortHandByManaValue"));
aMulligan->setShortcuts(shortcuts.getShortcut("Player/aMulligan"));
aMulliganSame->setShortcuts(shortcuts.getShortcut("Player/aMulliganSame"));
aMulliganMinusOne->setShortcuts(shortcuts.getShortcut("Player/aMulliganMinusOne"));
aRevealHandToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealHandToAll"));
aRevealRandomHandCardToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealRandomHandCardToAll"));
}
void HandMenu::setShortcutsInactive()
{
aViewHand->setShortcut(QKeySequence());
aSortHandByName->setShortcut(QKeySequence());
aSortHandByType->setShortcut(QKeySequence());
aSortHandByManaValue->setShortcut(QKeySequence());
aMulligan->setShortcut(QKeySequence());
aRevealHandToAll->setShortcut(QKeySequence());
aRevealRandomHandCardToAll->setShortcut(QKeySequence());
}
void HandMenu::populateRevealHandMenuWithActivePlayers()
{
mRevealHand->clear();
mRevealHand->addAction(aRevealHandToAll);
mRevealHand->addSeparator();
const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) {
if (other == player->getLogic()) {
continue;
}
QAction *a = mRevealHand->addAction(other->getPlayerInfo()->getName());
a->setData(other->getPlayerInfo()->getId());
connect(a, &QAction::triggered, this, &HandMenu::onRevealHandTriggered);
}
}
void HandMenu::populateRevealRandomHandCardMenuWithActivePlayers()
{
mRevealRandomHandCard->clear();
mRevealRandomHandCard->addAction(aRevealRandomHandCardToAll);
mRevealRandomHandCard->addSeparator();
const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) {
if (other == player->getLogic()) {
continue;
}
QAction *a = mRevealRandomHandCard->addAction(other->getPlayerInfo()->getName());
a->setData(other->getPlayerInfo()->getId());
connect(a, &QAction::triggered, this, &HandMenu::onRevealRandomHandCardTriggered);
}
}
void HandMenu::onRevealHandTriggered()
{
auto *action = qobject_cast<QAction *>(sender());
if (!action) {
return;
}
const int targetId = action->data().toInt();
player->getLogic()->getPlayerActions()->actRevealHand(targetId);
}
void HandMenu::onRevealRandomHandCardTriggered()
{
auto *action = qobject_cast<QAction *>(sender());
if (!action) {
return;
}
const int targetId = action->data().toInt();
player->getLogic()->getPlayerActions()->actRevealRandomHandCard(targetId);
}

View file

@ -1,71 +0,0 @@
/**
* @file hand_menu.h
* @ingroup GameMenusZones
*/
//! \todo Document this file.
#ifndef COCKATRICE_HAND_MENU_H
#define COCKATRICE_HAND_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction>
#include <QMenu>
class PlayerGraphicsItem;
class PlayerActions;
class HandMenu : public TearOffMenu, public AbstractPlayerComponent
{
Q_OBJECT
public:
HandMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr);
QMenu *revealHandMenu() const
{
return mRevealHand;
}
QMenu *revealRandomHandCardMenu() const
{
return mRevealRandomHandCard;
}
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
private slots:
void populateRevealHandMenuWithActivePlayers();
void populateRevealRandomHandCardMenuWithActivePlayers();
void onRevealHandTriggered();
void onRevealRandomHandCardTriggered();
private:
PlayerGraphicsItem *player;
QAction *aViewHand = nullptr;
QAction *aMulligan = nullptr;
QAction *aMulliganSame = nullptr;
QAction *aMulliganMinusOne = nullptr;
QMenu *mSortHand = nullptr;
QAction *aSortHandByName = nullptr;
QAction *aSortHandByType = nullptr;
QAction *aSortHandByManaValue = nullptr;
QMenu *mRevealHand = nullptr;
QAction *aRevealHandToAll = nullptr;
QMenu *mRevealRandomHandCard = nullptr;
QAction *aRevealRandomHandCardToAll = nullptr;
QMenu *mMoveHandMenu = nullptr;
QAction *aMoveHandToTopLibrary = nullptr;
QAction *aMoveHandToBottomLibrary = nullptr;
QAction *aMoveHandToGrave = nullptr;
QAction *aMoveHandToRfg = nullptr;
};
#endif // COCKATRICE_HAND_MENU_H

View file

@ -1,420 +0,0 @@
#include "library_menu.h"
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../../interface/widgets/tabs/tab_game.h"
#include "../../abstract_game.h"
#include "../player_actions.h"
#include "../player_logic.h"
#include <QAction>
#include <QGraphicsView>
#include <QMenu>
LibraryMenu::LibraryMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{
createDrawActions();
createShuffleActions();
createMoveActions();
createViewActions();
addAction(aDrawCard);
addAction(aDrawCards);
addAction(aUndoDraw);
addSeparator();
addAction(aShuffle);
addSeparator();
addAction(aViewLibrary);
addAction(aViewTopCards);
addAction(aViewBottomCards);
addSeparator();
mRevealLibrary = addMenu(QString());
connect(mRevealLibrary, &QMenu::aboutToShow, this, &LibraryMenu::populateRevealLibraryMenuWithActivePlayers);
mLendLibrary = addMenu(QString());
connect(mLendLibrary, &QMenu::aboutToShow, this, &LibraryMenu::populateLendLibraryMenuWithActivePlayers);
mRevealTopCard = addMenu(QString());
connect(mRevealTopCard, &QMenu::aboutToShow, this, &LibraryMenu::populateRevealTopCardMenuWithActivePlayers);
addAction(aAlwaysRevealTopCard);
addAction(aAlwaysLookAtTopCard);
addSeparator();
topLibraryMenu = addTearOffMenu(QString());
bottomLibraryMenu = addTearOffMenu(QString());
addSeparator();
addAction(aOpenDeckInDeckEditor);
topLibraryMenu->addAction(aMoveTopToPlay);
topLibraryMenu->addAction(aMoveTopToPlayFaceDown);
topLibraryMenu->addAction(aMoveTopCardToBottom);
topLibraryMenu->addSeparator();
topLibraryMenu->addAction(aMoveTopCardToGraveyard);
topLibraryMenu->addAction(aMoveTopCardsToGraveyard);
topLibraryMenu->addAction(aMoveTopCardsToGraveyardFaceDown);
topLibraryMenu->addAction(aMoveTopCardToExile);
topLibraryMenu->addAction(aMoveTopCardsToExile);
topLibraryMenu->addAction(aMoveTopCardsToExileFaceDown);
topLibraryMenu->addAction(aMoveTopCardsUntil);
topLibraryMenu->addSeparator();
topLibraryMenu->addAction(aShuffleTopCards);
bottomLibraryMenu->addAction(aDrawBottomCard);
bottomLibraryMenu->addAction(aDrawBottomCards);
bottomLibraryMenu->addSeparator();
bottomLibraryMenu->addAction(aMoveBottomToPlay);
bottomLibraryMenu->addAction(aMoveBottomToPlayFaceDown);
bottomLibraryMenu->addAction(aMoveBottomCardToTop);
bottomLibraryMenu->addSeparator();
bottomLibraryMenu->addAction(aMoveBottomCardToGraveyard);
bottomLibraryMenu->addAction(aMoveBottomCardsToGraveyard);
bottomLibraryMenu->addAction(aMoveBottomCardsToGraveyardFaceDown);
bottomLibraryMenu->addAction(aMoveBottomCardToExile);
bottomLibraryMenu->addAction(aMoveBottomCardsToExile);
bottomLibraryMenu->addAction(aMoveBottomCardsToExileFaceDown);
bottomLibraryMenu->addSeparator();
bottomLibraryMenu->addAction(aShuffleBottomCards);
connect(player->getLogic(), &PlayerLogic::resetTopCardMenuActions, this, &LibraryMenu::resetTopCardMenuActions);
connect(player->getLogic(), &PlayerLogic::deckChanged, this, &LibraryMenu::enableOpenInDeckEditorAction);
retranslateUi();
}
void LibraryMenu::enableOpenInDeckEditorAction() const
{
aOpenDeckInDeckEditor->setEnabled(true);
}
void LibraryMenu::resetTopCardMenuActions()
{
aAlwaysRevealTopCard->setChecked(false);
aAlwaysLookAtTopCard->setChecked(false);
}
void LibraryMenu::createDrawActions()
{
PlayerActions *playerActions = player->getLogic()->getPlayerActions();
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aDrawCard = new QAction(this);
connect(aDrawCard, &QAction::triggered, playerActions, &PlayerActions::actDrawCard);
aDrawCards = new QAction(this);
connect(aDrawCards, &QAction::triggered, playerActions, &PlayerActions::actRequestDrawCardsDialog);
aUndoDraw = new QAction(this);
connect(aUndoDraw, &QAction::triggered, playerActions, &PlayerActions::actUndoDraw);
aDrawBottomCard = new QAction(this);
connect(aDrawBottomCard, &QAction::triggered, playerActions, &PlayerActions::actDrawBottomCard);
aDrawBottomCards = new QAction(this);
connect(aDrawBottomCards, &QAction::triggered, playerActions, &PlayerActions::actRequestDrawBottomCardsDialog);
}
}
void LibraryMenu::createShuffleActions()
{
PlayerActions *playerActions = player->getLogic()->getPlayerActions();
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aShuffle = new QAction(this);
connect(aShuffle, &QAction::triggered, playerActions, &PlayerActions::actShuffle);
aShuffleTopCards = new QAction(this);
connect(aShuffleTopCards, &QAction::triggered, playerActions, &PlayerActions::actRequestShuffleTopDialog);
aShuffleBottomCards = new QAction(this);
connect(aShuffleBottomCards, &QAction::triggered, playerActions, &PlayerActions::actRequestShuffleBottomDialog);
}
}
void LibraryMenu::createMoveActions()
{
PlayerActions *playerActions = player->getLogic()->getPlayerActions();
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aMoveTopToPlay = new QAction(this);
connect(aMoveTopToPlay, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToPlay);
aMoveTopToPlayFaceDown = new QAction(this);
connect(aMoveTopToPlayFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveTopCardToPlayFaceDown);
aMoveTopCardToGraveyard = new QAction(this);
connect(aMoveTopCardToGraveyard, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToGrave);
aMoveTopCardToExile = new QAction(this);
connect(aMoveTopCardToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToExile);
aMoveTopCardsToGraveyard = new QAction(this);
connect(aMoveTopCardsToGraveyard, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsToGrave);
aMoveTopCardsToGraveyardFaceDown = new QAction(this);
connect(aMoveTopCardsToGraveyardFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveTopCardsToGraveFaceDown);
aMoveTopCardsToExile = new QAction(this);
connect(aMoveTopCardsToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsToExile);
aMoveTopCardsToExileFaceDown = new QAction(this);
connect(aMoveTopCardsToExileFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveTopCardsToExileFaceDown);
aMoveTopCardsUntil = new QAction(this);
connect(aMoveTopCardsUntil, &QAction::triggered, playerActions,
&PlayerActions::actRequestMoveTopCardsUntilDialog);
aMoveTopCardToBottom = new QAction(this);
connect(aMoveTopCardToBottom, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToBottom);
aMoveBottomToPlay = new QAction(this);
connect(aMoveBottomToPlay, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardToPlay);
aMoveBottomToPlayFaceDown = new QAction(this);
connect(aMoveBottomToPlayFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveBottomCardToPlayFaceDown);
aMoveBottomCardToGraveyard = new QAction(this);
connect(aMoveBottomCardToGraveyard, &QAction::triggered, playerActions,
&PlayerActions::actMoveBottomCardToGrave);
aMoveBottomCardToExile = new QAction(this);
connect(aMoveBottomCardToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardToExile);
aMoveBottomCardsToGraveyard = new QAction(this);
connect(aMoveBottomCardsToGraveyard, &QAction::triggered, playerActions,
&PlayerActions::actMoveBottomCardsToGrave);
aMoveBottomCardsToGraveyardFaceDown = new QAction(this);
connect(aMoveBottomCardsToGraveyardFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveBottomCardsToGraveFaceDown);
aMoveBottomCardsToExile = new QAction(this);
connect(aMoveBottomCardsToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardsToExile);
aMoveBottomCardsToExileFaceDown = new QAction(this);
connect(aMoveBottomCardsToExileFaceDown, &QAction::triggered, playerActions,
&PlayerActions::actMoveBottomCardsToExileFaceDown);
aMoveBottomCardToTop = new QAction(this);
connect(aMoveBottomCardToTop, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardToTop);
}
}
void LibraryMenu::createViewActions()
{
PlayerActions *playerActions = player->getLogic()->getPlayerActions();
if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) {
aViewLibrary = new QAction(this);
connect(aViewLibrary, &QAction::triggered, playerActions, &PlayerActions::actViewLibrary);
aViewTopCards = new QAction(this);
connect(aViewTopCards, &QAction::triggered, playerActions, &PlayerActions::actRequestViewTopCardsDialog);
aViewBottomCards = new QAction(this);
connect(aViewBottomCards, &QAction::triggered, playerActions, &PlayerActions::actRequestViewBottomCardsDialog);
aAlwaysRevealTopCard = new QAction(this);
aAlwaysRevealTopCard->setCheckable(true);
connect(aAlwaysRevealTopCard, &QAction::triggered, playerActions, &PlayerActions::actAlwaysRevealTopCard);
aAlwaysLookAtTopCard = new QAction(this);
aAlwaysLookAtTopCard->setCheckable(true);
connect(aAlwaysLookAtTopCard, &QAction::triggered, playerActions, &PlayerActions::actAlwaysLookAtTopCard);
aOpenDeckInDeckEditor = new QAction(this);
aOpenDeckInDeckEditor->setEnabled(false);
connect(aOpenDeckInDeckEditor, &QAction::triggered, playerActions, &PlayerActions::actOpenDeckInDeckEditor);
}
}
void LibraryMenu::retranslateUi()
{
setTitle(tr("&Library"));
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
aViewLibrary->setText(tr("&View library"));
aViewTopCards->setText(tr("View &top cards of library..."));
aViewBottomCards->setText(tr("View bottom cards of library..."));
mRevealLibrary->setTitle(tr("Reveal &library to..."));
mLendLibrary->setTitle(tr("Lend library to..."));
mRevealTopCard->setTitle(tr("Reveal &top cards to..."));
topLibraryMenu->setTitle(tr("&Top of library..."));
bottomLibraryMenu->setTitle(tr("&Bottom of library..."));
aAlwaysRevealTopCard->setText(tr("&Always reveal top card"));
aAlwaysLookAtTopCard->setText(tr("&Always look at top card"));
aOpenDeckInDeckEditor->setText(tr("&Open deck in deck editor"));
aDrawCard->setText(tr("&Draw card"));
aDrawCards->setText(tr("D&raw cards..."));
aUndoDraw->setText(tr("&Undo last draw"));
aShuffle->setText(tr("Shuffle"));
aMoveTopToPlay->setText(tr("&Play top card"));
aMoveTopToPlayFaceDown->setText(tr("Play top card &face down"));
aMoveTopCardToBottom->setText(tr("Put top card on &bottom"));
aMoveTopCardToGraveyard->setText(tr("Move top card to grave&yard"));
aMoveTopCardToExile->setText(tr("Move top card to e&xile"));
aMoveTopCardsToGraveyard->setText(tr("Move top cards to &graveyard..."));
aMoveTopCardsToGraveyardFaceDown->setText(tr("Move top cards to graveyard face down..."));
aMoveTopCardsToExile->setText(tr("Move top cards to &exile..."));
aMoveTopCardsToExileFaceDown->setText(tr("Move top cards to exile face down..."));
aMoveTopCardsUntil->setText(tr("Put top cards on stack &until..."));
aShuffleTopCards->setText(tr("Shuffle top cards..."));
aDrawBottomCard->setText(tr("&Draw bottom card"));
aDrawBottomCards->setText(tr("D&raw bottom cards..."));
aMoveBottomToPlay->setText(tr("&Play bottom card"));
aMoveBottomToPlayFaceDown->setText(tr("Play bottom card &face down"));
aMoveBottomCardToGraveyard->setText(tr("Move bottom card to grave&yard"));
aMoveBottomCardToExile->setText(tr("Move bottom card to e&xile"));
aMoveBottomCardsToGraveyard->setText(tr("Move bottom cards to &graveyard..."));
aMoveBottomCardsToGraveyardFaceDown->setText(tr("Move bottom cards to graveyard face down..."));
aMoveBottomCardsToExile->setText(tr("Move bottom cards to &exile..."));
aMoveBottomCardsToExileFaceDown->setText(tr("Move bottom cards to exile face down..."));
aMoveBottomCardToTop->setText(tr("Put bottom card on &top"));
aShuffleBottomCards->setText(tr("Shuffle bottom cards..."));
}
}
void LibraryMenu::populateRevealLibraryMenuWithActivePlayers()
{
mRevealLibrary->clear();
QAction *allPlayers = mRevealLibrary->addAction(tr("&All players"));
allPlayers->setData(-1);
connect(allPlayers, &QAction::triggered, this, &LibraryMenu::onRevealLibraryTriggered);
mRevealLibrary->addSeparator();
const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) {
if (other == player->getLogic()) {
continue;
}
QAction *a = mRevealLibrary->addAction(other->getPlayerInfo()->getName());
a->setData(other->getPlayerInfo()->getId());
connect(a, &QAction::triggered, this, &LibraryMenu::onRevealLibraryTriggered);
}
}
void LibraryMenu::populateLendLibraryMenuWithActivePlayers()
{
mLendLibrary->clear();
const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) {
if (other == player->getLogic()) {
continue;
}
QAction *a = mLendLibrary->addAction(other->getPlayerInfo()->getName());
a->setData(other->getPlayerInfo()->getId());
connect(a, &QAction::triggered, this, &LibraryMenu::onLendLibraryTriggered);
}
}
void LibraryMenu::populateRevealTopCardMenuWithActivePlayers()
{
mRevealTopCard->clear();
QAction *allPlayers = mRevealTopCard->addAction(tr("&All players"));
allPlayers->setData(-1);
connect(allPlayers, &QAction::triggered, this, &LibraryMenu::onRevealTopCardTriggered);
mRevealTopCard->addSeparator();
const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
for (auto *other : players) {
if (other == player->getLogic()) {
continue;
}
QAction *a = mRevealTopCard->addAction(other->getPlayerInfo()->getName());
a->setData(other->getPlayerInfo()->getId());
connect(a, &QAction::triggered, this, &LibraryMenu::onRevealTopCardTriggered);
}
}
void LibraryMenu::onRevealLibraryTriggered()
{
if (auto *a = qobject_cast<QAction *>(sender())) {
player->getLogic()->getPlayerActions()->actRevealLibrary(a->data().toInt());
}
}
void LibraryMenu::onLendLibraryTriggered()
{
if (auto *a = qobject_cast<QAction *>(sender())) {
player->getLogic()->getPlayerActions()->actLendLibrary(a->data().toInt());
}
}
void LibraryMenu::onRevealTopCardTriggered()
{
QWidget *parent = nullptr;
if (auto *view = player->scene() ? player->scene()->views().value(0) : nullptr) {
parent = view->window();
}
if (auto *a = qobject_cast<QAction *>(sender())) {
int deckSize = player->getLogic()->getDeckZone()->getCards().size();
bool ok = true;
int number = QInputDialog::getInt(parent, tr("Reveal top cards of library"),
tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1,
deckSize, 1, &ok);
if (ok) {
player->getLogic()->getPlayerActions()->actRevealTopCards(a->data().toInt(), number);
defaultNumberTopCards = number;
}
}
}
void LibraryMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aViewLibrary->setShortcuts(shortcuts.getShortcut("Player/aViewLibrary"));
aViewTopCards->setShortcuts(shortcuts.getShortcut("Player/aViewTopCards"));
aViewBottomCards->setShortcuts(shortcuts.getShortcut("Player/aViewBottomCards"));
aDrawCard->setShortcuts(shortcuts.getShortcut("Player/aDrawCard"));
aDrawCards->setShortcuts(shortcuts.getShortcut("Player/aDrawCards"));
aUndoDraw->setShortcuts(shortcuts.getShortcut("Player/aUndoDraw"));
aShuffle->setShortcuts(shortcuts.getShortcut("Player/aShuffle"));
aShuffleTopCards->setShortcuts(shortcuts.getShortcut("Player/aShuffleTopCards"));
aShuffleBottomCards->setShortcuts(shortcuts.getShortcut("Player/aShuffleBottomCards"));
aAlwaysRevealTopCard->setShortcuts(shortcuts.getShortcut("Player/aAlwaysRevealTopCard"));
aAlwaysLookAtTopCard->setShortcuts(shortcuts.getShortcut("Player/aAlwaysLookAtTopCard"));
aMoveTopToPlay->setShortcuts(shortcuts.getShortcut("Player/aMoveTopToPlay"));
aMoveTopToPlayFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopToPlayFaceDown"));
aMoveTopCardToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToGraveyard"));
aMoveTopCardsToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToGraveyard"));
aMoveTopCardsToGraveyardFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToGraveyardFaceDown"));
aMoveTopCardToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToExile"));
aMoveTopCardsToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToExile"));
aMoveTopCardsToExileFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToExileFaceDown"));
aMoveTopCardsUntil->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsUntil"));
aMoveTopCardToBottom->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToBottom"));
aDrawBottomCard->setShortcuts(shortcuts.getShortcut("Player/aDrawBottomCard"));
aDrawBottomCards->setShortcuts(shortcuts.getShortcut("Player/aDrawBottomCards"));
aMoveBottomToPlay->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomToPlay"));
aMoveBottomToPlayFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomToPlayFaceDown"));
aMoveBottomCardToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToGrave"));
aMoveBottomCardsToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToGrave"));
aMoveBottomCardsToGraveyardFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToGraveFaceDown"));
aMoveBottomCardToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToExile"));
aMoveBottomCardsToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToExile"));
aMoveBottomCardsToExileFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToExileFaceDown"));
aMoveBottomCardToTop->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToTop"));
}
void LibraryMenu::setShortcutsInactive()
{
aViewLibrary->setShortcut(QKeySequence());
aViewTopCards->setShortcut(QKeySequence());
aViewBottomCards->setShortcut(QKeySequence());
aDrawCard->setShortcut(QKeySequence());
aDrawCards->setShortcut(QKeySequence());
aUndoDraw->setShortcut(QKeySequence());
aShuffle->setShortcut(QKeySequence());
aShuffleTopCards->setShortcut(QKeySequence());
aShuffleBottomCards->setShortcut(QKeySequence());
aAlwaysRevealTopCard->setShortcut(QKeySequence());
aAlwaysLookAtTopCard->setShortcut(QKeySequence());
aMoveTopToPlay->setShortcut(QKeySequence());
aMoveTopToPlayFaceDown->setShortcut(QKeySequence());
aMoveTopCardToGraveyard->setShortcut(QKeySequence());
aMoveTopCardsToGraveyard->setShortcut(QKeySequence());
aMoveTopCardsToGraveyardFaceDown->setShortcut(QKeySequence());
aMoveTopCardToExile->setShortcut(QKeySequence());
aMoveTopCardsToExile->setShortcut(QKeySequence());
aMoveTopCardsToExileFaceDown->setShortcut(QKeySequence());
aMoveTopCardsUntil->setShortcut(QKeySequence());
aDrawBottomCard->setShortcut(QKeySequence());
aDrawBottomCards->setShortcut(QKeySequence());
aMoveBottomToPlay->setShortcut(QKeySequence());
aMoveBottomToPlayFaceDown->setShortcut(QKeySequence());
aMoveBottomCardToGraveyard->setShortcut(QKeySequence());
aMoveBottomCardsToGraveyard->setShortcut(QKeySequence());
aMoveBottomCardsToGraveyardFaceDown->setShortcut(QKeySequence());
aMoveBottomCardToExile->setShortcut(QKeySequence());
aMoveBottomCardsToExile->setShortcut(QKeySequence());
aMoveBottomCardsToExileFaceDown->setShortcut(QKeySequence());
}

View file

@ -1,118 +0,0 @@
/**
* @file library_menu.h
* @ingroup GameMenusZones
*/
//! \todo Document this file.
#ifndef COCKATRICE_LIBRARY_MENU_H
#define COCKATRICE_LIBRARY_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction>
#include <QMenu>
class PlayerGraphicsItem;
class PlayerLogic;
class PlayerActions;
class LibraryMenu : public TearOffMenu, public AbstractPlayerComponent
{
Q_OBJECT
public slots:
void enableOpenInDeckEditorAction() const;
void resetTopCardMenuActions();
public:
LibraryMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr);
void createDrawActions();
void createShuffleActions();
void createMoveActions();
void createViewActions();
void retranslateUi() override;
void populateRevealLibraryMenuWithActivePlayers();
void populateLendLibraryMenuWithActivePlayers();
void populateRevealTopCardMenuWithActivePlayers();
void onRevealLibraryTriggered();
void onLendLibraryTriggered();
void onRevealTopCardTriggered();
void setShortcutsActive() override;
void setShortcutsInactive() override;
[[nodiscard]] bool isAlwaysRevealTopCardChecked() const
{
return aAlwaysRevealTopCard->isChecked();
}
[[nodiscard]] bool isAlwaysLookAtTopCardChecked() const
{
return aAlwaysLookAtTopCard->isChecked();
}
// expose useful actions/menus if PlayerMenu needs them
[[nodiscard]] QMenu *revealLibrary() const
{
return mRevealLibrary;
}
[[nodiscard]] QMenu *lendLibraryMenu() const
{
return mLendLibrary;
}
[[nodiscard]] QMenu *revealTopCardMenu() const
{
return mRevealTopCard;
}
QMenu *topLibraryMenu = nullptr;
QMenu *bottomLibraryMenu = nullptr;
// Expose submenus that PlayerMenu tracks in its lists
QMenu *mRevealLibrary = nullptr;
QMenu *mLendLibrary = nullptr;
QMenu *mRevealTopCard = nullptr;
QAction *aDrawCard = nullptr;
QAction *aDrawCards = nullptr;
QAction *aUndoDraw = nullptr;
QAction *aShuffle = nullptr;
QAction *aViewLibrary = nullptr;
QAction *aViewTopCards = nullptr;
QAction *aViewBottomCards = nullptr;
QAction *aAlwaysRevealTopCard = nullptr;
QAction *aAlwaysLookAtTopCard = nullptr;
QAction *aOpenDeckInDeckEditor = nullptr;
QAction *aMoveTopToPlay = nullptr;
QAction *aMoveTopToPlayFaceDown = nullptr;
QAction *aMoveTopCardToBottom = nullptr;
QAction *aMoveTopCardToGraveyard = nullptr;
QAction *aMoveTopCardToExile = nullptr;
QAction *aMoveTopCardsToGraveyard = nullptr;
QAction *aMoveTopCardsToGraveyardFaceDown = nullptr;
QAction *aMoveTopCardsToExile = nullptr;
QAction *aMoveTopCardsToExileFaceDown = nullptr;
QAction *aMoveTopCardsUntil = nullptr;
QAction *aShuffleTopCards = nullptr;
QAction *aDrawBottomCard = nullptr;
QAction *aDrawBottomCards = nullptr;
QAction *aMoveBottomToPlay = nullptr;
QAction *aMoveBottomToPlayFaceDown = nullptr;
QAction *aMoveBottomCardToTop = nullptr;
QAction *aMoveBottomCardToGraveyard = nullptr;
QAction *aMoveBottomCardToExile = nullptr;
QAction *aMoveBottomCardsToGraveyard = nullptr;
QAction *aMoveBottomCardsToGraveyardFaceDown = nullptr;
QAction *aMoveBottomCardsToExile = nullptr;
QAction *aMoveBottomCardsToExileFaceDown = nullptr;
QAction *aShuffleBottomCards = nullptr;
int defaultNumberTopCards = 1;
private:
PlayerGraphicsItem *player;
};
#endif // COCKATRICE_LIBRARY_MENU_H

View file

@ -1,78 +0,0 @@
#include "move_menu.h"
#include "../card_menu_action_type.h"
#include "../player_actions.h"
#include "../player_logic.h"
MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to"))
{
aMoveToTopLibrary = new QAction(this);
aMoveToTopLibrary->setData(cmMoveToTopLibrary);
aMoveToBottomLibrary = new QAction(this);
aMoveToBottomLibrary->setData(cmMoveToBottomLibrary);
aMoveToXfromTopOfLibrary = new QAction(this);
aMoveToTable = new QAction(this);
aMoveToTable->setData(cmMoveToTable);
aMoveToGraveyard = new QAction(this);
aMoveToHand = new QAction(this);
aMoveToHand->setData(cmMoveToHand);
aMoveToGraveyard->setData(cmMoveToGraveyard);
aMoveToExile = new QAction(this);
aMoveToExile->setData(cmMoveToExile);
auto *actions = player->getLogic()->getPlayerActions();
auto invoke = [player](CardMenuActionType type) {
return [type, player]() {
player->getLogic()->getPlayerActions()->cardMenuAction(player->getGameScene()->selectedCards(), type);
};
};
connect(aMoveToTopLibrary, &QAction::triggered, actions, invoke(cmMoveToTopLibrary));
connect(aMoveToBottomLibrary, &QAction::triggered, actions, invoke(cmMoveToBottomLibrary));
connect(aMoveToXfromTopOfLibrary, &QAction::triggered, actions,
&PlayerActions::actRequestMoveCardXCardsFromTopDialog);
connect(aMoveToTable, &QAction::triggered, actions, invoke(cmMoveToTable));
connect(aMoveToHand, &QAction::triggered, actions, invoke(cmMoveToHand));
connect(aMoveToGraveyard, &QAction::triggered, actions, invoke(cmMoveToGraveyard));
connect(aMoveToExile, &QAction::triggered, actions, invoke(cmMoveToExile));
addAction(aMoveToTopLibrary);
addAction(aMoveToXfromTopOfLibrary);
addAction(aMoveToBottomLibrary);
addSeparator();
addAction(aMoveToTable);
addSeparator();
addAction(aMoveToHand);
addSeparator();
addAction(aMoveToGraveyard);
addSeparator();
addAction(aMoveToExile);
setShortcutsActive();
retranslateUi();
}
void MoveMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aMoveToTopLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToTopLibrary"));
aMoveToBottomLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToBottomLibrary"));
aMoveToTable->setShortcuts(shortcuts.getShortcut("Player/aMoveToTable"));
aMoveToHand->setShortcuts(shortcuts.getShortcut("Player/aMoveToHand"));
aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard"));
aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile"));
}
void MoveMenu::retranslateUi()
{
aMoveToTopLibrary->setText(tr("&Top of library in random order"));
aMoveToXfromTopOfLibrary->setText(tr("X cards from the top of library..."));
aMoveToBottomLibrary->setText(tr("&Bottom of library in random order"));
aMoveToTable->setText(tr("T&able"));
aMoveToHand->setText(tr("&Hand"));
aMoveToGraveyard->setText(tr("&Graveyard"));
aMoveToExile->setText(tr("&Exile"));
}

View file

@ -1,31 +0,0 @@
/**
* @file move_menu.h
* @ingroup GameMenusZones
*/
//! \todo Document this file.
#ifndef COCKATRICE_MOVE_MENU_H
#define COCKATRICE_MOVE_MENU_H
#include <QMenu>
class PlayerGraphicsItem;
class MoveMenu : public QMenu
{
Q_OBJECT
public:
explicit MoveMenu(PlayerGraphicsItem *player);
void setShortcutsActive();
void retranslateUi();
QAction *aMoveToTopLibrary = nullptr;
QAction *aMoveToXfromTopOfLibrary = nullptr;
QAction *aMoveToBottomLibrary = nullptr;
QAction *aMoveToHand = nullptr;
QAction *aMoveToTable = nullptr;
QAction *aMoveToGraveyard = nullptr;
QAction *aMoveToExile = nullptr;
};
#endif // COCKATRICE_MOVE_MENU_H

View file

@ -1,139 +0,0 @@
#include "player_menu.h"
#include "../../../game_graphics/zones/hand_zone.h"
#include "../../../game_graphics/zones/pile_zone.h"
#include "../../../game_graphics/zones/table_zone.h"
#include "../../../interface/widgets/tabs/tab_game.h"
#include "../../board/card_item.h"
#include "card_menu.h"
#include "hand_menu.h"
#include <libcockatrice/protocol/pb/command_reveal_cards.pb.h>
PlayerMenu::PlayerMenu(PlayerGraphicsItem *_player) : QObject(_player), player(_player)
{
connect(player->getLogic(), &PlayerLogic::requestCardMenuUpdate, this, &PlayerMenu::updateCardMenu);
connect(this, &PlayerMenu::cardInfoRequested, player, &PlayerGraphicsItem::cardInfoRequested);
playerMenu = new TearOffMenu();
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
handMenu = addManagedMenu<HandMenu>(player, playerMenu);
libraryMenu = addManagedMenu<LibraryMenu>(player, playerMenu);
} else {
handMenu = nullptr;
libraryMenu = nullptr;
}
graveMenu = addManagedMenu<GraveyardMenu>(player, playerMenu);
rfgMenu = addManagedMenu<RfgMenu>(player, playerMenu);
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
sideboardMenu = addManagedMenu<SideboardMenu>(player, playerMenu);
customZonesMenu = addManagedMenu<CustomZoneMenu>(player);
playerMenu->addSeparator();
countersMenu = playerMenu->addMenu(QString());
utilityMenu = createManagedComponent<UtilityMenu>(player, playerMenu);
} else {
sideboardMenu = nullptr;
customZonesMenu = nullptr;
countersMenu = nullptr;
utilityMenu = nullptr;
}
if (player->getLogic()->getPlayerInfo()->getLocal()) {
sayMenu = addManagedMenu<SayMenu>(player);
} else {
sayMenu = nullptr;
}
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&PlayerMenu::refreshShortcuts);
refreshShortcuts();
retranslateUi();
}
void PlayerMenu::setMenusForGraphicItems()
{
player->getTableZoneGraphicsItem()->setMenu(playerMenu);
player->getGraveyardZoneGraphicsItem()->setMenu(graveMenu, graveMenu->aViewGraveyard);
player->getRfgZoneGraphicsItem()->setMenu(rfgMenu, rfgMenu->aViewRfg);
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
player->getHandZoneGraphicsItem()->setMenu(handMenu);
player->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard);
player->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu);
}
}
QMenu *PlayerMenu::updateCardMenu(const CardItem *card)
{
if (!card) {
emit cardMenuUpdated(nullptr);
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->getLogic()->getGame()->getPlayerManager()->isSpectator() &&
!player->getLogic()->getGame()->getPlayerManager()->isJudge()) ||
player->getLogic()->getGame()->getActiveCard() != card) {
return nullptr;
}
CardMenu *menu = new CardMenu(player, card, shortcutsActive);
connect(menu, &CardMenu::cardInfoRequested, this, &PlayerMenu::cardInfoRequested);
emit cardMenuUpdated(menu);
return menu;
}
void PlayerMenu::retranslateUi()
{
playerMenu->setTitle(tr("Player \"%1\"").arg(player->getLogic()->getPlayerInfo()->getName()));
for (auto *component : managedComponents) {
component->retranslateUi();
}
if (countersMenu) {
countersMenu->setTitle(tr("&Counters"));
}
emit retranslateRequested();
}
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->getLogic()->getPlayerInfo()->getLocalOrJudge() &&
!player->getLogic()->getPlayerInfo()->getLocal()) {
setShortcutsInactive();
} else {
setShortcutsActive();
}
} else {
setShortcutsInactive();
}
}
void PlayerMenu::setShortcutsActive()
{
shortcutsActive = true;
for (auto *c : managedComponents) {
c->setShortcutsActive();
}
emit shortcutsActivated();
}
void PlayerMenu::setShortcutsInactive()
{
shortcutsActive = false;
for (auto *c : managedComponents) {
c->setShortcutsInactive();
}
emit shortcutsDeactivated();
}

View file

@ -1,117 +0,0 @@
/**
* @file player_menu.h
* @ingroup GameMenusPlayers
* @brief Orchestrates lifecycle management for all player-bound UI components.
*/
#ifndef COCKATRICE_PLAYER_MENU_H
#define COCKATRICE_PLAYER_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "custom_zone_menu.h"
#include "grave_menu.h"
#include "hand_menu.h"
#include "library_menu.h"
#include "rfg_menu.h"
#include "say_menu.h"
#include "sideboard_menu.h"
#include "utility_menu.h"
#include <QList>
#include <QMenu>
#include <QObject>
class CardItem;
class CardMenu;
class PlayerGraphicsItem;
class PlayerMenu : public QObject
{
Q_OBJECT
signals:
void cardMenuUpdated(CardMenu *cardMenu);
void cardInfoRequested(const CardRef &cardRef);
void shortcutsActivated();
void shortcutsDeactivated();
void retranslateRequested();
public slots:
void setMenusForGraphicItems();
QMenu *updateCardMenu(const CardItem *card);
private slots:
void refreshShortcuts();
public:
explicit PlayerMenu(PlayerGraphicsItem *player);
/** @brief Retranslate all user-visible strings. Called on language change. */
void retranslateUi();
[[nodiscard]] QMenu *getPlayerMenu() const
{
return playerMenu;
}
[[nodiscard]] QMenu *getCountersMenu()
{
return countersMenu;
}
[[nodiscard]] LibraryMenu *getLibraryMenu() const
{
return libraryMenu;
}
[[nodiscard]] UtilityMenu *getUtilityMenu() const
{
return utilityMenu;
}
[[nodiscard]] bool getShortcutsActive() const
{
return shortcutsActive;
}
/** @brief Bind keyboard shortcuts. Called when this player gains focus. */
void setShortcutsActive();
/** @brief Unbind keyboard shortcuts. Called when this player loses focus. */
void setShortcutsInactive();
private:
PlayerGraphicsItem *player;
TearOffMenu *playerMenu;
QMenu *countersMenu;
HandMenu *handMenu;
LibraryMenu *libraryMenu;
SideboardMenu *sideboardMenu;
GraveyardMenu *graveMenu;
RfgMenu *rfgMenu;
UtilityMenu *utilityMenu;
SayMenu *sayMenu;
CustomZoneMenu *customZonesMenu;
/** @brief Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via
* player->getCounters().
*/
QList<AbstractPlayerComponent *> managedComponents;
bool shortcutsActive = false;
/** @brief 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;
}
/** @brief 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

View file

@ -1,88 +0,0 @@
#include "pt_menu.h"
#include "../player_actions.h"
#include "../player_logic.h"
PtMenu::PtMenu(PlayerGraphicsItem *player) : QMenu(tr("Power / toughness"))
{
PlayerActions *playerActions = player->getLogic()->getPlayerActions();
aIncP = new QAction(this);
connect(aIncP, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actIncP(player->getGameScene()->selectedCards()); });
aDecP = new QAction(this);
connect(aDecP, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actDecP(player->getGameScene()->selectedCards()); });
aIncT = new QAction(this);
connect(aIncT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actIncT(player->getGameScene()->selectedCards()); });
aDecT = new QAction(this);
connect(aDecT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actDecT(player->getGameScene()->selectedCards()); });
aIncPT = new QAction(this);
connect(aIncPT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actIncPT(player->getGameScene()->selectedCards()); });
aDecPT = new QAction(this);
connect(aDecPT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actDecPT(player->getGameScene()->selectedCards()); });
aFlowP = new QAction(this);
connect(aFlowP, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actFlowP(player->getGameScene()->selectedCards()); });
aFlowT = new QAction(this);
connect(aFlowT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actFlowT(player->getGameScene()->selectedCards()); });
aSetPT = new QAction(this);
connect(aSetPT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actRequestSetPTDialog(player->getGameScene()->selectedCards()); });
aResetPT = new QAction(this);
connect(aResetPT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actResetPT(player->getGameScene()->selectedCards()); });
addAction(aIncP);
addAction(aDecP);
addAction(aFlowP);
addSeparator();
addAction(aIncT);
addAction(aDecT);
addAction(aFlowT);
addSeparator();
addAction(aIncPT);
addAction(aDecPT);
addSeparator();
addAction(aSetPT);
addAction(aResetPT);
setShortcutsActive();
retranslateUi();
}
void PtMenu::retranslateUi()
{
aIncP->setText(tr("&Increase power"));
aDecP->setText(tr("&Decrease power"));
aIncT->setText(tr("I&ncrease toughness"));
aDecT->setText(tr("D&ecrease toughness"));
aIncPT->setText(tr("In&crease power and toughness"));
aDecPT->setText(tr("Dec&rease power and toughness"));
aFlowP->setText(tr("Increase power and decrease toughness"));
aFlowT->setText(tr("Decrease power and increase toughness"));
aSetPT->setText(tr("Set &power and toughness..."));
aResetPT->setText(tr("Reset p&ower and toughness"));
}
void PtMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aIncP->setShortcuts(shortcuts.getShortcut("Player/aIncP"));
aDecP->setShortcuts(shortcuts.getShortcut("Player/aDecP"));
aIncT->setShortcuts(shortcuts.getShortcut("Player/aIncT"));
aDecT->setShortcuts(shortcuts.getShortcut("Player/aDecT"));
aIncPT->setShortcuts(shortcuts.getShortcut("Player/aIncPT"));
aDecPT->setShortcuts(shortcuts.getShortcut("Player/aDecPT"));
aFlowP->setShortcuts(shortcuts.getShortcut("Player/aFlowP"));
aFlowT->setShortcuts(shortcuts.getShortcut("Player/aFlowT"));
aSetPT->setShortcuts(shortcuts.getShortcut("Player/aSetPT"));
aResetPT->setShortcuts(shortcuts.getShortcut("Player/aResetPT"));
}

View file

@ -1,35 +0,0 @@
/**
* @file pt_menu.h
* @ingroup GameMenusCards
*/
//! \todo Document this file.
#ifndef COCKATRICE_PT_MENU_H
#define COCKATRICE_PT_MENU_H
#include <QMenu>
class PlayerGraphicsItem;
class PtMenu : public QMenu
{
Q_OBJECT
public:
explicit PtMenu(PlayerGraphicsItem *player);
void retranslateUi();
void setShortcutsActive();
QAction *aIncP = nullptr;
QAction *aDecP = nullptr;
QAction *aFlowP = nullptr;
QAction *aIncT = nullptr;
QAction *aDecT = nullptr;
QAction *aFlowT = nullptr;
QAction *aIncPT = nullptr;
QAction *aDecPT = nullptr;
QAction *aSetPT = nullptr;
QAction *aResetPT = nullptr;
};
#endif // COCKATRICE_PT_MENU_H

View file

@ -1,71 +0,0 @@
#include "rfg_menu.h"
#include "../player_actions.h"
#include "../player_logic.h"
#include <libcockatrice/utility/zone_names.h>
RfgMenu::RfgMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{
createMoveActions();
createViewActions();
addAction(aViewRfg);
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
addSeparator();
moveRfgMenu = addTearOffMenu(QString());
moveRfgMenu->addAction(aMoveRfgToTopLibrary);
moveRfgMenu->addAction(aMoveRfgToBottomLibrary);
moveRfgMenu->addSeparator();
moveRfgMenu->addAction(aMoveRfgToHand);
moveRfgMenu->addSeparator();
moveRfgMenu->addAction(aMoveRfgToGrave);
}
retranslateUi();
}
void RfgMenu::createMoveActions()
{
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
auto rfg = player->getLogic()->getRfgZone();
aMoveRfgToTopLibrary = new QAction(this);
aMoveRfgToTopLibrary->setData(QList<QVariant>() << ZoneNames::DECK << 0);
aMoveRfgToBottomLibrary = new QAction(this);
aMoveRfgToBottomLibrary->setData(QList<QVariant>() << ZoneNames::DECK << -1);
aMoveRfgToHand = new QAction(this);
aMoveRfgToHand->setData(QList<QVariant>() << ZoneNames::HAND << 0);
aMoveRfgToGrave = new QAction(this);
aMoveRfgToGrave->setData(QList<QVariant>() << ZoneNames::GRAVE << 0);
connect(aMoveRfgToTopLibrary, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone);
connect(aMoveRfgToBottomLibrary, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone);
connect(aMoveRfgToHand, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone);
connect(aMoveRfgToGrave, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone);
}
}
void RfgMenu::createViewActions()
{
PlayerActions *playerActions = player->getLogic()->getPlayerActions();
aViewRfg = new QAction(this);
connect(aViewRfg, &QAction::triggered, playerActions, &PlayerActions::actViewRfg);
}
void RfgMenu::retranslateUi()
{
setTitle(tr("&Exile"));
aViewRfg->setText(tr("&View exile"));
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
moveRfgMenu->setTitle(tr("&Move exile to..."));
aMoveRfgToTopLibrary->setText(tr("&Top of library"));
aMoveRfgToBottomLibrary->setText(tr("&Bottom of library"));
aMoveRfgToHand->setText(tr("&Hand"));
aMoveRfgToGrave->setText(tr("&Graveyard"));
}
}

View file

@ -1,44 +0,0 @@
/**
* @file rfg_menu.h
* @ingroup GameMenusZones
*/
//! \todo Document this file.
#ifndef COCKATRICE_RFG_MENU_H
#define COCKATRICE_RFG_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "abstract_player_component.h"
#include <QAction>
#include <QMenu>
class PlayerGraphicsItem;
class RfgMenu : public TearOffMenu, public AbstractPlayerComponent
{
Q_OBJECT
public:
explicit RfgMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr);
void createMoveActions();
void createViewActions();
void retranslateUi() override;
void setShortcutsActive() override
{
}
void setShortcutsInactive() override
{
}
QMenu *moveRfgMenu = nullptr;
QAction *aViewRfg = nullptr;
QAction *aMoveRfgToTopLibrary = nullptr;
QAction *aMoveRfgToBottomLibrary = nullptr;
QAction *aMoveRfgToHand = nullptr;
QAction *aMoveRfgToGrave = nullptr;
private:
PlayerGraphicsItem *player;
};
#endif // COCKATRICE_RFG_MENU_H

View file

@ -1,54 +0,0 @@
#include "say_menu.h"
#include "../../../client/settings/cache_settings.h"
#include "../player_actions.h"
#include "../player_logic.h"
SayMenu::SayMenu(PlayerGraphicsItem *_player) : player(_player)
{
connect(&SettingsCache::instance().messages(), &MessageSettings::messageMacrosChanged, this, &SayMenu::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()
{
clear();
int count = SettingsCache::instance().messages().getCount();
setEnabled(count > 0);
for (int i = 0; i < count; ++i) {
auto *newAction = new QAction(SettingsCache::instance().messages().getMessageAt(i), this);
connect(newAction, &QAction::triggered, player->getLogic()->getPlayerActions(), &PlayerActions::actSayMessage);
addAction(newAction);
}
if (shortcutsActive) {
setShortcutsActive();
}
}

View file

@ -1,33 +0,0 @@
/**
* @file say_menu.h
* @ingroup GameMenusPlayers
*/
//! \todo Document this file.
#ifndef COCKATRICE_SAY_MENU_H
#define COCKATRICE_SAY_MENU_H
#include "abstract_player_component.h"
#include <QMenu>
class PlayerGraphicsItem;
class SayMenu : public QMenu, public AbstractPlayerComponent
{
Q_OBJECT
public:
explicit SayMenu(PlayerGraphicsItem *player);
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
private slots:
void initSayMenu();
private:
PlayerGraphicsItem *player;
bool shortcutsActive = false;
};
#endif // COCKATRICE_SAY_MENU_H

View file

@ -1,35 +0,0 @@
#include "sideboard_menu.h"
#include "../player_actions.h"
#include "../player_logic.h"
SideboardMenu::SideboardMenu(PlayerGraphicsItem *player, QMenu *playerMenu) : QMenu(playerMenu)
{
aViewSideboard = new QAction(this);
connect(aViewSideboard, &QAction::triggered, player->getLogic()->getPlayerActions(),
&PlayerActions::actViewSideboard);
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
addAction(aViewSideboard);
}
retranslateUi();
}
void SideboardMenu::retranslateUi()
{
setTitle(tr("&Sideboard"));
aViewSideboard->setText(tr("&View sideboard"));
}
void SideboardMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aViewSideboard->setShortcuts(shortcuts.getShortcut("Player/aViewSideboard"));
}
void SideboardMenu::setShortcutsInactive()
{
aViewSideboard->setShortcut(QKeySequence());
}

View file

@ -1,31 +0,0 @@
/**
* @file sideboard_menu.h
* @ingroup GameMenusZones
*/
//! \todo Document this file.
#ifndef COCKATRICE_SIDEBOARD_MENU_H
#define COCKATRICE_SIDEBOARD_MENU_H
#include "abstract_player_component.h"
#include <QMenu>
class PlayerGraphicsItem;
class SideboardMenu : public QMenu, public AbstractPlayerComponent
{
Q_OBJECT
public:
explicit SideboardMenu(PlayerGraphicsItem *player, QMenu *playerMenu);
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
private:
PlayerGraphicsItem *player;
QAction *aViewSideboard;
};
#endif // COCKATRICE_SIDEBOARD_MENU_H

View file

@ -1,146 +0,0 @@
#include "utility_menu.h"
#include "../../../interface/deck_loader/deck_loader.h"
#include "../player_actions.h"
#include "../player_logic.h"
#include "player_menu.h"
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <libcockatrice/deck_list/tree/inner_deck_list_node.h>
UtilityMenu::UtilityMenu(PlayerGraphicsItem *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player)
{
PlayerActions *playerActions = player->getLogic()->getPlayerActions();
connect(playerActions, &PlayerActions::requestEnableAndSetCreateAnotherTokenAction, this,
&UtilityMenu::setAndEnableCreateAnotherTokenAction);
connect(playerActions, &PlayerActions::requestSetLastToken, this, &UtilityMenu::setLastToken);
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
aUntapAll = new QAction(this);
connect(aUntapAll, &QAction::triggered, playerActions, &PlayerActions::actUntapAll);
aRollDie = new QAction(this);
connect(aRollDie, &QAction::triggered, playerActions, &PlayerActions::actRequestRollDieDialog);
aFlipCoin = new QAction(this);
connect(aFlipCoin, &QAction::triggered, playerActions, &PlayerActions::actFlipCoin);
aCreateToken = new QAction(this);
connect(aCreateToken, &QAction::triggered, playerActions, [this]() {
player->getLogic()->getPlayerActions()->actRequestCreateTokenDialog(getPredefinedTokens());
});
aCreateAnotherToken = new QAction(this);
connect(aCreateAnotherToken, &QAction::triggered, playerActions, &PlayerActions::actCreateAnotherToken);
aCreateAnotherToken->setEnabled(false);
aIncrementAllCardCounters = new QAction(this);
connect(aIncrementAllCardCounters, &QAction::triggered, playerActions, [this]() {
player->getLogic()->getPlayerActions()->actIncrementAllCardCounters(
player->getGameScene()->selectedCards());
});
createPredefinedTokenMenu = new QMenu(QString());
createPredefinedTokenMenu->setEnabled(false);
connect(player->getLogic(), &PlayerLogic::deckChanged, this, &UtilityMenu::populatePredefinedTokensMenu);
playerMenu->addAction(aIncrementAllCardCounters);
playerMenu->addSeparator();
playerMenu->addAction(aUntapAll);
playerMenu->addSeparator();
playerMenu->addAction(aRollDie);
playerMenu->addAction(aFlipCoin);
playerMenu->addSeparator();
playerMenu->addAction(aCreateToken);
playerMenu->addAction(aCreateAnotherToken);
playerMenu->addMenu(createPredefinedTokenMenu);
playerMenu->addSeparator();
} else {
aCreateToken = nullptr;
aCreateAnotherToken = nullptr;
createPredefinedTokenMenu = nullptr;
aIncrementAllCardCounters = nullptr;
aUntapAll = nullptr;
aRollDie = nullptr;
aFlipCoin = nullptr;
}
retranslateUi();
}
void UtilityMenu::populatePredefinedTokensMenu()
{
clear();
setEnabled(false);
predefinedTokens.clear();
const DeckList &deckList = player->getLogic()->getDeck();
if (deckList.isEmpty()) {
return;
}
auto tokenCardNodes = deckList.getCardNodes({DECK_ZONE_TOKENS});
if (!tokenCardNodes.isEmpty()) {
setEnabled(true);
for (int i = 0; i < tokenCardNodes.size(); ++i) {
const QString tokenName = tokenCardNodes[i]->getName();
predefinedTokens.append(tokenName);
QAction *a = addAction(tokenName);
if (i < 10) {
a->setShortcut(QKeySequence("Alt+" + QString::number((i + 1) % 10)));
}
connect(a, &QAction::triggered, player->getLogic()->getPlayerActions(),
&PlayerActions::actCreatePredefinedToken);
}
}
}
void UtilityMenu::setLastToken(CardInfoPtr lastToken)
{
if (!createAnotherTokenActionExists()) {
return;
}
player->getLogic()->getPlayerActions()->setLastTokenInfo(lastToken);
}
void UtilityMenu::retranslateUi()
{
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
aIncrementAllCardCounters->setText(tr("Increment all card counters"));
aUntapAll->setText(tr("&Untap all permanents"));
aRollDie->setText(tr("R&oll die..."));
aFlipCoin->setText(tr("Flip coin"));
aCreateToken->setText(tr("&Create token..."));
aCreateAnotherToken->setText(tr("C&reate another token"));
createPredefinedTokenMenu->setTitle(tr("Cr&eate predefined token"));
}
}
void UtilityMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
aIncrementAllCardCounters->setShortcuts(shortcuts.getShortcut("Player/aIncrementAllCardCounters"));
aUntapAll->setShortcuts(shortcuts.getShortcut("Player/aUntapAll"));
aRollDie->setShortcuts(shortcuts.getShortcut("Player/aRollDie"));
aFlipCoin->setShortcuts(shortcuts.getShortcut("Player/aFlipCoin"));
aCreateToken->setShortcuts(shortcuts.getShortcut("Player/aCreateToken"));
aCreateAnotherToken->setShortcuts(shortcuts.getShortcut("Player/aCreateAnotherToken"));
}
}
void UtilityMenu::setShortcutsInactive()
{
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
aUntapAll->setShortcut(QKeySequence());
aRollDie->setShortcut(QKeySequence());
aFlipCoin->setShortcut(QKeySequence());
aCreateToken->setShortcut(QKeySequence());
aCreateAnotherToken->setShortcut(QKeySequence());
aIncrementAllCardCounters->setShortcut(QKeySequence());
}
}

View file

@ -1,56 +0,0 @@
/**
* @file utility_menu.h
* @ingroup GameMenusPlayers
*/
//! \todo Document this file.
#ifndef COCKATRICE_UTILITY_MENU_H
#define COCKATRICE_UTILITY_MENU_H
#include "abstract_player_component.h"
#include <QMenu>
#include <libcockatrice/card/card_info.h>
class PlayerGraphicsItem;
class UtilityMenu : public QMenu, public AbstractPlayerComponent
{
Q_OBJECT
public slots:
void populatePredefinedTokensMenu();
void setLastToken(CardInfoPtr lastToken);
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
public:
explicit UtilityMenu(PlayerGraphicsItem *player, QMenu *playerMenu);
[[nodiscard]] bool createAnotherTokenActionExists() const
{
return aCreateAnotherToken != nullptr;
}
void setAndEnableCreateAnotherTokenAction(QString text)
{
aCreateAnotherToken->setText(tr("C&reate another %1 token").arg(text));
aCreateAnotherToken->setEnabled(true);
}
QStringList getPredefinedTokens() const
{
return predefinedTokens;
}
private:
PlayerGraphicsItem *player;
QStringList predefinedTokens;
QMenu *createPredefinedTokenMenu;
QAction *aIncrementAllCardCounters;
QAction *aUntapAll, *aRollDie, *aFlipCoin;
QAction *aCreateToken, *aCreateAnotherToken;
};
#endif // COCKATRICE_UTILITY_MENU_H

View file

@ -1,14 +1,13 @@
#include "player_actions.h"
#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h"
#include "../../game_graphics/dialogs/dlg_roll_dice.h"
#include "../../game_graphics/player/card_menu_action_type.h"
#include "../../game_graphics/zones/hand_zone.h"
#include "../../game_graphics/zones/table_zone.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../../interface/widgets/utility/get_text_with_max.h"
#include "../board/card_item.h"
#include "../dialogs/dlg_move_top_cards_until.h"
#include "../dialogs/dlg_roll_dice.h"
#include "../zones/view_zone_logic.h"
#include "card_menu_action_type.h"
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/relation/card_relation.h>

View file

@ -7,9 +7,11 @@
#ifndef COCKATRICE_PLAYER_ACTIONS_H
#define COCKATRICE_PLAYER_ACTIONS_H
#include "../dialogs/dlg_create_token.h"
#include "../dialogs/dlg_move_top_cards_until.h"
#include "card_menu_action_type.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/dialogs/dlg_create_token.h"
#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h"
#include "../../game_graphics/player/card_menu_action_type.h"
#include "event_processing_options.h"
#include "player_logic.h"
@ -26,7 +28,6 @@ class Message;
}
} // namespace google
class CardItem;
class Command_MoveCard;
class GameEventContext;
class PendingCommand;

View file

@ -1,34 +0,0 @@
#include "player_area.h"
#include "../../interface/theme_manager.h"
#include <QPainter>
PlayerArea::PlayerArea(QGraphicsItem *parentItem) : QObject(), QGraphicsItem(parentItem)
{
setCacheMode(DeviceCoordinateCache);
connect(themeManager, &ThemeManager::themeChanged, this, &PlayerArea::updateBg);
updateBg();
}
void PlayerArea::updateBg()
{
update();
}
void PlayerArea::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Player, playerZoneId);
painter->fillRect(boundingRect(), brush);
}
void PlayerArea::setSize(qreal width, qreal height)
{
prepareGeometryChange();
bRect = QRectF(0, 0, width, height);
}
void PlayerArea::setPlayerZoneId(int _playerZoneId)
{
playerZoneId = _playerZoneId;
}

View file

@ -1,52 +0,0 @@
/**
* @file player_area.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef COCKATRICE_PLAYER_AREA_H
#define COCKATRICE_PLAYER_AREA_H
#include "../../game_graphics/board/graphics_item_type.h"
#include "QGraphicsItem"
/**
* The entire graphical area belonging to a single player.
*/
class PlayerArea : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
private:
QRectF bRect;
int playerZoneId;
private slots:
void updateBg();
public:
enum
{
Type = typeOther
};
[[nodiscard]] int type() const override
{
return Type;
}
explicit PlayerArea(QGraphicsItem *parent = nullptr);
[[nodiscard]] QRectF boundingRect() const override
{
return bRect;
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void setSize(qreal width, qreal height);
void setPlayerZoneId(int _playerZoneId);
[[nodiscard]] int getPlayerZoneId() const
{
return playerZoneId;
}
};
#endif // COCKATRICE_PLAYER_AREA_H

View file

@ -1,298 +0,0 @@
#include "player_dialogs.h"
#include "../../client/settings/card_counter_settings.h"
#include "../../interface/widgets/utility/get_text_with_max.h"
#include "../board/card_item.h"
#include "../dialogs/dlg_roll_dice.h"
#include "../player/player_graphics_item.h"
#include <QInputDialog>
#include <libcockatrice/card/relation/card_relation.h>
PlayerDialogs::PlayerDialogs(PlayerGraphicsItem *_player, PlayerActions *_playerActions)
: QObject(_player), player(_player), playerActions(_playerActions)
{
connect(playerActions, &PlayerActions::requestViewTopCardsDialog, this,
&PlayerDialogs::onViewTopCardsDialogRequested);
connect(playerActions, &PlayerActions::requestViewBottomCardsDialog, this,
&PlayerDialogs::onViewBottomCardsDialogRequested);
connect(playerActions, &PlayerActions::requestShuffleTopDialog, this, &PlayerDialogs::onShuffleTopDialogRequested);
connect(playerActions, &PlayerActions::requestShuffleBottomDialog, this,
&PlayerDialogs::onShuffleBottomDialogRequested);
connect(playerActions, &PlayerActions::requestMulliganDialog, this, &PlayerDialogs::onMulliganDialogRequested);
connect(playerActions, &PlayerActions::requestDrawCardsDialog, this, &PlayerDialogs::onDrawCardsDialogRequested);
connect(playerActions, &PlayerActions::requestMoveTopCardsToDialog, this,
&PlayerDialogs::onMoveTopCardsToDialogRequested);
connect(playerActions, &PlayerActions::requestMoveTopCardsUntilDialog, this,
&PlayerDialogs::onMoveTopCardsUntilDialogRequested);
connect(playerActions, &PlayerActions::requestMoveBottomCardsToDialog, this,
&PlayerDialogs::onMoveBottomCardsToDialogRequested);
connect(playerActions, &PlayerActions::requestDrawBottomCardsDialog, this,
&PlayerDialogs::onDrawBottomCardsDialogRequested);
connect(playerActions, &PlayerActions::requestRollDieDialog, this, &PlayerDialogs::onRollDieDialogRequested);
connect(playerActions, &PlayerActions::requestCreateTokenDialog, this,
&PlayerDialogs::onCreateTokenDialogRequested);
connect(playerActions, &PlayerActions::requestCreateRelatedFromRelationDialog, this,
&PlayerDialogs::onCreateRelatedFromRelationDialogRequested);
connect(playerActions, &PlayerActions::requestMoveCardXCardsFromTopDialog, this,
&PlayerDialogs::onMoveCardXCardsFromTopDialogRequested);
connect(playerActions, &PlayerActions::requestSetPTDialog, this, &PlayerDialogs::onSetPTDialogRequested);
connect(playerActions, &PlayerActions::requestSetAnnotationDialog, this,
&PlayerDialogs::onSetAnnotationDialogRequested);
connect(playerActions, &PlayerActions::requestSetCardCounterDialog, this,
&PlayerDialogs::onSetCardCounterDialogRequested);
}
void PlayerDialogs::onViewTopCardsDialogRequested(int defaultNumberTopCards, int deckSize)
{
bool ok;
int number = QInputDialog::getInt(dialogParent(), tr("View top cards of library"),
tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1,
deckSize, 1, &ok);
if (ok) {
playerActions->actViewTopCards(number);
}
}
void PlayerDialogs::onViewBottomCardsDialogRequested(int defaultNumberBottomCards, int deckSize)
{
bool ok;
int number = QInputDialog::getInt(dialogParent(), tr("View bottom cards of library"),
tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberBottomCards, 1,
deckSize, 1, &ok);
if (ok) {
playerActions->actViewBottomCards(number);
}
}
void PlayerDialogs::onShuffleTopDialogRequested(int defaultNumberTopCards, int maxCards)
{
bool ok;
int number = QInputDialog::getInt(dialogParent(), tr("Shuffle top cards of library"),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1,
maxCards, 1, &ok);
if (ok) {
playerActions->actShuffleTop(number);
}
}
void PlayerDialogs::onShuffleBottomDialogRequested(int defaultNumberBottomCards, int maxCards)
{
bool ok;
int number = QInputDialog::getInt(dialogParent(), tr("Shuffle bottom cards of library"),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1,
maxCards, 1, &ok);
if (ok) {
playerActions->actShuffleBottom(number);
}
}
void PlayerDialogs::onMulliganDialogRequested(int startSize, int handSize, int deckSize)
{
bool ok;
int number = QInputDialog::getInt(dialogParent(), tr("Draw hand"),
tr("Number of cards: (max. %1)").arg(deckSize) + '\n' +
tr("0 and lower are in comparison to current hand size"),
startSize, -handSize, deckSize, 1, &ok);
if (ok) {
playerActions->actMulligan(number);
}
}
void PlayerDialogs::onDrawCardsDialogRequested(int defaultNumberTopCards, int deckSize)
{
bool ok;
int number = QInputDialog::getInt(dialogParent(), tr("Draw cards"), tr("Number of cards: (max. %1)").arg(deckSize),
defaultNumberTopCards, 1, deckSize, 1, &ok);
if (ok) {
playerActions->actDrawCards(number);
}
}
void PlayerDialogs::onMoveTopCardsToDialogRequested(int defaultNumberTopCards,
int maxCards,
const QString &targetZone,
const QString &zoneDisplayName,
bool faceDown)
{
bool ok;
int number = QInputDialog::getInt(dialogParent(), tr("Move top cards to %1").arg(zoneDisplayName),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1,
maxCards, 1, &ok);
if (ok) {
playerActions->moveTopCardsTo(number, targetZone, faceDown);
}
}
void PlayerDialogs::onMoveTopCardsUntilDialogRequested(MoveTopCardsUntilOptions options)
{
DlgMoveTopCardsUntil dlg(dialogParent(), options);
if (!dlg.exec()) {
return;
}
playerActions->moveTopCardsUntil(dlg.getExpr(), dlg.getOptions());
}
void PlayerDialogs::onMoveBottomCardsToDialogRequested(int defaultNumberBottomCards,
int maxCards,
const QString &targetZone,
const QString &zoneDisplayName,
bool faceDown)
{
bool ok;
int number = QInputDialog::getInt(dialogParent(), tr("Move bottom cards to %1").arg(zoneDisplayName),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1,
maxCards, 1, &ok);
if (ok) {
playerActions->moveBottomCardsTo(number, targetZone, faceDown);
}
}
void PlayerDialogs::onDrawBottomCardsDialogRequested(int defaultNumberBottomCards, int maxCards)
{
bool ok;
int number =
QInputDialog::getInt(dialogParent(), tr("Draw bottom cards"), tr("Number of cards: (max. %1)").arg(maxCards),
defaultNumberBottomCards, 1, maxCards, 1, &ok);
if (ok) {
playerActions->actDrawBottomCards(number);
}
}
void PlayerDialogs::onRollDieDialogRequested()
{
DlgRollDice dlg(dialogParent());
if (!dlg.exec()) {
return;
}
playerActions->actRollDie(dlg.getDieSideCount(), dlg.getDiceToRollCount());
}
void PlayerDialogs::onCreateRelatedFromRelationDialogRequested(const CardItem *sourceCard,
const CardRelation *cardRelation)
{
if (sourceCard == nullptr || cardRelation == nullptr) {
playerActions->setLastRelatedCreationSucceeded(false);
return;
}
int variableCount = cardRelation->getDefaultCount();
if (cardRelation->getIsVariable()) {
bool ok;
emit requestDialogSemaphore(true);
variableCount = QInputDialog::getInt(dialogParent(), tr("Create tokens"), tr("Number:"),
cardRelation->getDefaultCount(), 1, MAX_TOKENS_PER_DIALOG, 1, &ok);
emit requestDialogSemaphore(false);
if (!ok) {
playerActions->setLastRelatedCreationSucceeded(false); // cancelled
return;
}
}
const bool succeeded = playerActions->createRelatedFromRelation(sourceCard, cardRelation, variableCount);
playerActions->setLastRelatedCreationSucceeded(succeeded);
if (succeeded) {
playerActions->onRelatedCardCreated(sourceCard, cardRelation); // only on confirmed success
}
}
void PlayerDialogs::onCreateTokenDialogRequested(const QStringList &predefinedTokens)
{
DlgCreateToken dlg(predefinedTokens, dialogParent());
if (!dlg.exec()) {
return;
}
playerActions->actCreateToken(dlg.getTokenInfo());
}
void PlayerDialogs::onMoveCardXCardsFromTopDialogRequested(int defaultNumberTopCardsToPlaceBelow, int deckSize)
{
bool ok;
int number =
QInputDialog::getInt(dialogParent(), tr("Place card X cards from top of library"),
tr("Which position should this card be placed:") + "\n" + tr("(max. %1)").arg(deckSize),
defaultNumberTopCardsToPlaceBelow, 1, deckSize, 1, &ok);
number -= 1; // indexes start at 0
if (ok) {
playerActions->actMoveCardXCardsFromTop(player->getGameScene()->selectedCards(), number);
}
}
void PlayerDialogs::onSetPTDialogRequested(const QString &oldPT)
{
bool ok;
auto cards = player->getGameScene()->selectedCards();
emit requestDialogSemaphore(true);
QString pt = getTextWithMax(dialogParent(), tr("Change power/toughness"), tr("Change stats to:"), QLineEdit::Normal,
oldPT, &ok);
emit requestDialogSemaphore(false);
if (!ok || player->getLogic()->clearCardsToDelete()) {
return;
}
playerActions->actSetPT(cards, pt);
}
void PlayerDialogs::onSetAnnotationDialogRequested(const QString &oldAnnotation)
{
auto cards = player->getGameScene()->selectedCards();
emit requestDialogSemaphore(true);
AnnotationDialog *dialog = new AnnotationDialog(dialogParent());
dialog->setOptions(QInputDialog::UsePlainTextEditForTextInput);
dialog->setWindowTitle(tr("Set annotation"));
dialog->setLabelText(tr("Please enter the new annotation:"));
dialog->setTextValue(oldAnnotation);
bool ok = dialog->exec();
emit requestDialogSemaphore(false);
if (!ok || player->getLogic()->clearCardsToDelete()) {
return;
}
QString annotation = dialog->textValue().left(MAX_NAME_LENGTH);
playerActions->actSetAnnotation(cards, annotation);
}
void PlayerDialogs::onSetCardCounterDialogRequested(int counterId, const QString &oldValueForDlg)
{
auto cards = player->getGameScene()->selectedCards();
emit requestDialogSemaphore(true);
auto &cardCounterSettings = SettingsCache::instance().cardCounters();
QString counterName = cardCounterSettings.displayName(counterId);
AbstractCounterDialog dialog(counterName, oldValueForDlg, dialogParent());
int ok = dialog.exec();
emit requestDialogSemaphore(false);
if (!ok || player->getLogic()->clearCardsToDelete()) {
return;
}
playerActions->actSetCardCounter(cards, counterId, dialog.textValue());
}

View file

@ -1,62 +0,0 @@
#ifndef COCKATRICE_PLAYER_DIALOGS_H
#define COCKATRICE_PLAYER_DIALOGS_H
#include "player_actions.h"
#include <QGraphicsView>
#include <QObject>
class PlayerGraphicsItem;
class PlayerDialogs : public QObject
{
Q_OBJECT
public:
explicit PlayerDialogs(PlayerGraphicsItem *player, PlayerActions *playerActions);
signals:
void requestDialogSemaphore(bool active);
public slots:
void onViewTopCardsDialogRequested(int defaultNumberTopCards, int deckSize);
void onViewBottomCardsDialogRequested(int defaultNumberBottomCards, int deckSize);
void onShuffleTopDialogRequested(int defaultNumberTopCards, int maxCards);
void onShuffleBottomDialogRequested(int defaultNumberBottomCards, int maxCards);
void onMulliganDialogRequested(int startSize, int handSize, int deckSize);
void onDrawCardsDialogRequested(int defaultNumberTopCards, int deckSize);
void onMoveTopCardsToDialogRequested(int defaultNumberTopCards,
int maxCards,
const QString &targetZone,
const QString &zoneDisplayName,
bool faceDown);
void onMoveTopCardsUntilDialogRequested(MoveTopCardsUntilOptions options);
void onMoveBottomCardsToDialogRequested(int defaultNumberBottomCards,
int maxCards,
const QString &targetZone,
const QString &zoneDisplayName,
bool faceDown);
void onDrawBottomCardsDialogRequested(int defaultNumberBottomCards, int maxCards);
void onRollDieDialogRequested();
void onCreateRelatedFromRelationDialogRequested(const CardItem *sourceCard, const CardRelation *cardRelation);
void onCreateTokenDialogRequested(const QStringList &predefinedTokens);
void onMoveCardXCardsFromTopDialogRequested(int defaultNumberTopCardsToPlaceBelow, int deckSize);
void onSetPTDialogRequested(const QString &oldPT);
void onSetAnnotationDialogRequested(const QString &oldAnnotation);
void onSetCardCounterDialogRequested(int counterId, const QString &oldValueForDlg);
private:
PlayerGraphicsItem *player;
PlayerActions *playerActions;
QWidget *dialogParent() const
{
if (auto *s = player->scene()) {
if (auto *v = s->views().value(0)) {
return v->window();
}
}
return nullptr;
}
};
#endif // COCKATRICE_PLAYER_DIALOGS_H

View file

@ -1,10 +1,10 @@
#include "player_event_handler.h"
#include "../../game_graphics/board/arrow_item.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../board/arrow_data.h"
#include "../board/arrow_item.h"
#include "../board/card_item.h"
#include "../board/card_list.h"
#include "player_actions.h"
#include "player_logic.h"

View file

@ -1,282 +0,0 @@
#include "player_graphics_item.h"
#include "../../game_graphics/zones/hand_zone.h"
#include "../../game_graphics/zones/pile_zone.h"
#include "../../game_graphics/zones/stack_zone.h"
#include "../../game_graphics/zones/table_zone.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../board/abstract_card_item.h"
#include "../board/counter_general.h"
#include "../hand_counter.h"
#include "player_actions.h"
#include "player_dialogs.h"
#include <QGraphicsView>
PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
{
connect(&SettingsCache::instance(), &SettingsCache::horizontalHandChanged, this,
&PlayerGraphicsItem::rearrangeZones);
connect(&SettingsCache::instance(), &SettingsCache::handJustificationChanged, this,
&PlayerGraphicsItem::rearrangeZones);
connect(player, &PlayerLogic::rearrangeCounters, this, &PlayerGraphicsItem::rearrangeCounters);
connect(player, &PlayerLogic::activeChanged, this, &PlayerGraphicsItem::onPlayerActiveChanged);
connect(player, &PlayerLogic::concededChanged, this, [this](int, bool c) { setVisible(!c); });
connect(player, &PlayerLogic::zoneIdChanged, this, [this](int id) { playerArea->setPlayerZoneId(id); });
connect(player, &PlayerLogic::counterAdded, this, &PlayerGraphicsItem::onCounterAdded);
connect(player, &PlayerLogic::counterRemoved, this, &PlayerGraphicsItem::onCounterRemoved);
playerMenu = new PlayerMenu(this);
connect(playerMenu, &PlayerMenu::shortcutsActivated, this, [this]() {
for (auto *ctr : counterWidgets) {
ctr->setShortcutsActive();
}
});
connect(playerMenu, &PlayerMenu::shortcutsDeactivated, this, [this]() {
for (auto *ctr : counterWidgets) {
ctr->setShortcutsInactive();
}
});
connect(playerMenu, &PlayerMenu::retranslateRequested, this, [this]() {
for (auto *ctr : counterWidgets) {
ctr->retranslateUi();
}
});
playerDialogs = new PlayerDialogs(this, player->getPlayerActions());
connect(playerDialogs, &PlayerDialogs::requestDialogSemaphore, player, &PlayerLogic::setDialogSemaphore);
playerArea = new PlayerArea(this);
playerTarget = new PlayerTarget(player, playerArea);
qreal avatarMargin =
(counterAreaWidth + CardDimensions::HEIGHT_F + 15 - playerTarget->boundingRect().width()) / 2.0;
playerTarget->setPos(QPointF(avatarMargin, avatarMargin));
initializeZones();
playerMenu->setMenusForGraphicItems();
connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect);
updateBoundingRect();
rearrangeZones();
retranslateUi();
}
void PlayerGraphicsItem::retranslateUi()
{
playerMenu->retranslateUi();
QMapIterator<QString, CardZoneLogic *> zoneIterator(player->getZones());
while (zoneIterator.hasNext()) {
emit zoneIterator.next().value()->retranslateUi();
}
}
void PlayerGraphicsItem::onPlayerActiveChanged(bool _active)
{
tableZoneGraphicsItem->setActive(_active);
}
void PlayerGraphicsItem::initializeZones()
{
deckZoneGraphicsItem = new PileZone(player->getDeckZone(), this);
auto base = QPointF(counterAreaWidth + (CardDimensions::HEIGHT_F - CardDimensions::WIDTH_F + 15) / 2.0,
10 + playerTarget->boundingRect().height() + 5 -
(CardDimensions::HEIGHT_F - CardDimensions::WIDTH_F) / 2.0);
deckZoneGraphicsItem->setPos(base);
qreal h = deckZoneGraphicsItem->boundingRect().width() + 5;
sideboardGraphicsItem = new PileZone(player->getSideboardZone(), this);
player->getSideboardZone()->setGraphicsVisibility(false);
auto *handCounter = new HandCounter(playerArea);
handCounter->setPos(base + QPointF(0, h + 10));
qreal h2 = handCounter->boundingRect().height();
graveyardZoneGraphicsItem = new PileZone(player->getGraveZone(), this);
graveyardZoneGraphicsItem->setPos(base + QPointF(0, h + h2 + 10));
rfgZoneGraphicsItem = new PileZone(player->getRfgZone(), this);
rfgZoneGraphicsItem->setPos(base + QPointF(0, 2 * h + h2 + 10));
tableZoneGraphicsItem = new TableZone(player->getTableZone(), mirrored, this);
connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect);
connect(this, &PlayerGraphicsItem::mirroredChanged, tableZoneGraphicsItem, &TableZone::setMirrored);
stackZoneGraphicsItem =
new StackZone(player->getStackZone(), static_cast<int>(tableZoneGraphicsItem->boundingRect().height()), this);
handZoneGraphicsItem =
new HandZone(player->getHandZone(), static_cast<int>(tableZoneGraphicsItem->boundingRect().height()), this);
connect(player->getPlayerActions(), &PlayerActions::requestSortHand, handZoneGraphicsItem, &HandZone::sortHand);
connect(handZoneGraphicsItem->getLogic(), &HandZoneLogic::cardCountChanged, handCounter,
&HandCounter::updateNumber);
connect(handCounter, &HandCounter::showContextMenu, handZoneGraphicsItem, &HandZone::showContextMenu);
}
QRectF PlayerGraphicsItem::boundingRect() const
{
return bRect;
}
qreal PlayerGraphicsItem::getMinimumWidth() const
{
qreal result = tableZoneGraphicsItem->getMinimumWidth() + CardDimensions::HEIGHT_F + 15 + counterAreaWidth +
stackZoneGraphicsItem->boundingRect().width();
if (!SettingsCache::instance().getHorizontalHand()) {
result += handZoneGraphicsItem->boundingRect().width();
}
return result;
}
void PlayerGraphicsItem::paint(QPainter * /*painter*/,
const QStyleOptionGraphicsItem * /*option*/,
QWidget * /*widget*/)
{
}
void PlayerGraphicsItem::processSceneSizeChange(int newPlayerWidth)
{
// Extend table (and hand, if horizontal) to accommodate the new player width.
qreal tableWidth = newPlayerWidth - CardDimensions::HEIGHT_F - 15 - counterAreaWidth -
stackZoneGraphicsItem->boundingRect().width();
if (!SettingsCache::instance().getHorizontalHand()) {
tableWidth -= handZoneGraphicsItem->boundingRect().width();
}
tableZoneGraphicsItem->setWidth(tableWidth);
handZoneGraphicsItem->setWidth(tableWidth + stackZoneGraphicsItem->boundingRect().width());
}
void PlayerGraphicsItem::setMirrored(bool _mirrored)
{
if (mirrored != _mirrored) {
mirrored = _mirrored;
emit mirroredChanged(mirrored);
rearrangeZones();
}
}
void PlayerGraphicsItem::onCounterAdded(CounterState *state)
{
AbstractCounter *widget;
if (state->getName() == "life") {
widget = playerTarget->addCounter(state);
} else {
widget = new GeneralCounter(state, player, true, this);
}
counterWidgets.insert(state->getId(), widget);
if (playerMenu->getCountersMenu() && widget->getMenu()) {
playerMenu->getCountersMenu()->addMenu(widget->getMenu());
}
if (playerMenu->getShortcutsActive()) {
widget->setShortcutsActive();
}
rearrangeCounters();
}
void PlayerGraphicsItem::onCounterRemoved(int counterId)
{
auto *widget = counterWidgets.take(counterId);
if (!widget) {
return;
}
if (playerMenu->getCountersMenu() && widget->getMenu()) {
playerMenu->getCountersMenu()->removeAction(widget->getMenu()->menuAction());
}
widget->delCounter();
rearrangeCounters();
}
void PlayerGraphicsItem::rearrangeCounters()
{
qreal ySize = boundingRect().y() + 80;
constexpr qreal padding = 5;
for (auto *ctr : counterWidgets.values()) {
if (!ctr->getShownInCounterArea()) {
continue;
}
QRectF br = ctr->boundingRect();
ctr->setPos((counterAreaWidth - br.width()) / 2, ySize);
ySize += br.height() + padding;
}
}
void PlayerGraphicsItem::rearrangeZones()
{
auto base = QPointF(CardDimensions::HEIGHT_F + counterAreaWidth + 15, 0);
if (SettingsCache::instance().getHorizontalHand()) {
if (mirrored) {
if (player->getHandZone()->contentsKnown()) {
handVisible = true;
handZoneGraphicsItem->setPos(base);
base += QPointF(0, handZoneGraphicsItem->boundingRect().height());
} else {
handVisible = false;
}
stackZoneGraphicsItem->setPos(base);
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
tableZoneGraphicsItem->setPos(base);
} else {
stackZoneGraphicsItem->setPos(base);
tableZoneGraphicsItem->setPos(base.x() + stackZoneGraphicsItem->boundingRect().width(), 0);
base += QPointF(0, tableZoneGraphicsItem->boundingRect().height());
if (player->getHandZone()->contentsKnown()) {
handVisible = true;
handZoneGraphicsItem->setPos(base);
} else {
handVisible = false;
}
}
handZoneGraphicsItem->setWidth(tableZoneGraphicsItem->getWidth() +
stackZoneGraphicsItem->boundingRect().width());
} else {
handVisible = true;
handZoneGraphicsItem->setPos(base);
base += QPointF(handZoneGraphicsItem->boundingRect().width(), 0);
stackZoneGraphicsItem->setPos(base);
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
tableZoneGraphicsItem->setPos(base);
}
handZoneGraphicsItem->setVisible(handVisible);
handZoneGraphicsItem->updateOrientation();
tableZoneGraphicsItem->reorganizeCards();
updateBoundingRect();
rearrangeCounters();
}
void PlayerGraphicsItem::updateBoundingRect()
{
prepareGeometryChange();
qreal width = CardDimensions::HEIGHT_F + 15 + counterAreaWidth + stackZoneGraphicsItem->boundingRect().width();
if (SettingsCache::instance().getHorizontalHand()) {
qreal handHeight = handVisible ? handZoneGraphicsItem->boundingRect().height() : 0;
bRect = QRectF(0, 0, width + tableZoneGraphicsItem->boundingRect().width(),
tableZoneGraphicsItem->boundingRect().height() + handHeight);
} else {
bRect = QRectF(
0, 0, width + handZoneGraphicsItem->boundingRect().width() + tableZoneGraphicsItem->boundingRect().width(),
tableZoneGraphicsItem->boundingRect().height());
}
playerArea->setSize(CardDimensions::HEIGHT_F + counterAreaWidth + 15, bRect.height());
emit sizeChanged();
}

View file

@ -1,146 +0,0 @@
/**
* @file player_graphics_item.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef COCKATRICE_PLAYER_GRAPHICS_ITEM_H
#define COCKATRICE_PLAYER_GRAPHICS_ITEM_H
#include "../board/abstract_counter.h"
#include "../game_scene.h"
#include "player_logic.h"
#include <QGraphicsObject>
class HandZone;
class PileZone;
class PlayerDialogs;
class PlayerTarget;
class StackZone;
class TableZone;
class ZoneViewZone;
class PlayerGraphicsItem : public QGraphicsObject
{
Q_OBJECT
public:
enum
{
Type = typeOther
};
int type() const override
{
return Type;
}
static constexpr int counterAreaWidth = 55;
explicit PlayerGraphicsItem(PlayerLogic *player);
void initializeZones();
[[nodiscard]] QRectF boundingRect() const override;
qreal getMinimumWidth() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void processSceneSizeChange(int newPlayerWidth);
void setMirrored(bool _mirrored);
bool getMirrored() const
{
return mirrored;
}
GameScene *getGameScene() const
{
return static_cast<GameScene *>(scene());
}
PlayerLogic *getLogic() const
{
return player;
}
[[nodiscard]] PlayerMenu *getPlayerMenu() const
{
return playerMenu;
}
PlayerArea *getPlayerArea() const
{
return playerArea;
}
PlayerTarget *getPlayerTarget() const
{
return playerTarget;
}
[[nodiscard]] PileZone *getDeckZoneGraphicsItem() const
{
return deckZoneGraphicsItem;
}
[[nodiscard]] PileZone *getSideboardZoneGraphicsItem() const
{
return sideboardGraphicsItem;
}
[[nodiscard]] PileZone *getGraveyardZoneGraphicsItem() const
{
return graveyardZoneGraphicsItem;
}
[[nodiscard]] PileZone *getRfgZoneGraphicsItem() const
{
return rfgZoneGraphicsItem;
}
[[nodiscard]] TableZone *getTableZoneGraphicsItem() const
{
return tableZoneGraphicsItem;
}
[[nodiscard]] StackZone *getStackZoneGraphicsItem() const
{
return stackZoneGraphicsItem;
}
[[nodiscard]] HandZone *getHandZoneGraphicsItem() const
{
return handZoneGraphicsItem;
}
public slots:
void onPlayerActiveChanged(bool _active);
void onCounterAdded(CounterState *state);
void onCounterRemoved(int counterId);
void rearrangeCounters();
void retranslateUi();
signals:
void sizeChanged();
void playerCountChanged();
void mirroredChanged(bool isMirrored);
void cardInfoRequested(const CardRef &cardRef);
private:
PlayerLogic *player;
PlayerMenu *playerMenu;
PlayerDialogs *playerDialogs;
PlayerArea *playerArea;
PlayerTarget *playerTarget;
QMap<int, AbstractCounter *> counterWidgets;
PileZone *deckZoneGraphicsItem;
PileZone *sideboardGraphicsItem;
PileZone *graveyardZoneGraphicsItem;
PileZone *rfgZoneGraphicsItem;
TableZone *tableZoneGraphicsItem;
StackZone *stackZoneGraphicsItem;
HandZone *handZoneGraphicsItem;
QRectF bRect;
bool mirrored;
bool handVisible = false;
private slots:
void updateBoundingRect();
void rearrangeZones();
};
#endif // COCKATRICE_PLAYER_GRAPHICS_ITEM_H

View file

@ -7,7 +7,7 @@
#ifndef COCKATRICE_PLAYER_INFO_H
#define COCKATRICE_PLAYER_INFO_H
#include "player_target.h"
#include "../../game_graphics/player/player_target.h"
#include <QObject>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>

View file

@ -1,236 +0,0 @@
#include "player_list_widget.h"
#include "../../interface/pixel_map_generator.h"
#include "../../interface/widgets/server/user/user_context_menu.h"
#include "../../interface/widgets/server/user/user_list_manager.h"
#include "../../interface/widgets/server/user/user_list_widget.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../../interface/widgets/tabs/tab_supervisor.h"
#include <QHeaderView>
#include <QMouseEvent>
#include <libcockatrice/protocol/pb/command_kick_from_game.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_playerproperties.pb.h>
#include <libcockatrice/protocol/pb/session_commands.pb.h>
PlayerListItemDelegate::PlayerListItemDelegate(QObject *const parent) : QStyledItemDelegate(parent)
{
}
bool PlayerListItemDelegate::editorEvent(QEvent *event,
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
if ((event->type() == QEvent::MouseButtonPress) && index.isValid()) {
auto *const mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::RightButton) {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
static_cast<PlayerListWidget *>(parent())->showContextMenu(mouseEvent->globalPosition().toPoint(), index);
#else
static_cast<PlayerListWidget *>(parent())->showContextMenu(mouseEvent->globalPos(), index);
#endif
return true;
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
PlayerListTWI::PlayerListTWI() : QTreeWidgetItem(Type)
{
}
bool PlayerListTWI::operator<(const QTreeWidgetItem &other) const
{
// Sort by spectator/player
if (data(1, Qt::UserRole) != other.data(1, Qt::UserRole)) {
return data(1, Qt::UserRole).toBool();
}
// Sort by player ID
return data(4, Qt::UserRole + 1).toInt() < other.data(4, Qt::UserRole + 1).toInt();
}
PlayerListWidget::PlayerListWidget(TabSupervisor *_tabSupervisor,
AbstractClient *_client,
AbstractGame *_game,
QWidget *parent)
: QTreeWidget(parent), tabSupervisor(_tabSupervisor), client(_client), game(_game), gameStarted(false)
{
readyIcon = QPixmap("theme:icons/ready_start");
notReadyIcon = QPixmap("theme:icons/not_ready_start");
concededIcon = QPixmap("theme:icons/conceded");
playerIcon = loadColorAdjustedPixmap("theme:icons/player");
judgeIcon = loadColorAdjustedPixmap("theme:icons/scales");
spectatorIcon = loadColorAdjustedPixmap("theme:icons/spectator");
lockIcon = QPixmap("theme:icons/lock");
if (tabSupervisor) {
itemDelegate = new PlayerListItemDelegate(this);
setItemDelegate(itemDelegate);
userContextMenu = new UserContextMenu(tabSupervisor, this, game);
connect(userContextMenu, &UserContextMenu::openMessageDialog, this, &PlayerListWidget::openMessageDialog);
} else {
userContextMenu = nullptr;
}
setMinimumHeight(40);
setIconSize(QSize(20, 15));
setColumnCount(6);
setColumnWidth(0, 20);
setColumnWidth(1, 20);
setColumnWidth(2, 20);
setColumnWidth(3, 20);
setColumnWidth(5, 20);
setHeaderHidden(true);
setRootIsDecorated(false);
setUniformRowHeights(true);
setItemsExpandable(false);
retranslateUi();
}
void PlayerListWidget::retranslateUi()
{
}
void PlayerListWidget::addPlayer(const ServerInfo_PlayerProperties &player)
{
QTreeWidgetItem *newPlayer = new PlayerListTWI;
players.insert(player.player_id(), newPlayer);
updatePlayerProperties(player);
addTopLevelItem(newPlayer);
sortItems(1, Qt::AscendingOrder);
resizeColumnToContents(4);
resizeColumnToContents(5);
}
void PlayerListWidget::updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId)
{
if (playerId == -1) {
playerId = prop.player_id();
}
QTreeWidgetItem *player = players.value(playerId, 0);
if (!player) {
return;
}
bool isSpectator = prop.has_spectator() && prop.spectator();
if (prop.has_judge() || prop.has_spectator()) {
if (prop.has_judge() && prop.judge()) {
player->setIcon(1, judgeIcon);
} else if (isSpectator) {
player->setIcon(1, spectatorIcon);
} else {
player->setIcon(1, playerIcon);
}
player->setData(1, Qt::UserRole, !isSpectator);
}
if (!isSpectator) {
if (prop.has_conceded()) {
player->setData(2, Qt::UserRole, prop.conceded());
}
if (prop.has_ready_start()) {
player->setData(2, Qt::UserRole + 1, prop.ready_start());
}
if (prop.has_conceded() || prop.has_ready_start()) {
player->setIcon(2, gameStarted ? (prop.conceded() ? concededIcon : QIcon())
: (prop.ready_start() ? readyIcon : notReadyIcon));
}
}
if (prop.has_user_info()) {
player->setData(3, Qt::UserRole, prop.user_info().user_level());
player->setIcon(3, UserLevelPixmapGenerator::generateIcon(
12, UserLevelFlags(prop.user_info().user_level()), prop.user_info().pawn_colors(), false,
QString::fromStdString(prop.user_info().privlevel())));
player->setText(4, QString::fromStdString(prop.user_info().name()));
const QString country = QString::fromStdString(prop.user_info().country());
if (!country.isEmpty()) {
player->setIcon(4, QIcon(CountryPixmapGenerator::generatePixmap(12, country)));
}
player->setData(4, Qt::UserRole, QString::fromStdString(prop.user_info().name()));
}
if (prop.has_player_id()) {
player->setData(4, Qt::UserRole + 1, prop.player_id());
}
if (!isSpectator) {
if (prop.has_deck_hash()) {
player->setText(5, QString::fromStdString(prop.deck_hash()));
}
if (prop.has_sideboard_locked()) {
player->setIcon(5, prop.sideboard_locked() ? lockIcon : QIcon());
}
}
if (prop.has_ping_seconds()) {
player->setIcon(0, QIcon(PingPixmapGenerator::generatePixmap(12, prop.ping_seconds(), 10)));
}
}
void PlayerListWidget::removePlayer(int playerId)
{
QTreeWidgetItem *player = players.value(playerId, 0);
if (!player) {
return;
}
players.remove(playerId);
delete takeTopLevelItem(indexOfTopLevelItem(player));
}
void PlayerListWidget::setActivePlayer(int playerId)
{
QMapIterator<int, QTreeWidgetItem *> i(players);
while (i.hasNext()) {
i.next();
QTreeWidgetItem *twi = i.value();
if (i.key() == playerId) {
twi->setBackground(4, QColor(150, 255, 150));
twi->setForeground(4, QColor(0, 0, 0));
} else {
twi->setBackground(4, palette().base().color());
twi->setForeground(4, palette().text().color());
}
}
}
void PlayerListWidget::setGameStarted(bool _gameStarted, bool resuming)
{
gameStarted = _gameStarted;
QMapIterator<int, QTreeWidgetItem *> i(players);
while (i.hasNext()) {
QTreeWidgetItem *twi = i.next().value();
bool isPlayer = twi->data(1, Qt::UserRole).toBool();
if (!isPlayer) {
continue;
}
if (gameStarted) {
if (resuming) {
twi->setIcon(2, twi->data(2, Qt::UserRole).toBool() ? concededIcon : QIcon());
} else {
twi->setData(2, Qt::UserRole, false);
twi->setIcon(2, QIcon());
}
} else {
twi->setIcon(2, notReadyIcon);
}
}
}
void PlayerListWidget::showContextMenu(const QPoint &pos, const QModelIndex &index)
{
if (!userContextMenu) {
return;
}
const QString &userName = index.sibling(index.row(), 4).data(Qt::UserRole).toString();
int playerId = index.sibling(index.row(), 4).data(Qt::UserRole + 1).toInt();
UserLevelFlags userLevel(index.sibling(index.row(), 3).data(Qt::UserRole).toInt());
QString deckHash = index.sibling(index.row(), 5).data().toString();
userContextMenu->showContextMenu(pos, userName, userLevel, true, playerId, deckHash);
}

View file

@ -1,71 +0,0 @@
/**
* @file player_list_widget.h
* @ingroup GameWidgets
*/
//! \todo Document this file.
#ifndef PLAYERLISTWIDGET_H
#define PLAYERLISTWIDGET_H
#include "player_logic.h"
#include <QIcon>
#include <QMap>
#include <QStyledItemDelegate>
#include <QTreeWidget>
class ServerInfo_PlayerProperties;
class TabSupervisor;
class AbstractClient;
class AbstractGame;
class UserContextMenu;
class PlayerListItemDelegate : public QStyledItemDelegate
{
public:
explicit PlayerListItemDelegate(QObject *parent);
bool editorEvent(QEvent *event,
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index) override;
};
class PlayerListTWI : public QTreeWidgetItem
{
public:
PlayerListTWI();
bool operator<(const QTreeWidgetItem &other) const override;
};
class PlayerListWidget : public QTreeWidget
{
Q_OBJECT
private:
PlayerListItemDelegate *itemDelegate;
QMap<int, QTreeWidgetItem *> players;
TabSupervisor *tabSupervisor;
AbstractClient *client;
AbstractGame *game;
UserContextMenu *userContextMenu;
QIcon readyIcon, notReadyIcon, concededIcon, playerIcon, judgeIcon, spectatorIcon, lockIcon;
bool gameStarted;
signals:
void openMessageDialog(const QString &userName, bool focus);
public:
PlayerListWidget(TabSupervisor *_tabSupervisor,
AbstractClient *_client,
AbstractGame *_game,
QWidget *parent = nullptr);
void retranslateUi();
void setActivePlayer(int playerId);
void setGameStarted(bool _gameStarted, bool resuming);
void showContextMenu(const QPoint &pos, const QModelIndex &index);
public slots:
void addPlayer(const ServerInfo_PlayerProperties &player);
void removePlayer(int playerId);
void updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId = -1);
};
#endif

View file

@ -1,18 +1,18 @@
#include "player_logic.h"
#include "../../game_graphics/board/arrow_item.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/board/counter_general.h"
#include "../../game_graphics/game_scene.h"
#include "../../game_graphics/player/player_target.h"
#include "../../game_graphics/zones/hand_zone.h"
#include "../../game_graphics/zones/pile_zone.h"
#include "../../game_graphics/zones/stack_zone.h"
#include "../../game_graphics/zones/table_zone.h"
#include "../../interface/theme_manager.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../board/arrow_item.h"
#include "../board/card_item.h"
#include "../board/card_list.h"
#include "../board/counter_general.h"
#include "../game_scene.h"
#include "player_actions.h"
#include "player_target.h"
#include <QDebug>
#include <QMenu>

View file

@ -7,6 +7,7 @@
#ifndef PLAYER_H
#define PLAYER_H
#include "../../game_graphics/player/player_area.h"
#include "../../interface/widgets/menus/tearoff_menu.h"
#include "../board/arrow_data.h"
#include "../interface/deck_loader/loaded_deck.h"
@ -14,10 +15,7 @@
#include "../zones/pile_zone_logic.h"
#include "../zones/stack_zone_logic.h"
#include "../zones/table_zone_logic.h"
#include "menu/player_menu.h"
#include "player_area.h"
#include "player_event_handler.h"
#include "player_graphics_item.h"
#include "player_info.h"
#include <QInputDialog>
@ -54,6 +52,7 @@ class PlayerMenu;
class QAction;
class QMenu;
class ServerInfo_Arrow;
class ServerInfo_Card;
class ServerInfo_Counter;
class ServerInfo_Player;
class ServerInfo_User;

View file

@ -1,169 +0,0 @@
#include "player_target.h"
#include "../../interface/pixel_map_generator.h"
#include "player_logic.h"
#include <QDebug>
#include <QPainter>
#include <QPixmapCache>
#include <QtMath>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
PlayerCounter::PlayerCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent)
: AbstractCounter(state, player, false, false, parent)
{
}
QRectF PlayerCounter::boundingRect() const
{
return {0, 0, 50, 30};
}
void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
const int radius = 8;
const qreal border = 1;
QPainterPath path(QPointF(50 - border / 2, border / 2));
path.lineTo(radius, border / 2);
path.arcTo(border / 2, border / 2, 2 * radius, 2 * radius, 90, 90);
path.lineTo(border / 2, 30 - border / 2);
path.lineTo(50 - border / 2, 30 - border / 2);
path.closeSubpath();
QPen pen(QColor(100, 100, 100));
pen.setWidth(border);
painter->setPen(pen);
painter->setBrush(hovered ? QColor(50, 50, 50, 160) : QColor(0, 0, 0, 160));
painter->drawPath(path);
QRectF translatedRect = path.controlPointRect();
QSize translatedSize = translatedRect.size().toSize();
QFont font("Serif");
font.setWeight(QFont::Bold);
font.setPixelSize(qMax(qRound(translatedSize.height() / 1.3), 9));
painter->setFont(font);
painter->setPen(Qt::white);
painter->drawText(translatedRect, Qt::AlignCenter, QString::number(value));
}
PlayerTarget::PlayerTarget(PlayerLogic *_owner, QGraphicsItem *parentItem)
: ArrowTarget(_owner, parentItem), playerCounter(nullptr)
{
setCacheMode(DeviceCoordinateCache);
const std::string &bmp = _owner->getPlayerInfo()->getUserInfo()->avatar_bmp();
if (!fullPixmap.loadFromData((const uchar *)bmp.data(), static_cast<uint>(bmp.size()))) {
fullPixmap = QPixmap();
}
}
PlayerTarget::~PlayerTarget()
{
// Explicit deletion is necessary in spite of parent/child relationship
// as we need this object to be alive to receive the destroyed() signal.
delete playerCounter;
}
QRectF PlayerTarget::boundingRect() const
{
return {0, 0, 160, 64};
}
void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
const ServerInfo_User *const info = owner->getPlayerInfo()->getUserInfo();
const qreal border = 2;
QRectF avatarBoundingRect = boundingRect().adjusted(border, border, -border, -border);
QRectF translatedRect = painter->combinedTransform().mapRect(avatarBoundingRect);
QSize translatedSize = translatedRect.size().toSize();
QPixmap cachedPixmap;
const QString cacheKey = "avatar" + QString::number(translatedSize.width()) + "_" +
QString::number(info->user_level()) + "_" + QString::number(fullPixmap.cacheKey());
if (!QPixmapCache::find(cacheKey, &cachedPixmap)) {
cachedPixmap = QPixmap(translatedSize.width(), translatedSize.height());
QPainter tempPainter(&cachedPixmap);
// pow(foo, 0.5) equals to sqrt(foo), but using sqrt(foo) in this context will produce a compile error with
// MSVC++
QRadialGradient grad(translatedRect.center(), qPow(translatedSize.width() * translatedSize.width() +
translatedSize.height() * translatedSize.height(),
0.5) /
2);
grad.setColorAt(1, Qt::black);
grad.setColorAt(0, QColor(180, 180, 180));
tempPainter.fillRect(QRectF(0, 0, translatedSize.width(), translatedSize.height()), grad);
if (fullPixmap.isNull()) {
int sideLength = translatedSize.height();
QPixmap tempPixmap = UserLevelPixmapGenerator::generatePixmap(
sideLength, UserLevelFlags(info->user_level()), info->pawn_colors(), false,
QString::fromStdString(info->privlevel()));
int x = (translatedSize.width() - sideLength) / 2;
int y = 0;
tempPainter.drawPixmap(x, y, tempPixmap);
} else {
QPixmap tempPixmap = fullPixmap.scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
int x = (translatedSize.width() - tempPixmap.width()) / 2;
int y = (translatedSize.height() - tempPixmap.height()) / 2;
tempPainter.drawPixmap(x, y, tempPixmap);
}
QPixmapCache::insert(cacheKey, cachedPixmap);
}
painter->save();
resetPainterTransform(painter);
painter->translate((translatedSize.width() - cachedPixmap.width()) / 2.0, 0);
painter->drawPixmap(translatedRect, cachedPixmap, cachedPixmap.rect());
painter->restore();
QRectF nameRect = QRectF(0, boundingRect().height() - 20, 110, 20);
painter->fillRect(nameRect, QColor(0, 0, 0, 160));
QRectF translatedNameRect = painter->combinedTransform().mapRect(nameRect);
painter->save();
resetPainterTransform(painter);
QString name = QString::fromStdString(info->name());
if (name.size() > 13) {
name = name.mid(0, 10) + "...";
}
QFont font;
font.setPixelSize(qMax(qRound(translatedNameRect.height() / 1.5), 9));
painter->setFont(font);
painter->setPen(Qt::white);
painter->drawText(translatedNameRect, Qt::AlignVCenter | Qt::AlignLeft, " " + name);
painter->restore();
QPen pen(QColor(100, 100, 100));
pen.setWidth(border);
pen.setJoinStyle(Qt::RoundJoin);
painter->setPen(pen);
painter->drawRect(boundingRect().adjusted(border / 2, border / 2, -border / 2, -border / 2));
if (getBeingPointedAt()) {
painter->fillRect(boundingRect(), QBrush(QColor(255, 0, 0, 100)));
}
}
AbstractCounter *PlayerTarget::addCounter(CounterState *state)
{
if (playerCounter) {
disconnect(playerCounter, nullptr, this, nullptr);
playerCounter->delCounter();
}
playerCounter = new PlayerCounter(state, owner, this);
playerCounter->setPos(boundingRect().width() - playerCounter->boundingRect().width(),
boundingRect().height() - playerCounter->boundingRect().height());
connect(playerCounter, &PlayerCounter::destroyed, this, &PlayerTarget::counterDeleted);
return playerCounter;
}
void PlayerTarget::counterDeleted()
{
playerCounter = nullptr;
}

View file

@ -1,54 +0,0 @@
/**
* @file player_target.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef PLAYERTARGET_H
#define PLAYERTARGET_H
#include "../../game_graphics/board/graphics_item_type.h"
#include "../board/abstract_counter.h"
#include "../board/arrow_target.h"
#include <QPixmap>
class PlayerLogic;
class PlayerCounter : public AbstractCounter
{
Q_OBJECT
public:
PlayerCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent);
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
};
class PlayerTarget : public ArrowTarget
{
Q_OBJECT
private:
QPixmap fullPixmap;
PlayerCounter *playerCounter;
public slots:
void counterDeleted();
public:
enum
{
Type = typePlayerTarget
};
int type() const override
{
return Type;
}
explicit PlayerTarget(PlayerLogic *_player = nullptr, QGraphicsItem *parentItem = nullptr);
~PlayerTarget() override;
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
AbstractCounter *addCounter(CounterState *state);
};
#endif

View file

@ -2,9 +2,9 @@
#include "../interface/widgets/tabs/tab_game.h"
Replay::Replay(TabGame *_tab, GameReplay *_replay) : AbstractGame(_tab)
Replay::Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent)
{
gameState = new GameState(this, 0, -1, tab->getTabSupervisor()->getIsLocalGame(), {}, false, false, -1, false);
gameState = new GameState(this, 0, -1, isLocalGame, {}, false, false, -1, false);
connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged);
playerManager = new PlayerManager(this, -1, false, true);
loadReplay(_replay);

View file

@ -15,7 +15,7 @@ class Replay : public AbstractGame
Q_OBJECT
public:
explicit Replay(TabGame *_tab, GameReplay *_replay);
explicit Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame);
};
#endif // COCKATRICE_REPLAY_H

View file

@ -1,133 +0,0 @@
/**
* @file z_value_layer_manager.h
* @ingroup GameGraphics
* @brief Semantic Z-value layer management for game scene rendering.
*
* This file provides a structured approach to Z-value allocation in the game scene.
* Z-values in Qt determine stacking order - higher values render on top of lower values.
*
* ## Layer Architecture
*
* The game scene is organized into three conceptual layers:
*
* 1. **Zone Layer (0-999)**: Zone backgrounds, containers, and static elements
* - Zone backgrounds (0.5-1.0)
* - Cards within zones (1.0 base + index)
*
* 2. **Card Layer (1-40,000,000)**: Dynamic card rendering on the table zone
* - Cards use formula: (actualY + CardDimensions::HEIGHT) * 100000 + (actualX + 1) * 100
* - Maximum card Z-value: ~40,000,000 (with 3 rows, actualY <= ~289)
*
* 3. **Overlay Layer (2,000,000,000+)**: UI elements that must appear above all cards
* - Hovered cards (+1)
* - Arrows (+3)
* - Zone views (+4)
* - Drag items (+5, +6)
* - Top UI elements (+7)
*
* ## Design Rationale
*
* The large gap between card Z-values (max ~40M) and overlay base (2B) provides
* safety margin for future table zone expansions while ensuring overlays always
* render above cards regardless of table position.
*
* ## Usage
*
* Prefer using the semantic constants from ZValues namespace:
* @code
* card->setZValue(ZValues::HOVERED_CARD);
* arrow->setZValue(ZValues::ARROWS);
* @endcode
*
* Use validation functions to verify card Z-values during development:
* @code
* Q_ASSERT(ZValueLayerManager::isValidCardZValue(cardZ));
* @endcode
*/
#ifndef Z_VALUE_LAYER_MANAGER_H
#define Z_VALUE_LAYER_MANAGER_H
#include <QtGlobal>
/**
* @namespace ZValueLayerManager
* @brief Utilities for Z-value validation and layer management.
*/
namespace ZValueLayerManager
{
/**
* @enum Layer
* @brief Semantic layer identifiers for Z-value allocation.
*
* These represent conceptual rendering layers, not actual Z-values.
* Use the corresponding ZValues constants for actual rendering.
*/
enum class Layer
{
Zone, ///< Zone-level elements like backgrounds and containers.
Card, ///< Cards rendered in zones (uses sequential Z-values).
Overlay ///< Temporary UI elements like hovered cards and drag items.
};
/**
* @brief Maximum Z-value a card can have on the table zone.
*
* Based on table zone formula: (actualY + CardDimensions::HEIGHT) * 100000 + (actualX + 1) * 100
* With maximum 3 rows and CardDimensions::HEIGHT = 102, actualY <= ~289.
* Maximum: (289 + 102) * 100000 + 100 * 100 = 39,110,000
*
* We use 40,000,000 as a safe upper bound with margin.
*/
constexpr qreal CARD_Z_VALUE_MAX = 40000000.0;
/**
* @brief Base Z-value for overlay elements.
*
* Must exceed CARD_Z_VALUE_MAX to ensure overlays render above all cards.
* The 50x margin (2B vs 40M) provides safety for future expansion.
*/
constexpr qreal OVERLAY_BASE = 2000000000.0;
/**
* @brief Validates that a Z-value is within the valid card range.
*
* Cards should have Z-values between CARD_BASE (1.0) and CARD_Z_VALUE_MAX.
* Values outside this range may interfere with overlay rendering.
*
* @param zValue The Z-value to validate
* @return true if the Z-value is valid for a card
*/
[[nodiscard]] constexpr bool isValidCardZValue(qreal zValue)
{
return zValue >= 1.0 && zValue <= CARD_Z_VALUE_MAX;
}
/**
* @brief Validates that a Z-value is in the overlay layer.
*
* Overlay elements should have Z-values at or above OVERLAY_BASE.
*
* @param zValue The Z-value to validate
* @return true if the Z-value is valid for an overlay element
*/
[[nodiscard]] constexpr bool isOverlayZValue(qreal zValue)
{
return zValue >= OVERLAY_BASE;
}
/**
* @brief Returns the Z-value for a specific overlay element.
*
* @param offset Offset from OVERLAY_BASE (0-7 for current elements)
* @return The absolute Z-value for the overlay element
*/
[[nodiscard]] constexpr qreal overlayZValue(qreal offset)
{
return OVERLAY_BASE + offset;
}
} // namespace ZValueLayerManager
#endif // Z_VALUE_LAYER_MANAGER_H

View file

@ -1,83 +0,0 @@
#ifndef Z_VALUES_H
#define Z_VALUES_H
#include "card_dimensions.h"
#include "z_value_layer_manager.h"
/**
* @file z_values.h
* @ingroup GameGraphics
* @brief Centralized Z-value constants for rendering layer order.
*
* Z-values in Qt determine stacking order. Higher values render on top.
* These constants define the visual layering hierarchy for the game scene.
*
* ## Layer Architecture
*
* See z_value_layer_manager.h for detailed documentation on the three-layer
* architecture (Zone, Card, Overlay) and the rationale for Z-value choices.
*
* ## Quick Reference
*
* | Layer | Z-Value Range | Purpose |
* |----------|------------------|-----------------------------------|
* | Zone | 0.5 - 1.0 | Zone backgrounds, containers |
* | Card | 1.0 - 40,000,000 | Cards on table (position-based) |
* | Overlay | 2,000,000,000+ | UI elements above all cards |
*/
namespace ZValues
{
// Expose base for callers that need it
constexpr qreal OVERLAY_BASE = ZValueLayerManager::OVERLAY_BASE;
// Overlay layer Z-values for items that should appear above normal cards
constexpr qreal HOVERED_CARD = ZValueLayerManager::overlayZValue(1.0);
constexpr qreal ARROWS = ZValueLayerManager::overlayZValue(3.0);
constexpr qreal ZONE_VIEW_WIDGET = ZValueLayerManager::overlayZValue(4.0);
constexpr qreal DRAG_ITEM = ZValueLayerManager::overlayZValue(5.0);
constexpr qreal DRAG_ITEM_CHILD = ZValueLayerManager::overlayZValue(6.0);
constexpr qreal TOP_UI = ZValueLayerManager::overlayZValue(7.0);
/**
* @brief Compute Z-value for child drag items based on hotspot position.
*
* When dragging multiple cards together, each child card needs a unique Z-value
* to prevent Z-fighting (flickering/flashing). The Z-values are derived from
* their position when grabbed to conserve original stacking. The formula encodes
* 2D coordinates into a single value where X has higher weight, ensuring
* deterministic visual stacking.
*
* @param hotSpotX The X coordinate of the grab position
* @param hotSpotY The Y coordinate of the grab position
* @return Unique Z-value for the child drag item
*/
[[nodiscard]] constexpr qreal childDragZValue(qreal hotSpotX, qreal hotSpotY)
{
return DRAG_ITEM_CHILD + hotSpotX * 1000000 + hotSpotY * 1000 + 1000;
}
/**
* @brief Compute Z-value for cards on the table zone based on position.
*
* Cards lower on the table (higher Y) render above cards higher up,
* and cards to the right (higher X) render above cards to the left.
* This creates natural visual stacking for overlapping cards.
*
* @param x The X coordinate of the card position
* @param y The Y coordinate of the card position
* @return Z-value for the card's table position
*/
[[nodiscard]] constexpr qreal tableCardZValue(qreal x, qreal y)
{
return (y + CardDimensions::HEIGHT_F) * 100000.0 + (x + 1) * 100.0;
}
// Card layering (general architecture, not command-zone specific)
constexpr qreal CARD_BASE = 1.0;
constexpr qreal CARD_MAX = ZValueLayerManager::CARD_Z_VALUE_MAX;
} // namespace ZValues
#endif // Z_VALUES_H

View file

@ -1,7 +1,7 @@
#include "card_zone_logic.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../board/card_item.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "view_zone_logic.h"

View file

@ -1,6 +1,6 @@
#include "hand_zone_logic.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
#include "card_zone_algorithms.h"
HandZoneLogic::HandZoneLogic(PlayerLogic *_player,

View file

@ -1,6 +1,6 @@
#include "pile_zone_logic.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
PileZoneLogic::PileZoneLogic(PlayerLogic *_player,
const QString &_name,

View file

@ -1,6 +1,6 @@
#include "stack_zone_logic.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
#include "card_zone_algorithms.h"
StackZoneLogic::StackZoneLogic(PlayerLogic *_player,

View file

@ -1,6 +1,6 @@
#include "table_zone_logic.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
TableZoneLogic::TableZoneLogic(PlayerLogic *_player,
const QString &_name,

View file

@ -1,7 +1,7 @@
#include "view_zone_logic.h"
#include "../../client/settings/cache_settings.h"
#include "../board/card_item.h"
#include "../../game_graphics/board/card_item.h"
/**
* @param _player the player that the cards are revealed to.