mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-15 19:47:46 -07:00
Major Directory Refactoring (#5118)
* refactored cardzone.cpp, added doc and changed if to switch case * started moving every files into different folders * remove undercase to match with other files naming convention * refactored dialog files * ran format.sh * refactored client/tabs folder * refactored client/tabs folder * refactored client/tabs folder * refactored client folder * refactored carddbparser * refactored dialogs * Create sonar-project.properties temporary file for lint * Create build.yml temporary file for lint * removed all files from root directory * removed all files from root directory * added current branch to workflow * fixed most broken import * fixed issues while renaming files * fixed oracle importer * fixed dbconverter * updated translations * made sub-folders for client * removed linter * removed linter folder * fixed oracle import * revert card_zone documentation * renamed db parser files name and deck_view imports * fixed dlg file issue * ran format file and fixed test file * fixed carddb test files * moved player folder in game * updated translations and format files * fixed peglib import * format cmake files * removing vcpkg to try to add it back later * tried fixing vcpkg file * renamed filter to filters and moved database parser to cards folder * reverted translation files * reverted oracle translated * Update cockatrice/src/dialogs/dlg_register.cpp Co-authored-by: tooomm <tooomm@users.noreply.github.com> * Update cockatrice/src/client/ui/window_main.cpp Co-authored-by: tooomm <tooomm@users.noreply.github.com> * removed empty line at file start * removed useless include from tab_supervisor.cpp * refactored cardzone.cpp, added doc and changed if to switch case * started moving every files into different folders * remove undercase to match with other files naming convention * refactored dialog files * ran format.sh * refactored client/tabs folder * refactored client folder * refactored carddbparser * refactored dialogs * removed all files from root directory * Create sonar-project.properties temporary file for lint * Create build.yml temporary file for lint * added current branch to workflow * fixed most broken import * fixed issues while renaming files * fixed oracle importer * fixed dbconverter * updated translations * made sub-folders for client * removed linter * removed linter folder * fixed oracle import * revert card_zone documentation * renamed db parser files name and deck_view imports * fixed dlg file issue * ran format file and fixed test file * fixed carddb test files * moved player folder in game * updated translations and format files * fixed peglib import * reverted translation files * format cmake files * removing vcpkg to try to add it back later * tried fixing vcpkg file * pre-updating of cockatrice changes * removed empty line at file start * reverted oracle translated * Update cockatrice/src/dialogs/dlg_register.cpp Co-authored-by: tooomm <tooomm@users.noreply.github.com> * Update cockatrice/src/client/ui/window_main.cpp Co-authored-by: tooomm <tooomm@users.noreply.github.com> * removed useless include from tab_supervisor.cpp --------- Co-authored-by: tooomm <tooomm@users.noreply.github.com>
This commit is contained in:
parent
d1e0f9dfc5
commit
fa999880ee
261 changed files with 735 additions and 729 deletions
230
cockatrice/src/game/board/abstract_counter.cpp
Normal file
230
cockatrice/src/game/board/abstract_counter.cpp
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
#include "abstract_counter.h"
|
||||
|
||||
#include "../../client/translate_counter_name.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../player/player.h"
|
||||
#include "expression.h"
|
||||
#include "pb/command_inc_counter.pb.h"
|
||||
#include "pb/command_set_counter.pb.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QGraphicsSceneHoverEvent>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <QString>
|
||||
|
||||
AbstractCounter::AbstractCounter(Player *_player,
|
||||
int _id,
|
||||
const QString &_name,
|
||||
bool _shownInCounterArea,
|
||||
int _value,
|
||||
bool _useNameForShortcut,
|
||||
QGraphicsItem *parent,
|
||||
QWidget *_game)
|
||||
: QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value),
|
||||
useNameForShortcut(_useNameForShortcut), hovered(false), aDec(nullptr), aInc(nullptr), dialogSemaphore(false),
|
||||
deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea), game(_game)
|
||||
{
|
||||
setAcceptHoverEvents(true);
|
||||
|
||||
shortcutActive = false;
|
||||
|
||||
if (player->getLocalOrJudge()) {
|
||||
QString displayName = TranslateCounterName::getDisplayName(_name);
|
||||
menu = new TearOffMenu(displayName);
|
||||
aSet = new QAction(this);
|
||||
connect(aSet, SIGNAL(triggered()), this, SLOT(setCounter()));
|
||||
menu->addAction(aSet);
|
||||
menu->addSeparator();
|
||||
for (int i = 10; i >= -10; --i) {
|
||||
if (i == 0) {
|
||||
menu->addSeparator();
|
||||
} else {
|
||||
QAction *aIncrement = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this);
|
||||
if (i == -1)
|
||||
aDec = aIncrement;
|
||||
else if (i == 1)
|
||||
aInc = aIncrement;
|
||||
aIncrement->setData(i);
|
||||
connect(aIncrement, SIGNAL(triggered()), this, SLOT(incrementCounter()));
|
||||
menu->addAction(aIncrement);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
menu = nullptr;
|
||||
}
|
||||
|
||||
connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
|
||||
refreshShortcuts();
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
AbstractCounter::~AbstractCounter()
|
||||
{
|
||||
delete menu;
|
||||
}
|
||||
|
||||
void AbstractCounter::delCounter()
|
||||
{
|
||||
if (dialogSemaphore)
|
||||
deleteAfterDialog = true;
|
||||
else
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void AbstractCounter::retranslateUi()
|
||||
{
|
||||
if (menu) {
|
||||
aSet->setText(tr("&Set counter..."));
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractCounter::setShortcutsActive()
|
||||
{
|
||||
if (!player->getLocal()) {
|
||||
return;
|
||||
}
|
||||
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
||||
if (name == "life") {
|
||||
shortcutActive = true;
|
||||
aSet->setShortcuts(shortcuts.getShortcut("Player/aSet"));
|
||||
aDec->setShortcuts(shortcuts.getShortcut("Player/aDec"));
|
||||
aInc->setShortcuts(shortcuts.getShortcut("Player/aInc"));
|
||||
} else if (useNameForShortcut) {
|
||||
shortcutActive = true;
|
||||
aSet->setShortcuts(shortcuts.getShortcut("Player/aSetCounter_" + name));
|
||||
aDec->setShortcuts(shortcuts.getShortcut("Player/aDecCounter_" + name));
|
||||
aInc->setShortcuts(shortcuts.getShortcut("Player/aIncCounter_" + name));
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractCounter::setShortcutsInactive()
|
||||
{
|
||||
shortcutActive = false;
|
||||
if (name == "life" || useNameForShortcut) {
|
||||
aSet->setShortcut(QKeySequence());
|
||||
aDec->setShortcut(QKeySequence());
|
||||
aInc->setShortcut(QKeySequence());
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractCounter::refreshShortcuts()
|
||||
{
|
||||
if (shortcutActive) {
|
||||
setShortcutsActive();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractCounter::setValue(int _value)
|
||||
{
|
||||
value = _value;
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (isUnderMouse() && player->getLocalOrJudge()) {
|
||||
if (event->button() == Qt::MiddleButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
|
||||
if (menu)
|
||||
menu->exec(event->screenPos());
|
||||
event->accept();
|
||||
} else if (event->button() == Qt::LeftButton) {
|
||||
Command_IncCounter cmd;
|
||||
cmd.set_counter_id(id);
|
||||
cmd.set_delta(1);
|
||||
player->sendGameCommand(cmd);
|
||||
event->accept();
|
||||
} else if (event->button() == Qt::RightButton) {
|
||||
Command_IncCounter cmd;
|
||||
cmd.set_counter_id(id);
|
||||
cmd.set_delta(-1);
|
||||
player->sendGameCommand(cmd);
|
||||
event->accept();
|
||||
}
|
||||
} else
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
|
||||
{
|
||||
hovered = true;
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
|
||||
{
|
||||
hovered = false;
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractCounter::incrementCounter()
|
||||
{
|
||||
const int delta = static_cast<QAction *>(sender())->data().toInt();
|
||||
Command_IncCounter cmd;
|
||||
cmd.set_counter_id(id);
|
||||
cmd.set_delta(delta);
|
||||
player->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void AbstractCounter::setCounter()
|
||||
{
|
||||
dialogSemaphore = true;
|
||||
AbstractCounterDialog dialog(name, QString::number(value), game);
|
||||
const int ok = dialog.exec();
|
||||
|
||||
if (deleteAfterDialog) {
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
dialogSemaphore = false;
|
||||
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
Expression exp(value);
|
||||
int newValue = static_cast<int>(exp.parse(dialog.textValue()));
|
||||
|
||||
Command_SetCounter cmd;
|
||||
cmd.set_counter_id(id);
|
||||
cmd.set_value(newValue);
|
||||
player->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));
|
||||
}
|
||||
95
cockatrice/src/game/board/abstract_counter.h
Normal file
95
cockatrice/src/game/board/abstract_counter.h
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
#ifndef COUNTER_H
|
||||
#define COUNTER_H
|
||||
|
||||
#include "../../client/tearoff_menu.h"
|
||||
|
||||
#include <QGraphicsItem>
|
||||
#include <QInputDialog>
|
||||
|
||||
class Player;
|
||||
class QAction;
|
||||
class QKeyEvent;
|
||||
class QMenu;
|
||||
class QString;
|
||||
|
||||
class AbstractCounter : public QObject, public QGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsItem)
|
||||
|
||||
protected:
|
||||
Player *player;
|
||||
int id;
|
||||
QString name;
|
||||
int value;
|
||||
bool useNameForShortcut, hovered;
|
||||
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
|
||||
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
|
||||
|
||||
private:
|
||||
QAction *aSet, *aDec, *aInc;
|
||||
TearOffMenu *menu;
|
||||
bool dialogSemaphore, deleteAfterDialog;
|
||||
bool shownInCounterArea;
|
||||
bool shortcutActive;
|
||||
QWidget *game;
|
||||
|
||||
private slots:
|
||||
void refreshShortcuts();
|
||||
void incrementCounter();
|
||||
void setCounter();
|
||||
|
||||
public:
|
||||
AbstractCounter(Player *_player,
|
||||
int _id,
|
||||
const QString &_name,
|
||||
bool _shownInCounterArea,
|
||||
int _value,
|
||||
bool _useNameForShortcut = false,
|
||||
QGraphicsItem *parent = nullptr,
|
||||
QWidget *game = nullptr);
|
||||
~AbstractCounter() override;
|
||||
|
||||
void retranslateUi();
|
||||
void setValue(int _value);
|
||||
void setShortcutsActive();
|
||||
void setShortcutsInactive();
|
||||
void delCounter();
|
||||
|
||||
QMenu *getMenu() const
|
||||
{
|
||||
return menu;
|
||||
}
|
||||
|
||||
int getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
bool getShownInCounterArea() const
|
||||
{
|
||||
return shownInCounterArea;
|
||||
}
|
||||
int getValue() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
class AbstractCounterDialog : public QInputDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AbstractCounterDialog(const QString &name, const QString &value, QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
void changeValue(int diff);
|
||||
};
|
||||
|
||||
#endif
|
||||
65
cockatrice/src/game/board/abstract_graphics_item.cpp
Normal file
65
cockatrice/src/game/board/abstract_graphics_item.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#include "abstract_graphics_item.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
void AbstractGraphicsItem::paintNumberEllipse(int number,
|
||||
int fontSize,
|
||||
const QColor &color,
|
||||
int position,
|
||||
int count,
|
||||
QPainter *painter)
|
||||
{
|
||||
painter->save();
|
||||
|
||||
QString numStr = QString::number(number);
|
||||
QFont font("Serif");
|
||||
font.setPixelSize(fontSize);
|
||||
font.setWeight(QFont::Bold);
|
||||
|
||||
QFontMetrics fm(font);
|
||||
double w = 1.3 *
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
fm.horizontalAdvance(numStr);
|
||||
#else
|
||||
fm.width(numStr);
|
||||
#endif
|
||||
double h = fm.height() * 1.3;
|
||||
if (w < h)
|
||||
w = h;
|
||||
|
||||
painter->setPen(QColor(255, 255, 255, 0));
|
||||
painter->setBrush(QBrush(QColor(color)));
|
||||
|
||||
QRectF textRect;
|
||||
if (position == -1)
|
||||
textRect = QRectF((boundingRect().width() - w) / 2.0, (boundingRect().height() - h) / 2.0, w, h);
|
||||
else {
|
||||
qreal xOffset = 10;
|
||||
qreal yOffset = 20;
|
||||
qreal spacing = 2;
|
||||
if (position < 2)
|
||||
textRect = QRectF(count == 1 ? ((boundingRect().width() - w) / 2.0)
|
||||
: (position % 2 == 0 ? xOffset : (boundingRect().width() - xOffset - w)),
|
||||
yOffset, w, h);
|
||||
else
|
||||
textRect = QRectF(count == 3 ? ((boundingRect().width() - w) / 2.0)
|
||||
: (position % 2 == 0 ? xOffset : (boundingRect().width() - xOffset - w)),
|
||||
yOffset + (spacing + h) * (position / 2), w, h);
|
||||
}
|
||||
|
||||
painter->drawEllipse(textRect);
|
||||
|
||||
painter->setPen(Qt::black);
|
||||
painter->setFont(font);
|
||||
painter->drawText(textRect, Qt::AlignCenter, numStr);
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
int resetPainterTransform(QPainter *painter)
|
||||
{
|
||||
painter->resetTransform();
|
||||
auto tx = painter->deviceTransform().inverted();
|
||||
painter->setTransform(tx);
|
||||
return tx.isScaling() ? 1.0 / tx.m11() : 1;
|
||||
}
|
||||
31
cockatrice/src/game/board/abstract_graphics_item.h
Normal file
31
cockatrice/src/game/board/abstract_graphics_item.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef ABSTRACTGRAPHICSITEM_H
|
||||
#define ABSTRACTGRAPHICSITEM_H
|
||||
|
||||
#include <QGraphicsItem>
|
||||
|
||||
enum GraphicsItemType
|
||||
{
|
||||
typeCard = QGraphicsItem::UserType + 1,
|
||||
typeCardDrag = QGraphicsItem::UserType + 2,
|
||||
typeZone = QGraphicsItem::UserType + 3,
|
||||
typePlayerTarget = QGraphicsItem::UserType + 4,
|
||||
typeDeckViewCardContainer = QGraphicsItem::UserType + 5,
|
||||
typeOther = QGraphicsItem::UserType + 6
|
||||
};
|
||||
|
||||
class AbstractGraphicsItem : public QObject, public QGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsItem)
|
||||
protected:
|
||||
void paintNumberEllipse(int number, int radius, const QColor &color, int position, int count, QPainter *painter);
|
||||
|
||||
public:
|
||||
AbstractGraphicsItem(QGraphicsItem *parent = nullptr) : QObject(), QGraphicsItem(parent)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
int resetPainterTransform(QPainter *painter);
|
||||
|
||||
#endif
|
||||
336
cockatrice/src/game/board/arrow_item.cpp
Normal file
336
cockatrice/src/game/board/arrow_item.cpp
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
#define _USE_MATH_DEFINES
|
||||
#include "arrow_item.h"
|
||||
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../cards/card_database.h"
|
||||
#include "../cards/card_item.h"
|
||||
#include "../player/player.h"
|
||||
#include "../player/player_target.h"
|
||||
#include "../zones/card_zone.h"
|
||||
#include "color.h"
|
||||
#include "pb/command_attach_card.pb.h"
|
||||
#include "pb/command_create_arrow.pb.h"
|
||||
#include "pb/command_delete_arrow.pb.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
|
||||
ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color)
|
||||
: QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color),
|
||||
fullColor(true)
|
||||
{
|
||||
qDebug() << "ArrowItem constructor: startItem=" << static_cast<QGraphicsItem *>(startItem);
|
||||
setZValue(2000000005);
|
||||
|
||||
if (startItem)
|
||||
startItem->addArrowFrom(this);
|
||||
if (targetItem)
|
||||
targetItem->addArrowTo(this);
|
||||
|
||||
if (startItem && targetItem)
|
||||
updatePath();
|
||||
}
|
||||
|
||||
ArrowItem::~ArrowItem()
|
||||
{
|
||||
qDebug() << "ArrowItem destructor";
|
||||
}
|
||||
|
||||
void ArrowItem::delArrow()
|
||||
{
|
||||
if (startItem) {
|
||||
startItem->removeArrowFrom(this);
|
||||
startItem = 0;
|
||||
}
|
||||
|
||||
if (targetItem) {
|
||||
targetItem->setBeingPointedAt(false);
|
||||
targetItem->removeArrowTo(this);
|
||||
targetItem = 0;
|
||||
}
|
||||
|
||||
player->removeArrow(this);
|
||||
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(color);
|
||||
if (fullColor)
|
||||
paintColor.setAlpha(200);
|
||||
else
|
||||
paintColor.setAlpha(150);
|
||||
painter->setBrush(paintColor);
|
||||
painter->drawPath(path);
|
||||
}
|
||||
|
||||
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!player->getLocal()) {
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
QList<QGraphicsItem *> colliding = scene()->items(event->scenePos());
|
||||
for (QGraphicsItem *item : colliding) {
|
||||
if (qgraphicsitem_cast<CardItem *>(item)) {
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event->accept();
|
||||
if (event->button() == Qt::RightButton) {
|
||||
Command_DeleteArrow cmd;
|
||||
cmd.set_arrow_id(id);
|
||||
player->sendGameCommand(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color)
|
||||
: ArrowItem(_owner, -1, _startItem, 0, _color)
|
||||
{
|
||||
}
|
||||
|
||||
void ArrowDragItem::addChildArrow(ArrowDragItem *childArrow)
|
||||
{
|
||||
childArrows.append(childArrow);
|
||||
}
|
||||
|
||||
void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
// This ensures that if a mouse move event happens after a call to delArrow(),
|
||||
// the event will be discarded as it would create some stray pointers.
|
||||
if (!startItem)
|
||||
return;
|
||||
|
||||
QPointF endPos = event->scenePos();
|
||||
|
||||
QList<QGraphicsItem *> colliding = scene()->items(endPos);
|
||||
ArrowTarget *cursorItem = 0;
|
||||
qreal cursorItemZ = -1;
|
||||
for (int i = colliding.size() - 1; i >= 0; i--) {
|
||||
if (qgraphicsitem_cast<PlayerTarget *>(colliding.at(i)) || qgraphicsitem_cast<CardItem *>(colliding.at(i))) {
|
||||
if (colliding.at(i)->zValue() > cursorItemZ) {
|
||||
cursorItem = static_cast<ArrowTarget *>(colliding.at(i));
|
||||
cursorItemZ = cursorItem->zValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((cursorItem != targetItem) && targetItem) {
|
||||
targetItem->setBeingPointedAt(false);
|
||||
targetItem->removeArrowTo(this);
|
||||
}
|
||||
if (!cursorItem) {
|
||||
fullColor = false;
|
||||
targetItem = 0;
|
||||
updatePath(endPos);
|
||||
} else {
|
||||
if (cursorItem != targetItem) {
|
||||
fullColor = true;
|
||||
if (cursorItem != startItem) {
|
||||
cursorItem->setBeingPointedAt(true);
|
||||
cursorItem->addArrowTo(this);
|
||||
}
|
||||
targetItem = cursorItem;
|
||||
}
|
||||
updatePath();
|
||||
}
|
||||
update();
|
||||
|
||||
for (ArrowDragItem *child : childArrows) {
|
||||
child->mouseMoveEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!startItem)
|
||||
return;
|
||||
|
||||
if (targetItem && (targetItem != startItem)) {
|
||||
CardZone *startZone = static_cast<CardItem *>(startItem)->getZone();
|
||||
// For now, we can safely assume that the start item is always a card.
|
||||
// The target item can be a player as well.
|
||||
CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
|
||||
CardItem *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
|
||||
|
||||
Command_CreateArrow cmd;
|
||||
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color));
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_start_card_id(startCard->getId());
|
||||
|
||||
if (targetCard) {
|
||||
CardZone *targetZone = targetCard->getZone();
|
||||
cmd.set_target_player_id(targetZone->getPlayer()->getId());
|
||||
cmd.set_target_zone(targetZone->getName().toStdString());
|
||||
cmd.set_target_card_id(targetCard->getId());
|
||||
} else {
|
||||
PlayerTarget *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem);
|
||||
cmd.set_target_player_id(targetPlayer->getOwner()->getId());
|
||||
}
|
||||
if (startZone->getName().compare("hand") == 0) {
|
||||
startCard->playCard(false);
|
||||
CardInfoPtr ci = startCard->getInfo();
|
||||
if (ci && (((!SettingsCache::instance().getPlayToStack() && ci->getTableRow() == 3) ||
|
||||
((SettingsCache::instance().getPlayToStack() && ci->getTableRow() != 0) &&
|
||||
startCard->getZone()->getName().toStdString() != "stack"))))
|
||||
cmd.set_start_zone("stack");
|
||||
else
|
||||
cmd.set_start_zone(SettingsCache::instance().getPlayToStack() ? "stack" : "table");
|
||||
}
|
||||
player->sendGameCommand(cmd);
|
||||
}
|
||||
delArrow();
|
||||
|
||||
for (ArrowDragItem *child : childArrows) {
|
||||
child->mouseReleaseEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
|
||||
: ArrowItem(_startItem->getOwner(), -1, _startItem, 0, Qt::green)
|
||||
{
|
||||
}
|
||||
|
||||
void ArrowAttachItem::addChildArrow(ArrowAttachItem *childArrow)
|
||||
{
|
||||
childArrows.append(childArrow);
|
||||
}
|
||||
|
||||
void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!startItem)
|
||||
return;
|
||||
|
||||
QPointF endPos = event->scenePos();
|
||||
|
||||
QList<QGraphicsItem *> colliding = scene()->items(endPos);
|
||||
ArrowTarget *cursorItem = 0;
|
||||
qreal cursorItemZ = -1;
|
||||
for (int i = colliding.size() - 1; i >= 0; i--) {
|
||||
if (qgraphicsitem_cast<CardItem *>(colliding.at(i))) {
|
||||
if (colliding.at(i)->zValue() > cursorItemZ) {
|
||||
cursorItem = static_cast<ArrowTarget *>(colliding.at(i));
|
||||
cursorItemZ = cursorItem->zValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((cursorItem != targetItem) && targetItem) {
|
||||
targetItem->setBeingPointedAt(false);
|
||||
}
|
||||
if (!cursorItem) {
|
||||
fullColor = false;
|
||||
targetItem = 0;
|
||||
updatePath(endPos);
|
||||
} else {
|
||||
fullColor = true;
|
||||
if (cursorItem != startItem) {
|
||||
cursorItem->setBeingPointedAt(true);
|
||||
}
|
||||
targetItem = cursorItem;
|
||||
updatePath();
|
||||
}
|
||||
update();
|
||||
|
||||
for (ArrowAttachItem *child : childArrows) {
|
||||
child->mouseMoveEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!startItem)
|
||||
return;
|
||||
|
||||
if (targetItem && (targetItem != startItem)) {
|
||||
CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
|
||||
CardZone *startZone = startCard->getZone();
|
||||
CardItem *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
|
||||
CardZone *targetZone = targetCard->getZone();
|
||||
|
||||
Command_AttachCard cmd;
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_card_id(startCard->getId());
|
||||
cmd.set_target_player_id(targetZone->getPlayer()->getId());
|
||||
cmd.set_target_zone(targetZone->getName().toStdString());
|
||||
cmd.set_target_card_id(targetCard->getId());
|
||||
|
||||
player->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
delArrow();
|
||||
|
||||
for (ArrowAttachItem *child : childArrows) {
|
||||
child->mouseReleaseEvent(event);
|
||||
}
|
||||
}
|
||||
100
cockatrice/src/game/board/arrow_item.h
Normal file
100
cockatrice/src/game/board/arrow_item.h
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#ifndef ARROWITEM_H
|
||||
#define ARROWITEM_H
|
||||
|
||||
#include <QGraphicsItem>
|
||||
|
||||
class CardItem;
|
||||
class QGraphicsSceneMouseEvent;
|
||||
class QMenu;
|
||||
class Player;
|
||||
class ArrowTarget;
|
||||
|
||||
class ArrowItem : public QObject, public QGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsItem)
|
||||
private:
|
||||
QPainterPath path;
|
||||
QMenu *menu;
|
||||
|
||||
protected:
|
||||
Player *player;
|
||||
int id;
|
||||
ArrowTarget *startItem, *targetItem;
|
||||
QColor color;
|
||||
bool fullColor;
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event);
|
||||
|
||||
public:
|
||||
ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &color);
|
||||
~ArrowItem();
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
QRectF boundingRect() const
|
||||
{
|
||||
return path.boundingRect();
|
||||
}
|
||||
QPainterPath shape() const
|
||||
{
|
||||
return path;
|
||||
}
|
||||
void updatePath();
|
||||
void updatePath(const QPointF &endPoint);
|
||||
|
||||
int getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
Player *getPlayer() const
|
||||
{
|
||||
return player;
|
||||
}
|
||||
void setStartItem(ArrowTarget *_item)
|
||||
{
|
||||
startItem = _item;
|
||||
}
|
||||
void setTargetItem(ArrowTarget *_item)
|
||||
{
|
||||
targetItem = _item;
|
||||
}
|
||||
ArrowTarget *getStartItem() const
|
||||
{
|
||||
return startItem;
|
||||
}
|
||||
ArrowTarget *getTargetItem() const
|
||||
{
|
||||
return targetItem;
|
||||
}
|
||||
void delArrow();
|
||||
};
|
||||
|
||||
class ArrowDragItem : public ArrowItem
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QList<ArrowDragItem *> childArrows;
|
||||
|
||||
public:
|
||||
ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color);
|
||||
void addChildArrow(ArrowDragItem *childArrow);
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
|
||||
};
|
||||
|
||||
class ArrowAttachItem : public ArrowItem
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QList<ArrowAttachItem *> childArrows;
|
||||
|
||||
public:
|
||||
ArrowAttachItem(ArrowTarget *_startItem);
|
||||
void addChildArrow(ArrowAttachItem *childArrow);
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
|
||||
};
|
||||
|
||||
#endif // ARROWITEM_H
|
||||
27
cockatrice/src/game/board/arrow_target.cpp
Normal file
27
cockatrice/src/game/board/arrow_target.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#include "arrow_target.h"
|
||||
|
||||
#include "../player/player.h"
|
||||
#include "arrow_item.h"
|
||||
|
||||
ArrowTarget::ArrowTarget(Player *_owner, QGraphicsItem *parent)
|
||||
: AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false)
|
||||
{
|
||||
}
|
||||
|
||||
ArrowTarget::~ArrowTarget()
|
||||
{
|
||||
for (int i = 0; i < arrowsFrom.size(); ++i) {
|
||||
arrowsFrom[i]->setStartItem(0);
|
||||
arrowsFrom[i]->delArrow();
|
||||
}
|
||||
for (int i = 0; i < arrowsTo.size(); ++i) {
|
||||
arrowsTo[i]->setTargetItem(0);
|
||||
arrowsTo[i]->delArrow();
|
||||
}
|
||||
}
|
||||
|
||||
void ArrowTarget::setBeingPointedAt(bool _beingPointedAt)
|
||||
{
|
||||
beingPointedAt = _beingPointedAt;
|
||||
update();
|
||||
}
|
||||
61
cockatrice/src/game/board/arrow_target.h
Normal file
61
cockatrice/src/game/board/arrow_target.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#ifndef ARROWTARGET_H
|
||||
#define ARROWTARGET_H
|
||||
|
||||
#include "abstract_graphics_item.h"
|
||||
|
||||
#include <QList>
|
||||
|
||||
class Player;
|
||||
class ArrowItem;
|
||||
|
||||
class ArrowTarget : public AbstractGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
Player *owner;
|
||||
|
||||
private:
|
||||
bool beingPointedAt;
|
||||
QList<ArrowItem *> arrowsFrom, arrowsTo;
|
||||
|
||||
public:
|
||||
ArrowTarget(Player *_owner, QGraphicsItem *parent = nullptr);
|
||||
~ArrowTarget();
|
||||
|
||||
Player *getOwner() const
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
|
||||
void setBeingPointedAt(bool _beingPointedAt);
|
||||
bool getBeingPointedAt() const
|
||||
{
|
||||
return beingPointedAt;
|
||||
}
|
||||
|
||||
const QList<ArrowItem *> &getArrowsFrom() const
|
||||
{
|
||||
return arrowsFrom;
|
||||
}
|
||||
void addArrowFrom(ArrowItem *arrow)
|
||||
{
|
||||
arrowsFrom.append(arrow);
|
||||
}
|
||||
void removeArrowFrom(ArrowItem *arrow)
|
||||
{
|
||||
arrowsFrom.removeOne(arrow);
|
||||
}
|
||||
const QList<ArrowItem *> &getArrowsTo() const
|
||||
{
|
||||
return arrowsTo;
|
||||
}
|
||||
void addArrowTo(ArrowItem *arrow)
|
||||
{
|
||||
arrowsTo.append(arrow);
|
||||
}
|
||||
void removeArrowTo(ArrowItem *arrow)
|
||||
{
|
||||
arrowsTo.removeOne(arrow);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
48
cockatrice/src/game/board/counter_general.cpp
Normal file
48
cockatrice/src/game/board/counter_general.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#include "counter_general.h"
|
||||
|
||||
#include "../../client/ui/pixel_map_generator.h"
|
||||
#include "abstract_graphics_item.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
GeneralCounter::GeneralCounter(Player *_player,
|
||||
int _id,
|
||||
const QString &_name,
|
||||
const QColor &_color,
|
||||
int _radius,
|
||||
int _value,
|
||||
bool useNameForShortcut,
|
||||
QGraphicsItem *parent,
|
||||
QWidget *game)
|
||||
: AbstractCounter(_player, _id, _name, true, _value, useNameForShortcut, parent, game), color(_color),
|
||||
radius(_radius)
|
||||
{
|
||||
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();
|
||||
}
|
||||
27
cockatrice/src/game/board/counter_general.h
Normal file
27
cockatrice/src/game/board/counter_general.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef COUNTER_GENERAL_H
|
||||
#define COUNTER_GENERAL_H
|
||||
|
||||
#include "abstract_counter.h"
|
||||
|
||||
class GeneralCounter : public AbstractCounter
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QColor color;
|
||||
int radius;
|
||||
|
||||
public:
|
||||
GeneralCounter(Player *_player,
|
||||
int _id,
|
||||
const QString &_name,
|
||||
const QColor &_color,
|
||||
int _radius,
|
||||
int _value,
|
||||
bool useNameForShortcut = false,
|
||||
QGraphicsItem *parent = nullptr,
|
||||
QWidget *game = nullptr);
|
||||
QRectF boundingRect() const;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
};
|
||||
|
||||
#endif
|
||||
67
cockatrice/src/game/cards/abstract_card_drag_item.cpp
Normal file
67
cockatrice/src/game/cards/abstract_card_drag_item.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#include "abstract_card_drag_item.h"
|
||||
|
||||
#include "card_database.h"
|
||||
|
||||
#include <QCursor>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
static const float CARD_WIDTH_HALF = CARD_WIDTH / 2;
|
||||
static const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2;
|
||||
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(2000000007 + hotSpot.x() * 1000000 + hotSpot.y() * 1000 + 1000);
|
||||
} else {
|
||||
hotSpot = QPointF{qBound(0.0, hotSpot.x(), static_cast<qreal>(CARD_WIDTH - 1)),
|
||||
qBound(0.0, hotSpot.y(), static_cast<qreal>(CARD_HEIGHT - 1))};
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
setZValue(2000000007);
|
||||
}
|
||||
if (item->getTapped())
|
||||
setTransform(QTransform()
|
||||
.translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF)
|
||||
.rotate(90)
|
||||
.translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF));
|
||||
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
}
|
||||
|
||||
AbstractCardDragItem::~AbstractCardDragItem()
|
||||
{
|
||||
for (int i = 0; i < childDrags.size(); i++)
|
||||
delete childDrags[i];
|
||||
}
|
||||
|
||||
QPainterPath AbstractCardDragItem::shape() const
|
||||
{
|
||||
QPainterPath shape;
|
||||
shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH);
|
||||
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;
|
||||
}
|
||||
51
cockatrice/src/game/cards/abstract_card_drag_item.h
Normal file
51
cockatrice/src/game/cards/abstract_card_drag_item.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#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
|
||||
};
|
||||
int type() const override
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0);
|
||||
~AbstractCardDragItem();
|
||||
QRectF boundingRect() const override
|
||||
{
|
||||
return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
|
||||
}
|
||||
QPainterPath shape() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
AbstractCardItem *getItem() const
|
||||
{
|
||||
return item;
|
||||
}
|
||||
QPointF getHotSpot() const
|
||||
{
|
||||
return hotSpot;
|
||||
}
|
||||
void addChildDrag(AbstractCardDragItem *child);
|
||||
virtual void updatePosition(const QPointF &cursorScenePos) = 0;
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
305
cockatrice/src/game/cards/abstract_card_item.cpp
Normal file
305
cockatrice/src/game/cards/abstract_card_item.cpp
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
#include "abstract_card_item.h"
|
||||
|
||||
#include "../../client/ui/picture_loader.h"
|
||||
#include "../../main.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../game_scene.h"
|
||||
#include "card_database.h"
|
||||
|
||||
#include <QCursor>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <algorithm>
|
||||
|
||||
AbstractCardItem::AbstractCardItem(const QString &_name, Player *_owner, int _id, QGraphicsItem *parent)
|
||||
: ArrowTarget(_owner, parent), id(_id), name(_name), tapped(false), facedown(false), tapAngle(0),
|
||||
bgColor(Qt::transparent), isHovered(false), realZValue(0)
|
||||
{
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
setFlag(ItemIsSelectable);
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
|
||||
connect(&SettingsCache::instance(), SIGNAL(displayCardNamesChanged()), this, SLOT(callUpdate()));
|
||||
cardInfoUpdated();
|
||||
}
|
||||
|
||||
AbstractCardItem::~AbstractCardItem()
|
||||
{
|
||||
emit deleteCardInfoPopup(name);
|
||||
}
|
||||
|
||||
QRectF AbstractCardItem::boundingRect() const
|
||||
{
|
||||
return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
|
||||
}
|
||||
|
||||
QPainterPath AbstractCardItem::shape() const
|
||||
{
|
||||
QPainterPath shape;
|
||||
shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH);
|
||||
return shape;
|
||||
}
|
||||
|
||||
void AbstractCardItem::pixmapUpdated()
|
||||
{
|
||||
update();
|
||||
emit sigPixmapUpdated();
|
||||
}
|
||||
|
||||
void AbstractCardItem::cardInfoUpdated()
|
||||
{
|
||||
info = db->getCard(name);
|
||||
|
||||
if (!info && !name.isEmpty()) {
|
||||
QVariantHash properties = QVariantHash();
|
||||
|
||||
info = CardInfo::newInstance(name, "", true, QVariantHash(), QList<CardRelation *>(), QList<CardRelation *>(),
|
||||
CardInfoPerSetMap(), false, -1, false);
|
||||
}
|
||||
if (info.data()) {
|
||||
connect(info.data(), SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated()));
|
||||
}
|
||||
|
||||
cacheBgColor();
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractCardItem::setRealZValue(qreal _zValue)
|
||||
{
|
||||
realZValue = _zValue;
|
||||
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 || name.isEmpty()) {
|
||||
// never reveal card color, always paint the card back
|
||||
PictureLoader::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) {
|
||||
PictureLoader::getPixmap(translatedPixmap, info, 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
|
||||
nameStr = 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::setName(const QString &_name)
|
||||
{
|
||||
if (name == _name)
|
||||
return;
|
||||
|
||||
emit deleteCardInfoPopup(name);
|
||||
if (info)
|
||||
disconnect(info.data(), nullptr, this, nullptr);
|
||||
name = _name;
|
||||
|
||||
cardInfoUpdated();
|
||||
}
|
||||
|
||||
void AbstractCardItem::setHovered(bool _hovered)
|
||||
{
|
||||
if (isHovered == _hovered)
|
||||
return;
|
||||
|
||||
if (_hovered)
|
||||
processHoverEvent();
|
||||
isHovered = _hovered;
|
||||
setZValue(_hovered ? 2000000004 : realZValue);
|
||||
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
|
||||
setTransformOriginPoint(_hovered ? CARD_WIDTH / 2 : 0, _hovered ? CARD_HEIGHT / 2 : 0);
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractCardItem::setColor(const QString &_color)
|
||||
{
|
||||
color = _color;
|
||||
cacheBgColor();
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractCardItem::cacheBgColor()
|
||||
{
|
||||
QChar colorChar;
|
||||
if (color.isEmpty()) {
|
||||
if (info)
|
||||
colorChar = info->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((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2)
|
||||
.rotate(tapAngle)
|
||||
.translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2));
|
||||
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(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(), name);
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::MiddleButton)
|
||||
emit deleteCardInfoPopup(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 QGraphicsItem::itemChange(change, value);
|
||||
}
|
||||
113
cockatrice/src/game/cards/abstract_card_item.h
Normal file
113
cockatrice/src/game/cards/abstract_card_item.h
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#ifndef ABSTRACTCARDITEM_H
|
||||
#define ABSTRACTCARDITEM_H
|
||||
|
||||
#include "../board/arrow_target.h"
|
||||
#include "card_database.h"
|
||||
|
||||
class Player;
|
||||
|
||||
const int CARD_WIDTH = 72;
|
||||
const int CARD_HEIGHT = 102;
|
||||
|
||||
class AbstractCardItem : public ArrowTarget
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
CardInfoPtr info;
|
||||
int id;
|
||||
QString name;
|
||||
bool tapped;
|
||||
bool facedown;
|
||||
int tapAngle;
|
||||
QString color;
|
||||
QColor bgColor;
|
||||
|
||||
private:
|
||||
bool isHovered;
|
||||
qreal realZValue;
|
||||
private slots:
|
||||
void pixmapUpdated();
|
||||
void cardInfoUpdated();
|
||||
void callUpdate()
|
||||
{
|
||||
update();
|
||||
}
|
||||
signals:
|
||||
void hovered(AbstractCardItem *card);
|
||||
void showCardInfoPopup(QPoint pos, QString cardName);
|
||||
void deleteCardInfoPopup(QString cardName);
|
||||
void sigPixmapUpdated();
|
||||
void cardShiftClicked(QString cardName);
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
Type = typeCard
|
||||
};
|
||||
int type() const override
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
AbstractCardItem(const QString &_name = QString(),
|
||||
Player *_owner = nullptr,
|
||||
int _id = -1,
|
||||
QGraphicsItem *parent = nullptr);
|
||||
~AbstractCardItem();
|
||||
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;
|
||||
CardInfoPtr getInfo() const
|
||||
{
|
||||
return info;
|
||||
}
|
||||
int getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
void setId(int _id)
|
||||
{
|
||||
id = _id;
|
||||
}
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
void setName(const QString &_name = QString());
|
||||
qreal getRealZValue() const
|
||||
{
|
||||
return realZValue;
|
||||
}
|
||||
void setRealZValue(qreal _zValue);
|
||||
void setHovered(bool _hovered);
|
||||
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(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
|
||||
738
cockatrice/src/game/cards/card_database.cpp
Normal file
738
cockatrice/src/game/cards/card_database.cpp
Normal file
|
|
@ -0,0 +1,738 @@
|
|||
#include "card_database.h"
|
||||
|
||||
#include "../../client/network/spoiler_background_updater.h"
|
||||
#include "../../client/ui/picture_loader.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../game_specific_terms.h"
|
||||
#include "./card_database_parser/cockatrice_xml_3.h"
|
||||
#include "./card_database_parser/cockatrice_xml_4.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
const char *CardDatabase::TOKENS_SETNAME = "TK";
|
||||
|
||||
CardSet::CardSet(const QString &_shortName,
|
||||
const QString &_longName,
|
||||
const QString &_setType,
|
||||
const QDate &_releaseDate)
|
||||
: shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType)
|
||||
{
|
||||
loadSetOptions();
|
||||
}
|
||||
|
||||
CardSetPtr CardSet::newInstance(const QString &_shortName,
|
||||
const QString &_longName,
|
||||
const QString &_setType,
|
||||
const QDate &_releaseDate)
|
||||
{
|
||||
CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate));
|
||||
// ptr->setSmartPointer(ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
QString CardSet::getCorrectedShortName() const
|
||||
{
|
||||
// For Windows machines.
|
||||
QSet<QString> invalidFileNames;
|
||||
invalidFileNames << "CON"
|
||||
<< "PRN"
|
||||
<< "AUX"
|
||||
<< "NUL"
|
||||
<< "COM1"
|
||||
<< "COM2"
|
||||
<< "COM3"
|
||||
<< "COM4"
|
||||
<< "COM5"
|
||||
<< "COM6"
|
||||
<< "COM7"
|
||||
<< "COM8"
|
||||
<< "COM9"
|
||||
<< "LPT1"
|
||||
<< "LPT2"
|
||||
<< "LPT3"
|
||||
<< "LPT4"
|
||||
<< "LPT5"
|
||||
<< "LPT6"
|
||||
<< "LPT7"
|
||||
<< "LPT8"
|
||||
<< "LPT9";
|
||||
|
||||
return invalidFileNames.contains(shortName) ? shortName + "_" : shortName;
|
||||
}
|
||||
|
||||
void CardSet::loadSetOptions()
|
||||
{
|
||||
sortKey = SettingsCache::instance().cardDatabase().getSortKey(shortName);
|
||||
enabled = SettingsCache::instance().cardDatabase().isEnabled(shortName);
|
||||
isknown = SettingsCache::instance().cardDatabase().isKnown(shortName);
|
||||
}
|
||||
|
||||
void CardSet::setSortKey(unsigned int _sortKey)
|
||||
{
|
||||
sortKey = _sortKey;
|
||||
SettingsCache::instance().cardDatabase().setSortKey(shortName, _sortKey);
|
||||
}
|
||||
|
||||
void CardSet::setEnabled(bool _enabled)
|
||||
{
|
||||
enabled = _enabled;
|
||||
SettingsCache::instance().cardDatabase().setEnabled(shortName, _enabled);
|
||||
}
|
||||
|
||||
void CardSet::setIsKnown(bool _isknown)
|
||||
{
|
||||
isknown = _isknown;
|
||||
SettingsCache::instance().cardDatabase().setIsKnown(shortName, _isknown);
|
||||
}
|
||||
|
||||
class SetList::KeyCompareFunctor
|
||||
{
|
||||
public:
|
||||
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
|
||||
{
|
||||
if (a.isNull() || b.isNull()) {
|
||||
qDebug() << "SetList::KeyCompareFunctor a or b is null";
|
||||
return false;
|
||||
}
|
||||
|
||||
return a->getSortKey() < b->getSortKey();
|
||||
}
|
||||
};
|
||||
|
||||
void SetList::sortByKey()
|
||||
{
|
||||
std::sort(begin(), end(), KeyCompareFunctor());
|
||||
}
|
||||
|
||||
int SetList::getEnabledSetsNum()
|
||||
{
|
||||
int num = 0;
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
if (set && set->getEnabled()) {
|
||||
++num;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
int SetList::getUnknownSetsNum()
|
||||
{
|
||||
int num = 0;
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
||||
++num;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
QStringList SetList::getUnknownSetsNames()
|
||||
{
|
||||
QStringList sets = QStringList();
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
||||
sets << set->getShortName();
|
||||
}
|
||||
}
|
||||
return sets;
|
||||
}
|
||||
|
||||
void SetList::enableAllUnknown()
|
||||
{
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
||||
set->setIsKnown(true);
|
||||
set->setEnabled(true);
|
||||
} else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
|
||||
set->setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetList::enableAll()
|
||||
{
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
|
||||
if (set == nullptr) {
|
||||
qDebug() << "enabledAll has null";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!set->getIsKnownIgnored()) {
|
||||
set->setIsKnown(true);
|
||||
}
|
||||
|
||||
set->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void SetList::markAllAsKnown()
|
||||
{
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
||||
set->setIsKnown(true);
|
||||
set->setEnabled(false);
|
||||
} else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
|
||||
set->setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetList::guessSortKeys()
|
||||
{
|
||||
// sort by release date DESC; invalid dates to the bottom.
|
||||
QDate distantFuture(2050, 1, 1);
|
||||
int aHundredYears = 36500;
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
CardSetPtr set = at(i);
|
||||
if (set.isNull()) {
|
||||
qDebug() << "guessSortKeys set is null";
|
||||
continue;
|
||||
}
|
||||
|
||||
QDate date = set->getReleaseDate();
|
||||
if (date.isNull()) {
|
||||
set->setSortKey(static_cast<unsigned int>(aHundredYears));
|
||||
} else {
|
||||
set->setSortKey(static_cast<unsigned int>(date.daysTo(distantFuture)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set)
|
||||
{
|
||||
}
|
||||
|
||||
CardInfo::CardInfo(const QString &_name,
|
||||
const QString &_text,
|
||||
bool _isToken,
|
||||
QVariantHash _properties,
|
||||
const QList<CardRelation *> &_relatedCards,
|
||||
const QList<CardRelation *> &_reverseRelatedCards,
|
||||
CardInfoPerSetMap _sets,
|
||||
bool _cipt,
|
||||
int _tableRow,
|
||||
bool _upsideDownArt)
|
||||
: name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
|
||||
reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), tableRow(_tableRow),
|
||||
upsideDownArt(_upsideDownArt)
|
||||
{
|
||||
pixmapCacheKey = QLatin1String("card_") + name;
|
||||
simpleName = CardInfo::simplifyName(name);
|
||||
|
||||
refreshCachedSetNames();
|
||||
}
|
||||
|
||||
CardInfo::~CardInfo()
|
||||
{
|
||||
PictureLoader::clearPixmapCache(smartThis);
|
||||
}
|
||||
|
||||
CardInfoPtr CardInfo::newInstance(const QString &_name,
|
||||
const QString &_text,
|
||||
bool _isToken,
|
||||
QVariantHash _properties,
|
||||
const QList<CardRelation *> &_relatedCards,
|
||||
const QList<CardRelation *> &_reverseRelatedCards,
|
||||
CardInfoPerSetMap _sets,
|
||||
bool _cipt,
|
||||
int _tableRow,
|
||||
bool _upsideDownArt)
|
||||
{
|
||||
CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards,
|
||||
_sets, _cipt, _tableRow, _upsideDownArt));
|
||||
ptr->setSmartPointer(ptr);
|
||||
|
||||
for (const CardInfoPerSet &set : _sets) {
|
||||
set.getPtr()->append(ptr);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
QString CardInfo::getCorrectedName() const
|
||||
{
|
||||
// remove all the characters reserved in windows file paths,
|
||||
// other oses only disallow a subset of these so it covers all
|
||||
static const QRegularExpression rmrx(R"(( // |[*<>:"\\?\x00-\x08\x10-\x1f]))");
|
||||
static const QRegularExpression spacerx(R"([/\x09-\x0f])");
|
||||
static const QString space(' ');
|
||||
QString result = name;
|
||||
// Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental?
|
||||
return result.remove(rmrx).replace(spacerx, space);
|
||||
}
|
||||
|
||||
void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info)
|
||||
{
|
||||
_set->append(smartThis);
|
||||
sets.insert(_set->getShortName(), _info);
|
||||
|
||||
refreshCachedSetNames();
|
||||
}
|
||||
|
||||
void CardInfo::refreshCachedSetNames()
|
||||
{
|
||||
QStringList setList;
|
||||
// update the cached list of set names
|
||||
for (const auto &set : sets) {
|
||||
if (set.getPtr()->getEnabled()) {
|
||||
setList << set.getPtr()->getShortName();
|
||||
}
|
||||
}
|
||||
setsNames = setList.join(", ");
|
||||
}
|
||||
|
||||
QString CardInfo::simplifyName(const QString &name)
|
||||
{
|
||||
static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)");
|
||||
static const QRegularExpression nonAlnum("[^a-z0-9]");
|
||||
|
||||
QString simpleName = name.toLower();
|
||||
|
||||
// remove spaces and right halves of split cards
|
||||
simpleName.remove(spaceOrSplit);
|
||||
|
||||
// So Aetherling would work, but not Ætherling since 'Æ' would get replaced
|
||||
// with nothing.
|
||||
simpleName.replace("æ", "ae");
|
||||
|
||||
// Replace Jötun Grunt with Jotun Grunt.
|
||||
simpleName = simpleName.normalized(QString::NormalizationForm_KD);
|
||||
|
||||
// remove all non alphanumeric characters from the name
|
||||
simpleName.remove(nonAlnum);
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
const QChar CardInfo::getColorChar() const
|
||||
{
|
||||
QString colors = getColors();
|
||||
switch (colors.size()) {
|
||||
case 0:
|
||||
return QChar();
|
||||
case 1:
|
||||
return colors.at(0);
|
||||
default:
|
||||
return QChar('m');
|
||||
}
|
||||
}
|
||||
|
||||
CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded)
|
||||
{
|
||||
qRegisterMetaType<CardInfoPtr>("CardInfoPtr");
|
||||
qRegisterMetaType<CardInfoPtr>("CardSetPtr");
|
||||
|
||||
// add new parsers here
|
||||
availableParsers << new CockatriceXml4Parser;
|
||||
availableParsers << new CockatriceXml3Parser;
|
||||
|
||||
for (auto &parser : availableParsers) {
|
||||
connect(parser, SIGNAL(addCard(CardInfoPtr)), this, SLOT(addCard(CardInfoPtr)), Qt::DirectConnection);
|
||||
connect(parser, SIGNAL(addSet(CardSetPtr)), this, SLOT(addSet(CardSetPtr)), Qt::DirectConnection);
|
||||
}
|
||||
|
||||
connect(&SettingsCache::instance(), SIGNAL(cardDatabasePathChanged()), this, SLOT(loadCardDatabases()));
|
||||
}
|
||||
|
||||
CardDatabase::~CardDatabase()
|
||||
{
|
||||
clear();
|
||||
qDeleteAll(availableParsers);
|
||||
}
|
||||
|
||||
void CardDatabase::clear()
|
||||
{
|
||||
clearDatabaseMutex->lock();
|
||||
|
||||
QHashIterator<QString, CardInfoPtr> i(cards);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (i.value()) {
|
||||
removeCard(i.value());
|
||||
}
|
||||
}
|
||||
|
||||
cards.clear();
|
||||
simpleNameCards.clear();
|
||||
|
||||
sets.clear();
|
||||
ICardDatabaseParser::clearSetlist();
|
||||
|
||||
loadStatus = NotLoaded;
|
||||
|
||||
clearDatabaseMutex->unlock();
|
||||
}
|
||||
|
||||
void CardDatabase::addCard(CardInfoPtr card)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
qDebug() << "addCard(nullptr)";
|
||||
return;
|
||||
}
|
||||
|
||||
// if card already exists just add the new set property
|
||||
if (cards.contains(card->getName())) {
|
||||
CardInfoPtr sameCard = cards[card->getName()];
|
||||
for (const CardInfoPerSet &set : card->getSets()) {
|
||||
sameCard->addToSet(set.getPtr(), set);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
addCardMutex->lock();
|
||||
cards.insert(card->getName(), card);
|
||||
simpleNameCards.insert(card->getSimpleName(), card);
|
||||
addCardMutex->unlock();
|
||||
emit cardAdded(card);
|
||||
}
|
||||
|
||||
void CardDatabase::removeCard(CardInfoPtr card)
|
||||
{
|
||||
if (card.isNull()) {
|
||||
qDebug() << "removeCard(nullptr)";
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto *cardRelation : card->getRelatedCards())
|
||||
cardRelation->deleteLater();
|
||||
|
||||
for (auto *cardRelation : card->getReverseRelatedCards())
|
||||
cardRelation->deleteLater();
|
||||
|
||||
for (auto *cardRelation : card->getReverseRelatedCards2Me())
|
||||
cardRelation->deleteLater();
|
||||
|
||||
removeCardMutex->lock();
|
||||
cards.remove(card->getName());
|
||||
simpleNameCards.remove(card->getSimpleName());
|
||||
removeCardMutex->unlock();
|
||||
emit cardRemoved(card);
|
||||
}
|
||||
|
||||
CardInfoPtr CardDatabase::getCard(const QString &cardName) const
|
||||
{
|
||||
return getCardFromMap(cards, cardName);
|
||||
}
|
||||
|
||||
QList<CardInfoPtr> CardDatabase::getCards(const QStringList &cardNames) const
|
||||
{
|
||||
QList<CardInfoPtr> cardInfos;
|
||||
foreach (QString cardName, cardNames) {
|
||||
CardInfoPtr ptr = getCardFromMap(cards, cardName);
|
||||
if (ptr)
|
||||
cardInfos.append(ptr);
|
||||
}
|
||||
|
||||
return cardInfos;
|
||||
}
|
||||
|
||||
CardInfoPtr CardDatabase::getCardBySimpleName(const QString &cardName) const
|
||||
{
|
||||
return getCardFromMap(simpleNameCards, CardInfo::simplifyName(cardName));
|
||||
}
|
||||
|
||||
CardInfoPtr CardDatabase::guessCard(const QString &cardName) const
|
||||
{
|
||||
CardInfoPtr temp = getCard(cardName);
|
||||
if (temp == nullptr) { // get card by simple name instead
|
||||
temp = getCardBySimpleName(cardName);
|
||||
if (temp == nullptr) { // still could not find the card, so simplify the cardName too
|
||||
QString simpleCardName = CardInfo::simplifyName(cardName);
|
||||
temp = getCardBySimpleName(simpleCardName);
|
||||
}
|
||||
}
|
||||
return temp; // returns nullptr if not found
|
||||
}
|
||||
|
||||
CardSetPtr CardDatabase::getSet(const QString &setName)
|
||||
{
|
||||
if (sets.contains(setName)) {
|
||||
return sets.value(setName);
|
||||
} else {
|
||||
CardSetPtr newSet = CardSet::newInstance(setName);
|
||||
sets.insert(setName, newSet);
|
||||
return newSet;
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabase::addSet(CardSetPtr set)
|
||||
{
|
||||
sets.insert(set->getShortName(), set);
|
||||
}
|
||||
|
||||
SetList CardDatabase::getSetList() const
|
||||
{
|
||||
SetList result;
|
||||
QHashIterator<QString, CardSetPtr> i(sets);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
result << i.value();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CardInfoPtr CardDatabase::getCardFromMap(const CardNameMap &cardMap, const QString &cardName) const
|
||||
{
|
||||
if (cardMap.contains(cardName))
|
||||
return cardMap.value(cardName);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
LoadStatus CardDatabase::loadFromFile(const QString &fileName)
|
||||
{
|
||||
QFile file(fileName);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
if (!file.isOpen()) {
|
||||
return FileError;
|
||||
}
|
||||
|
||||
for (auto parser : availableParsers) {
|
||||
file.reset();
|
||||
if (parser->getCanParseFile(fileName, file)) {
|
||||
file.reset();
|
||||
parser->parseFile(file);
|
||||
return Ok;
|
||||
}
|
||||
}
|
||||
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
LoadStatus CardDatabase::loadCardDatabase(const QString &path)
|
||||
{
|
||||
auto startTime = QTime::currentTime();
|
||||
LoadStatus tempLoadStatus = NotLoaded;
|
||||
if (!path.isEmpty()) {
|
||||
loadFromFileMutex->lock();
|
||||
tempLoadStatus = loadFromFile(path);
|
||||
loadFromFileMutex->unlock();
|
||||
}
|
||||
|
||||
int msecs = startTime.msecsTo(QTime::currentTime());
|
||||
qDebug() << "[CardDatabase] loadCardDatabase(): Path =" << path << "Status =" << tempLoadStatus
|
||||
<< "Cards =" << cards.size() << "Sets =" << sets.size() << QString("%1ms").arg(msecs);
|
||||
|
||||
return tempLoadStatus;
|
||||
}
|
||||
|
||||
LoadStatus CardDatabase::loadCardDatabases()
|
||||
{
|
||||
reloadDatabaseMutex->lock();
|
||||
|
||||
qDebug() << "CardDatabase::loadCardDatabases start";
|
||||
|
||||
clear(); // remove old db
|
||||
|
||||
loadStatus = loadCardDatabase(SettingsCache::instance().getCardDatabasePath()); // load main card database
|
||||
loadCardDatabase(SettingsCache::instance().getTokenDatabasePath()); // load tokens database
|
||||
loadCardDatabase(SettingsCache::instance().getSpoilerCardDatabasePath()); // load spoilers database
|
||||
|
||||
// find all custom card databases, recursively & following symlinks
|
||||
// then load them alphabetically
|
||||
QDirIterator customDatabaseIterator(SettingsCache::instance().getCustomCardDatabasePath(), QStringList() << "*.xml",
|
||||
QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
QStringList databasePaths;
|
||||
while (customDatabaseIterator.hasNext()) {
|
||||
customDatabaseIterator.next();
|
||||
databasePaths.push_back(customDatabaseIterator.filePath());
|
||||
}
|
||||
databasePaths.sort();
|
||||
|
||||
for (auto i = 0; i < databasePaths.size(); ++i) {
|
||||
const auto &databasePath = databasePaths.at(i);
|
||||
qDebug() << "Loading Custom Set" << i << "(" << databasePath << ")";
|
||||
loadCardDatabase(databasePath);
|
||||
}
|
||||
|
||||
// AFTER all the cards have been loaded
|
||||
|
||||
// resolve the reverse-related tags
|
||||
refreshCachedReverseRelatedCards();
|
||||
|
||||
if (loadStatus == Ok) {
|
||||
checkUnknownSets(); // update deck editors, etc
|
||||
qDebug() << "CardDatabase::loadCardDatabases success";
|
||||
} else {
|
||||
qDebug() << "CardDatabase::loadCardDatabases failed";
|
||||
emit cardDatabaseLoadingFailed(); // bring up the settings dialog
|
||||
}
|
||||
|
||||
reloadDatabaseMutex->unlock();
|
||||
return loadStatus;
|
||||
}
|
||||
|
||||
void CardDatabase::refreshCachedReverseRelatedCards()
|
||||
{
|
||||
for (const CardInfoPtr &card : cards)
|
||||
card->resetReverseRelatedCards2Me();
|
||||
|
||||
for (const CardInfoPtr &card : cards) {
|
||||
if (card->getReverseRelatedCards().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (CardRelation *cardRelation, card->getReverseRelatedCards()) {
|
||||
const QString &targetCard = cardRelation->getName();
|
||||
if (!cards.contains(targetCard)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto *newCardRelation = new CardRelation(
|
||||
card->getName(), cardRelation->getAttachType(), cardRelation->getIsCreateAllExclusion(),
|
||||
cardRelation->getIsVariable(), cardRelation->getDefaultCount(), cardRelation->getIsPersistent());
|
||||
cards.value(targetCard)->addReverseRelatedCards2Me(newCardRelation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList CardDatabase::getAllMainCardTypes() const
|
||||
{
|
||||
QSet<QString> types;
|
||||
QHashIterator<QString, CardInfoPtr> cardIterator(cards);
|
||||
while (cardIterator.hasNext()) {
|
||||
types.insert(cardIterator.next().value()->getMainCardType());
|
||||
}
|
||||
return types.values();
|
||||
}
|
||||
|
||||
void CardDatabase::checkUnknownSets()
|
||||
{
|
||||
auto _sets = getSetList();
|
||||
|
||||
if (_sets.getEnabledSetsNum()) {
|
||||
// if some sets are first found on this run, ask the user
|
||||
int numUnknownSets = _sets.getUnknownSetsNum();
|
||||
QStringList unknownSetNames = _sets.getUnknownSetsNames();
|
||||
if (numUnknownSets > 0) {
|
||||
emit cardDatabaseNewSetsFound(numUnknownSets, unknownSetNames);
|
||||
} else {
|
||||
_sets.markAllAsKnown();
|
||||
}
|
||||
} else {
|
||||
// No set enabled. Probably this is the first time running trice
|
||||
_sets.guessSortKeys();
|
||||
_sets.sortByKey();
|
||||
_sets.enableAll();
|
||||
notifyEnabledSetsChanged();
|
||||
|
||||
emit cardDatabaseAllNewSetsEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabase::enableAllUnknownSets()
|
||||
{
|
||||
auto _sets = getSetList();
|
||||
_sets.enableAllUnknown();
|
||||
}
|
||||
|
||||
void CardDatabase::markAllSetsAsKnown()
|
||||
{
|
||||
auto _sets = getSetList();
|
||||
_sets.markAllAsKnown();
|
||||
}
|
||||
|
||||
void CardDatabase::notifyEnabledSetsChanged()
|
||||
{
|
||||
// refresh the list of cached set names
|
||||
for (const CardInfoPtr &card : cards)
|
||||
card->refreshCachedSetNames();
|
||||
|
||||
// inform the carddatabasemodels that they need to re-check their list of cards
|
||||
emit cardDatabaseEnabledSetsChanged();
|
||||
}
|
||||
|
||||
bool CardDatabase::saveCustomTokensToFile()
|
||||
{
|
||||
QString fileName =
|
||||
SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardDatabase::TOKENS_SETNAME + ".xml";
|
||||
|
||||
SetNameMap tmpSets;
|
||||
CardSetPtr customTokensSet = getSet(CardDatabase::TOKENS_SETNAME);
|
||||
tmpSets.insert(CardDatabase::TOKENS_SETNAME, customTokensSet);
|
||||
|
||||
CardNameMap tmpCards;
|
||||
for (const CardInfoPtr &card : cards) {
|
||||
if (card->getSets().contains(CardDatabase::TOKENS_SETNAME)) {
|
||||
tmpCards.insert(card->getName(), card);
|
||||
}
|
||||
}
|
||||
|
||||
availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName);
|
||||
return true;
|
||||
}
|
||||
|
||||
CardRelation::CardRelation(const QString &_name,
|
||||
AttachType _attachType,
|
||||
bool _isCreateAllExclusion,
|
||||
bool _isVariableCount,
|
||||
int _defaultCount,
|
||||
bool _isPersistent)
|
||||
: name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion),
|
||||
isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent)
|
||||
{
|
||||
}
|
||||
|
||||
void CardInfo::resetReverseRelatedCards2Me()
|
||||
{
|
||||
foreach (CardRelation *cardRelation, this->getReverseRelatedCards2Me()) {
|
||||
cardRelation->deleteLater();
|
||||
}
|
||||
reverseRelatedCardsToMe = QList<CardRelation *>();
|
||||
}
|
||||
|
||||
// Back-compatibility methods. Remove ASAP
|
||||
const QString CardInfo::getCardType() const
|
||||
{
|
||||
return getProperty(Mtg::CardType);
|
||||
}
|
||||
void CardInfo::setCardType(const QString &value)
|
||||
{
|
||||
setProperty(Mtg::CardType, value);
|
||||
}
|
||||
const QString CardInfo::getCmc() const
|
||||
{
|
||||
return getProperty(Mtg::ConvertedManaCost);
|
||||
}
|
||||
const QString CardInfo::getColors() const
|
||||
{
|
||||
return getProperty(Mtg::Colors);
|
||||
}
|
||||
void CardInfo::setColors(const QString &value)
|
||||
{
|
||||
setProperty(Mtg::Colors, value);
|
||||
}
|
||||
const QString CardInfo::getLoyalty() const
|
||||
{
|
||||
return getProperty(Mtg::Loyalty);
|
||||
}
|
||||
const QString CardInfo::getMainCardType() const
|
||||
{
|
||||
return getProperty(Mtg::MainCardType);
|
||||
}
|
||||
const QString CardInfo::getManaCost() const
|
||||
{
|
||||
return getProperty(Mtg::ManaCost);
|
||||
}
|
||||
const QString CardInfo::getPowTough() const
|
||||
{
|
||||
return getProperty(Mtg::PowTough);
|
||||
}
|
||||
void CardInfo::setPowTough(const QString &value)
|
||||
{
|
||||
setProperty(Mtg::PowTough, value);
|
||||
}
|
||||
527
cockatrice/src/game/cards/card_database.h
Normal file
527
cockatrice/src/game/cards/card_database.h
Normal file
|
|
@ -0,0 +1,527 @@
|
|||
#ifndef CARDDATABASE_H
|
||||
#define CARDDATABASE_H
|
||||
|
||||
#include <QBasicMutex>
|
||||
#include <QDate>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QMetaType>
|
||||
#include <QSharedPointer>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
#include <utility>
|
||||
|
||||
class CardDatabase;
|
||||
class CardInfo;
|
||||
class CardInfoPerSet;
|
||||
class CardSet;
|
||||
class CardRelation;
|
||||
class ICardDatabaseParser;
|
||||
|
||||
typedef QMap<QString, QString> QStringMap;
|
||||
typedef QSharedPointer<CardInfo> CardInfoPtr;
|
||||
typedef QSharedPointer<CardSet> CardSetPtr;
|
||||
typedef QMap<QString, CardInfoPerSet> CardInfoPerSetMap;
|
||||
|
||||
Q_DECLARE_METATYPE(CardInfoPtr)
|
||||
|
||||
class CardSet : public QList<CardInfoPtr>
|
||||
{
|
||||
private:
|
||||
QString shortName, longName;
|
||||
unsigned int sortKey;
|
||||
QDate releaseDate;
|
||||
QString setType;
|
||||
bool enabled, isknown;
|
||||
|
||||
public:
|
||||
explicit CardSet(const QString &_shortName = QString(),
|
||||
const QString &_longName = QString(),
|
||||
const QString &_setType = QString(),
|
||||
const QDate &_releaseDate = QDate());
|
||||
static CardSetPtr newInstance(const QString &_shortName = QString(),
|
||||
const QString &_longName = QString(),
|
||||
const QString &_setType = QString(),
|
||||
const QDate &_releaseDate = QDate());
|
||||
QString getCorrectedShortName() const;
|
||||
QString getShortName() const
|
||||
{
|
||||
return shortName;
|
||||
}
|
||||
QString getLongName() const
|
||||
{
|
||||
return longName;
|
||||
}
|
||||
QString getSetType() const
|
||||
{
|
||||
return setType;
|
||||
}
|
||||
QDate getReleaseDate() const
|
||||
{
|
||||
return releaseDate;
|
||||
}
|
||||
void setLongName(const QString &_longName)
|
||||
{
|
||||
longName = _longName;
|
||||
}
|
||||
void setSetType(const QString &_setType)
|
||||
{
|
||||
setType = _setType;
|
||||
}
|
||||
void setReleaseDate(const QDate &_releaseDate)
|
||||
{
|
||||
releaseDate = _releaseDate;
|
||||
}
|
||||
|
||||
void loadSetOptions();
|
||||
int getSortKey() const
|
||||
{
|
||||
return sortKey;
|
||||
}
|
||||
void setSortKey(unsigned int _sortKey);
|
||||
bool getEnabled() const
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
void setEnabled(bool _enabled);
|
||||
bool getIsKnown() const
|
||||
{
|
||||
return isknown;
|
||||
}
|
||||
void setIsKnown(bool _isknown);
|
||||
|
||||
// Determine incomplete sets.
|
||||
bool getIsKnownIgnored() const
|
||||
{
|
||||
return longName.length() + setType.length() + releaseDate.toString().length() == 0;
|
||||
}
|
||||
};
|
||||
|
||||
class SetList : public QList<CardSetPtr>
|
||||
{
|
||||
private:
|
||||
class KeyCompareFunctor;
|
||||
|
||||
public:
|
||||
void sortByKey();
|
||||
void guessSortKeys();
|
||||
void enableAllUnknown();
|
||||
void enableAll();
|
||||
void markAllAsKnown();
|
||||
int getEnabledSetsNum();
|
||||
int getUnknownSetsNum();
|
||||
QStringList getUnknownSetsNames();
|
||||
};
|
||||
|
||||
class CardInfoPerSet
|
||||
{
|
||||
public:
|
||||
explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer<CardSet>(nullptr));
|
||||
~CardInfoPerSet() = default;
|
||||
|
||||
private:
|
||||
CardSetPtr set;
|
||||
// per-set card properties;
|
||||
QVariantHash properties;
|
||||
|
||||
public:
|
||||
const CardSetPtr getPtr() const
|
||||
{
|
||||
return set;
|
||||
}
|
||||
const QStringList getProperties() const
|
||||
{
|
||||
return properties.keys();
|
||||
}
|
||||
const QString getProperty(const QString &propertyName) const
|
||||
{
|
||||
return properties.value(propertyName).toString();
|
||||
}
|
||||
void setProperty(const QString &_name, const QString &_value)
|
||||
{
|
||||
properties.insert(_name, _value);
|
||||
}
|
||||
};
|
||||
|
||||
class CardInfo : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
CardInfoPtr smartThis;
|
||||
// The card name
|
||||
QString name;
|
||||
// The name without punctuation or capitalization, for better card name recognition.
|
||||
QString simpleName;
|
||||
// The key used to identify this card in the cache
|
||||
QString pixmapCacheKey;
|
||||
// card text
|
||||
QString text;
|
||||
// whether this is not a "real" card but a token
|
||||
bool isToken;
|
||||
// basic card properties; common for all the sets
|
||||
QVariantHash properties;
|
||||
// the cards i'm related to
|
||||
QList<CardRelation *> relatedCards;
|
||||
// the card i'm reverse-related to
|
||||
QList<CardRelation *> reverseRelatedCards;
|
||||
// the cards thare are reverse-related to me
|
||||
QList<CardRelation *> reverseRelatedCardsToMe;
|
||||
// card sets
|
||||
CardInfoPerSetMap sets;
|
||||
// cached set names
|
||||
QString setsNames;
|
||||
// positioning properties; used by UI
|
||||
bool cipt;
|
||||
int tableRow;
|
||||
bool upsideDownArt;
|
||||
|
||||
public:
|
||||
explicit CardInfo(const QString &_name = QString(),
|
||||
const QString &_text = QString(),
|
||||
bool _isToken = false,
|
||||
QVariantHash _properties = QVariantHash(),
|
||||
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
|
||||
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
|
||||
CardInfoPerSetMap _sets = CardInfoPerSetMap(),
|
||||
bool _cipt = false,
|
||||
int _tableRow = 0,
|
||||
bool _upsideDownArt = false);
|
||||
~CardInfo() override;
|
||||
|
||||
static CardInfoPtr newInstance(const QString &_name = QString(),
|
||||
const QString &_text = QString(),
|
||||
bool _isToken = false,
|
||||
QVariantHash _properties = QVariantHash(),
|
||||
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
|
||||
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
|
||||
CardInfoPerSetMap _sets = CardInfoPerSetMap(),
|
||||
bool _cipt = false,
|
||||
int _tableRow = 0,
|
||||
bool _upsideDownArt = false);
|
||||
|
||||
void setSmartPointer(CardInfoPtr _ptr)
|
||||
{
|
||||
smartThis = std::move(_ptr);
|
||||
}
|
||||
|
||||
// basic properties
|
||||
inline const QString &getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
const QString &getSimpleName() const
|
||||
{
|
||||
return simpleName;
|
||||
}
|
||||
const QString &getPixmapCacheKey() const
|
||||
{
|
||||
return pixmapCacheKey;
|
||||
}
|
||||
|
||||
const QString &getText() const
|
||||
{
|
||||
return text;
|
||||
}
|
||||
void setText(const QString &_text)
|
||||
{
|
||||
text = _text;
|
||||
emit cardInfoChanged(smartThis);
|
||||
}
|
||||
|
||||
bool getIsToken() const
|
||||
{
|
||||
return isToken;
|
||||
}
|
||||
const QStringList getProperties() const
|
||||
{
|
||||
return properties.keys();
|
||||
}
|
||||
const QString getProperty(const QString &propertyName) const
|
||||
{
|
||||
return properties.value(propertyName).toString();
|
||||
}
|
||||
void setProperty(const QString &_name, const QString &_value)
|
||||
{
|
||||
properties.insert(_name, _value);
|
||||
emit cardInfoChanged(smartThis);
|
||||
}
|
||||
bool hasProperty(const QString &propertyName) const
|
||||
{
|
||||
return properties.contains(propertyName);
|
||||
}
|
||||
const CardInfoPerSetMap &getSets() const
|
||||
{
|
||||
return sets;
|
||||
}
|
||||
const QString &getSetsNames() const
|
||||
{
|
||||
return setsNames;
|
||||
}
|
||||
const QString getSetProperty(const QString &setName, const QString &propertyName) const
|
||||
{
|
||||
if (!sets.contains(setName))
|
||||
return "";
|
||||
return sets[setName].getProperty(propertyName);
|
||||
}
|
||||
void setSetProperty(const QString &setName, const QString &_name, const QString &_value)
|
||||
{
|
||||
if (!sets.contains(setName))
|
||||
return;
|
||||
|
||||
sets[setName].setProperty(_name, _value);
|
||||
emit cardInfoChanged(smartThis);
|
||||
}
|
||||
|
||||
// related cards
|
||||
const QList<CardRelation *> &getRelatedCards() const
|
||||
{
|
||||
return relatedCards;
|
||||
}
|
||||
const QList<CardRelation *> &getReverseRelatedCards() const
|
||||
{
|
||||
return reverseRelatedCards;
|
||||
}
|
||||
const QList<CardRelation *> &getReverseRelatedCards2Me() const
|
||||
{
|
||||
return reverseRelatedCardsToMe;
|
||||
}
|
||||
const QList<CardRelation *> getAllRelatedCards() const
|
||||
{
|
||||
QList<CardRelation *> result;
|
||||
result.append(getRelatedCards());
|
||||
result.append(getReverseRelatedCards2Me());
|
||||
return result;
|
||||
}
|
||||
void resetReverseRelatedCards2Me();
|
||||
void addReverseRelatedCards2Me(CardRelation *cardRelation)
|
||||
{
|
||||
reverseRelatedCardsToMe.append(cardRelation);
|
||||
}
|
||||
|
||||
// positioning
|
||||
bool getCipt() const
|
||||
{
|
||||
return cipt;
|
||||
}
|
||||
int getTableRow() const
|
||||
{
|
||||
return tableRow;
|
||||
}
|
||||
void setTableRow(int _tableRow)
|
||||
{
|
||||
tableRow = _tableRow;
|
||||
}
|
||||
bool getUpsideDownArt() const
|
||||
{
|
||||
return upsideDownArt;
|
||||
}
|
||||
const QChar getColorChar() const;
|
||||
|
||||
// Back-compatibility methods. Remove ASAP
|
||||
const QString getCardType() const;
|
||||
void setCardType(const QString &value);
|
||||
const QString getCmc() const;
|
||||
const QString getColors() const;
|
||||
void setColors(const QString &value);
|
||||
const QString getLoyalty() const;
|
||||
const QString getMainCardType() const;
|
||||
const QString getManaCost() const;
|
||||
const QString getPowTough() const;
|
||||
void setPowTough(const QString &value);
|
||||
|
||||
// methods using per-set properties
|
||||
QString getCustomPicURL(const QString &set) const
|
||||
{
|
||||
return getSetProperty(set, "picurl");
|
||||
}
|
||||
QString getCorrectedName() const;
|
||||
void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet());
|
||||
void emitPixmapUpdated()
|
||||
{
|
||||
emit pixmapUpdated();
|
||||
}
|
||||
void refreshCachedSetNames();
|
||||
|
||||
/**
|
||||
* Simplify a name to have no punctuation and lowercase all letters, for
|
||||
* less strict name-matching.
|
||||
*/
|
||||
static QString simplifyName(const QString &name);
|
||||
|
||||
signals:
|
||||
void pixmapUpdated();
|
||||
void cardInfoChanged(CardInfoPtr card);
|
||||
};
|
||||
|
||||
enum LoadStatus
|
||||
{
|
||||
Ok,
|
||||
VersionTooOld,
|
||||
Invalid,
|
||||
NotLoaded,
|
||||
FileError,
|
||||
NoCards
|
||||
};
|
||||
|
||||
typedef QHash<QString, CardInfoPtr> CardNameMap;
|
||||
typedef QHash<QString, CardSetPtr> SetNameMap;
|
||||
|
||||
class CardDatabase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
/*
|
||||
* The cards, indexed by name.
|
||||
*/
|
||||
CardNameMap cards;
|
||||
|
||||
/**
|
||||
* The cards, indexed by their simple name.
|
||||
*/
|
||||
CardNameMap simpleNameCards;
|
||||
|
||||
/*
|
||||
* The sets, indexed by short name.
|
||||
*/
|
||||
SetNameMap sets;
|
||||
|
||||
LoadStatus loadStatus;
|
||||
|
||||
QVector<ICardDatabaseParser *> availableParsers;
|
||||
|
||||
private:
|
||||
CardInfoPtr getCardFromMap(const CardNameMap &cardMap, const QString &cardName) const;
|
||||
void checkUnknownSets();
|
||||
void refreshCachedReverseRelatedCards();
|
||||
|
||||
QBasicMutex *reloadDatabaseMutex = new QBasicMutex(), *clearDatabaseMutex = new QBasicMutex(),
|
||||
*loadFromFileMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(),
|
||||
*removeCardMutex = new QBasicMutex();
|
||||
|
||||
public:
|
||||
static const char *TOKENS_SETNAME;
|
||||
|
||||
explicit CardDatabase(QObject *parent = nullptr);
|
||||
~CardDatabase() override;
|
||||
void clear();
|
||||
void removeCard(CardInfoPtr card);
|
||||
CardInfoPtr getCard(const QString &cardName) const;
|
||||
QList<CardInfoPtr> getCards(const QStringList &cardNames) const;
|
||||
CardInfoPtr guessCard(const QString &cardName) const;
|
||||
|
||||
/*
|
||||
* Get a card by its simple name. The name will be simplified in this
|
||||
* function, so you don't need to simplify it beforehand.
|
||||
*/
|
||||
CardInfoPtr getCardBySimpleName(const QString &cardName) const;
|
||||
|
||||
CardSetPtr getSet(const QString &setName);
|
||||
QList<CardInfoPtr> getCardList() const
|
||||
{
|
||||
return cards.values();
|
||||
}
|
||||
SetList getSetList() const;
|
||||
LoadStatus loadFromFile(const QString &fileName);
|
||||
bool saveCustomTokensToFile();
|
||||
QStringList getAllMainCardTypes() const;
|
||||
LoadStatus getLoadStatus() const
|
||||
{
|
||||
return loadStatus;
|
||||
}
|
||||
void enableAllUnknownSets();
|
||||
void markAllSetsAsKnown();
|
||||
void notifyEnabledSetsChanged();
|
||||
|
||||
public slots:
|
||||
LoadStatus loadCardDatabases();
|
||||
void addCard(CardInfoPtr card);
|
||||
void addSet(CardSetPtr set);
|
||||
protected slots:
|
||||
LoadStatus loadCardDatabase(const QString &path);
|
||||
signals:
|
||||
void cardDatabaseLoadingFailed();
|
||||
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
|
||||
void cardDatabaseAllNewSetsEnabled();
|
||||
void cardDatabaseEnabledSetsChanged();
|
||||
void cardAdded(CardInfoPtr card);
|
||||
void cardRemoved(CardInfoPtr card);
|
||||
};
|
||||
|
||||
class CardRelation : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum AttachType
|
||||
{
|
||||
DoesNotAttach = 0,
|
||||
AttachTo = 1,
|
||||
TransformInto = 2,
|
||||
};
|
||||
|
||||
private:
|
||||
QString name;
|
||||
AttachType attachType;
|
||||
bool isCreateAllExclusion;
|
||||
bool isVariableCount;
|
||||
int defaultCount;
|
||||
bool isPersistent;
|
||||
|
||||
public:
|
||||
explicit CardRelation(const QString &_name = QString(),
|
||||
AttachType _attachType = DoesNotAttach,
|
||||
bool _isCreateAllExclusion = false,
|
||||
bool _isVariableCount = false,
|
||||
int _defaultCount = 1,
|
||||
bool _isPersistent = false);
|
||||
|
||||
inline const QString &getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
AttachType getAttachType() const
|
||||
{
|
||||
return attachType;
|
||||
}
|
||||
bool getDoesAttach() const
|
||||
{
|
||||
return attachType != DoesNotAttach;
|
||||
}
|
||||
bool getDoesTransform() const
|
||||
{
|
||||
return attachType == TransformInto;
|
||||
}
|
||||
QString getAttachTypeAsString() const
|
||||
{
|
||||
switch (attachType) {
|
||||
case AttachTo:
|
||||
return "attach";
|
||||
case TransformInto:
|
||||
return "transform";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
bool getCanCreateAnother() const
|
||||
{
|
||||
return !getDoesAttach();
|
||||
}
|
||||
bool getIsCreateAllExclusion() const
|
||||
{
|
||||
return isCreateAllExclusion;
|
||||
}
|
||||
bool getIsVariable() const
|
||||
{
|
||||
return isVariableCount;
|
||||
}
|
||||
int getDefaultCount() const
|
||||
{
|
||||
return defaultCount;
|
||||
}
|
||||
bool getIsPersistent() const
|
||||
{
|
||||
return isPersistent;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
385
cockatrice/src/game/cards/card_database_model.cpp
Normal file
385
cockatrice/src/game/cards/card_database_model.cpp
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
#include "card_database_model.h"
|
||||
|
||||
#include "../filters/filter_tree.h"
|
||||
|
||||
#include <QMap>
|
||||
|
||||
#define CARDDBMODEL_COLUMNS 6
|
||||
|
||||
CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent)
|
||||
: QAbstractListModel(parent), db(_db), showOnlyCardsFromEnabledSets(_showOnlyCardsFromEnabledSets)
|
||||
{
|
||||
connect(db, SIGNAL(cardAdded(CardInfoPtr)), this, SLOT(cardAdded(CardInfoPtr)));
|
||||
connect(db, SIGNAL(cardRemoved(CardInfoPtr)), this, SLOT(cardRemoved(CardInfoPtr)));
|
||||
connect(db, SIGNAL(cardDatabaseEnabledSetsChanged()), this, SLOT(cardDatabaseEnabledSetsChanged()));
|
||||
|
||||
cardDatabaseEnabledSetsChanged();
|
||||
}
|
||||
|
||||
CardDatabaseModel::~CardDatabaseModel() = default;
|
||||
|
||||
QMap<wchar_t, wchar_t> CardDatabaseDisplayModel::characterTranslation = {{L'“', L'\"'},
|
||||
{L'”', L'\"'},
|
||||
{L'‘', L'\''},
|
||||
{L'’', L'\''}};
|
||||
|
||||
int CardDatabaseModel::rowCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return cardList.size();
|
||||
}
|
||||
|
||||
int CardDatabaseModel::columnCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return CARDDBMODEL_COLUMNS;
|
||||
}
|
||||
|
||||
QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= cardList.size() || index.column() >= CARDDBMODEL_COLUMNS ||
|
||||
(role != Qt::DisplayRole && role != SortRole))
|
||||
return QVariant();
|
||||
|
||||
CardInfoPtr card = cardList.at(index.row());
|
||||
switch (index.column()) {
|
||||
case NameColumn:
|
||||
return card->getName();
|
||||
case SetListColumn:
|
||||
return card->getSetsNames();
|
||||
case ManaCostColumn:
|
||||
return role == SortRole ? QString("%1%2").arg(card->getCmc(), 4, QChar('0')).arg(card->getManaCost())
|
||||
: card->getManaCost();
|
||||
case CardTypeColumn:
|
||||
return card->getCardType();
|
||||
case PTColumn:
|
||||
return card->getPowTough();
|
||||
case ColorColumn:
|
||||
return card->getColors();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant CardDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
if (orientation != Qt::Horizontal)
|
||||
return QVariant();
|
||||
switch (section) {
|
||||
case NameColumn:
|
||||
return QString(tr("Name"));
|
||||
case SetListColumn:
|
||||
return QString(tr("Sets"));
|
||||
case ManaCostColumn:
|
||||
return QString(tr("Mana cost"));
|
||||
case CardTypeColumn:
|
||||
return QString(tr("Card type"));
|
||||
case PTColumn:
|
||||
return QString(tr("P/T"));
|
||||
case ColorColumn:
|
||||
return QString(tr("Color(s)"));
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardInfoChanged(CardInfoPtr card)
|
||||
{
|
||||
const int row = cardList.indexOf(card);
|
||||
if (row == -1)
|
||||
return;
|
||||
|
||||
emit dataChanged(index(row, 0), index(row, CARDDBMODEL_COLUMNS - 1));
|
||||
}
|
||||
|
||||
bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card)
|
||||
{
|
||||
if (!showOnlyCardsFromEnabledSets)
|
||||
return true;
|
||||
|
||||
for (const auto &set : card->getSets()) {
|
||||
if (set.getPtr()->getEnabled())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardDatabaseEnabledSetsChanged()
|
||||
{
|
||||
// remove all the cards no more present in at least one enabled set
|
||||
foreach (CardInfoPtr card, cardList) {
|
||||
if (!checkCardHasAtLeastOneEnabledSet(card))
|
||||
cardRemoved(card);
|
||||
}
|
||||
|
||||
// re-check all the card currently not shown, maybe their part of a newly-enabled set
|
||||
foreach (CardInfoPtr card, db->getCardList()) {
|
||||
if (!cardList.contains(card))
|
||||
cardAdded(card);
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardAdded(CardInfoPtr card)
|
||||
{
|
||||
if (checkCardHasAtLeastOneEnabledSet(card)) {
|
||||
// add the card if it's present in at least one enabled set
|
||||
beginInsertRows(QModelIndex(), cardList.size(), cardList.size());
|
||||
cardList.append(card);
|
||||
connect(card.data(), SIGNAL(cardInfoChanged(CardInfoPtr)), this, SLOT(cardInfoChanged(CardInfoPtr)));
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardRemoved(CardInfoPtr card)
|
||||
{
|
||||
const int row = cardList.indexOf(card);
|
||||
if (row == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
disconnect(card.data(), nullptr, this, nullptr);
|
||||
card.clear();
|
||||
cardList.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent), isToken(ShowAll), filterString(nullptr)
|
||||
{
|
||||
filterTree = nullptr;
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
|
||||
dirtyTimer.setSingleShot(true);
|
||||
connect(&dirtyTimer, &QTimer::timeout, this, &CardDatabaseDisplayModel::invalidate);
|
||||
|
||||
loadedRowCount = 0;
|
||||
}
|
||||
|
||||
bool CardDatabaseDisplayModel::canFetchMore(const QModelIndex &index) const
|
||||
{
|
||||
return loadedRowCount < sourceModel()->rowCount(index);
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index)
|
||||
{
|
||||
int remainder = sourceModel()->rowCount(index) - loadedRowCount;
|
||||
int itemsToFetch = qMin(100, remainder);
|
||||
|
||||
beginInsertRows(QModelIndex(), loadedRowCount, loadedRowCount + itemsToFetch - 1);
|
||||
|
||||
loadedRowCount += itemsToFetch;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return qMin(QSortFilterProxyModel::rowCount(parent), loadedRowCount);
|
||||
}
|
||||
|
||||
bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
|
||||
QString leftString = sourceModel()->data(left, CardDatabaseModel::SortRole).toString();
|
||||
QString rightString = sourceModel()->data(right, CardDatabaseModel::SortRole).toString();
|
||||
|
||||
if (!cardName.isEmpty() && left.column() == CardDatabaseModel::NameColumn) {
|
||||
bool isLeftType = leftString.startsWith(cardName, Qt::CaseInsensitive);
|
||||
bool isRightType = rightString.startsWith(cardName, Qt::CaseInsensitive);
|
||||
|
||||
// test for an exact match: isLeftType && leftString.size() == cardName.size()
|
||||
// or an exclusive start match: isLeftType && !isRightType
|
||||
if (isLeftType && (!isRightType || leftString.size() == cardName.size()))
|
||||
return true;
|
||||
|
||||
// same checks for the right string
|
||||
if (isRightType && (!isLeftType || rightString.size() == cardName.size()))
|
||||
return false;
|
||||
} else if (right.column() == CardDatabaseModel::PTColumn && left.column() == CardDatabaseModel::PTColumn) {
|
||||
QStringList leftList = leftString.split("/");
|
||||
QStringList rightList = rightString.split("/");
|
||||
|
||||
if (leftList.size() == 2 && rightList.size() == 2) {
|
||||
|
||||
// cool, have both P/T in list now
|
||||
int lessThanNum = lessThanNumerically(leftList.at(0), rightList.at(0));
|
||||
if (lessThanNum != 0) {
|
||||
return lessThanNum < 0;
|
||||
} else {
|
||||
// power equal, check toughness
|
||||
return lessThanNumerically(leftList.at(1), rightList.at(1)) < 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QString::localeAwareCompare(leftString, rightString) < 0;
|
||||
}
|
||||
|
||||
int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QString &right)
|
||||
{
|
||||
if (left == right) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool okLeft, okRight;
|
||||
float leftNum = left.toFloat(&okLeft);
|
||||
float rightNum = right.toFloat(&okRight);
|
||||
|
||||
if (okLeft && okRight) {
|
||||
if (leftNum < rightNum) {
|
||||
return -1;
|
||||
} else if (leftNum > rightNum) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// try and parsing again, for weird ones like "1+*"
|
||||
QString leftAfterNum = "";
|
||||
QString rightAfterNum = "";
|
||||
if (!okLeft) {
|
||||
int leftNumIndex = 0;
|
||||
for (; leftNumIndex < left.length(); leftNumIndex++) {
|
||||
if (!left.at(leftNumIndex).isDigit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (leftNumIndex != 0) {
|
||||
leftNum = left.left(leftNumIndex).toFloat(&okLeft);
|
||||
leftAfterNum = left.right(leftNumIndex);
|
||||
}
|
||||
}
|
||||
if (!okRight) {
|
||||
int rightNumIndex = 0;
|
||||
for (; rightNumIndex < right.length(); rightNumIndex++) {
|
||||
if (!right.at(rightNumIndex).isDigit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rightNumIndex != 0) {
|
||||
rightNum = right.left(rightNumIndex).toFloat(&okRight);
|
||||
rightAfterNum = right.right(rightNumIndex);
|
||||
}
|
||||
}
|
||||
if (okLeft && okRight) {
|
||||
|
||||
if (leftNum != rightNum) {
|
||||
// both parsed as numbers, but different number
|
||||
if (leftNum < rightNum) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// both parsed, same number, but at least one has something else
|
||||
// so compare the part after the number - prefer nothing
|
||||
return QString::localeAwareCompare(leftAfterNum, rightAfterNum);
|
||||
}
|
||||
} else if (okLeft) {
|
||||
return -1;
|
||||
} else if (okRight) {
|
||||
return 1;
|
||||
}
|
||||
// couldn't parse it, just return String comparison
|
||||
return QString::localeAwareCompare(left, right);
|
||||
}
|
||||
bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
|
||||
if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken()))
|
||||
return false;
|
||||
|
||||
if (filterString != nullptr) {
|
||||
if (filterTree != nullptr && !filterTree->acceptsCard(info)) {
|
||||
return false;
|
||||
}
|
||||
return filterString->check(info);
|
||||
}
|
||||
|
||||
return rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const
|
||||
{
|
||||
if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive))
|
||||
return false;
|
||||
|
||||
if (!cardNameSet.isEmpty() && !cardNameSet.contains(info->getName()))
|
||||
return false;
|
||||
|
||||
if (filterTree != nullptr)
|
||||
return filterTree->acceptsCard(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::clearFilterAll()
|
||||
{
|
||||
cardName.clear();
|
||||
cardText.clear();
|
||||
cardTypes.clear();
|
||||
cardColors.clear();
|
||||
if (filterTree != nullptr)
|
||||
filterTree->clear();
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree)
|
||||
{
|
||||
if (this->filterTree != nullptr)
|
||||
disconnect(this->filterTree, nullptr, this, nullptr);
|
||||
|
||||
this->filterTree = _filterTree;
|
||||
connect(this->filterTree, SIGNAL(changed()), this, SLOT(filterTreeChanged()));
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::filterTreeChanged()
|
||||
{
|
||||
invalidate();
|
||||
}
|
||||
|
||||
const QString CardDatabaseDisplayModel::sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table)
|
||||
{
|
||||
std::wstring toReturn = dirtyName.toStdWString();
|
||||
for (wchar_t &ch : toReturn) {
|
||||
if (table.contains(ch)) {
|
||||
ch = table.value(ch);
|
||||
}
|
||||
}
|
||||
return QString::fromStdWString(toReturn);
|
||||
}
|
||||
|
||||
TokenDisplayModel::TokenDisplayModel(QObject *parent) : CardDatabaseDisplayModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool TokenDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
return info->getIsToken() && rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
int TokenDisplayModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// always load all tokens at start
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
|
||||
TokenEditModel::TokenEditModel(QObject *parent) : CardDatabaseDisplayModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool TokenEditModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
return info->getIsToken() && info->getSets().contains(CardDatabase::TOKENS_SETNAME) && rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
int TokenEditModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// always load all tokens at start
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
155
cockatrice/src/game/cards/card_database_model.h
Normal file
155
cockatrice/src/game/cards/card_database_model.h
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#ifndef CARDDATABASEMODEL_H
|
||||
#define CARDDATABASEMODEL_H
|
||||
|
||||
#include "../filters/filter_string.h"
|
||||
#include "card_database.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTimer>
|
||||
|
||||
class FilterTree;
|
||||
|
||||
class CardDatabaseModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
NameColumn,
|
||||
SetListColumn,
|
||||
ManaCostColumn,
|
||||
PTColumn,
|
||||
CardTypeColumn,
|
||||
ColorColumn
|
||||
};
|
||||
enum Role
|
||||
{
|
||||
SortRole = Qt::UserRole
|
||||
};
|
||||
CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = nullptr);
|
||||
~CardDatabaseModel() override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
CardDatabase *getDatabase() const
|
||||
{
|
||||
return db;
|
||||
}
|
||||
CardInfoPtr getCard(int index) const
|
||||
{
|
||||
return cardList[index];
|
||||
}
|
||||
|
||||
private:
|
||||
QList<CardInfoPtr> cardList;
|
||||
CardDatabase *db;
|
||||
bool showOnlyCardsFromEnabledSets;
|
||||
|
||||
inline bool checkCardHasAtLeastOneEnabledSet(CardInfoPtr card);
|
||||
private slots:
|
||||
void cardAdded(CardInfoPtr card);
|
||||
void cardRemoved(CardInfoPtr card);
|
||||
void cardInfoChanged(CardInfoPtr card);
|
||||
void cardDatabaseEnabledSetsChanged();
|
||||
};
|
||||
|
||||
class CardDatabaseDisplayModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum FilterBool
|
||||
{
|
||||
ShowTrue,
|
||||
ShowFalse,
|
||||
ShowAll
|
||||
};
|
||||
|
||||
private:
|
||||
FilterBool isToken;
|
||||
QString cardName, cardText;
|
||||
QSet<QString> cardNameSet, cardTypes, cardColors;
|
||||
FilterTree *filterTree;
|
||||
FilterString *filterString;
|
||||
int loadedRowCount;
|
||||
QTimer dirtyTimer;
|
||||
|
||||
/** The translation table that will be used for sanitizeCardName. */
|
||||
static QMap<wchar_t, wchar_t> characterTranslation;
|
||||
|
||||
public:
|
||||
explicit CardDatabaseDisplayModel(QObject *parent = nullptr);
|
||||
void setFilterTree(FilterTree *_filterTree);
|
||||
void setIsToken(FilterBool _isToken)
|
||||
{
|
||||
isToken = _isToken;
|
||||
dirty();
|
||||
}
|
||||
|
||||
void setCardName(const QString &_cardName)
|
||||
{
|
||||
if (filterString != nullptr) {
|
||||
delete filterString;
|
||||
filterString = nullptr;
|
||||
}
|
||||
cardName = sanitizeCardName(_cardName, characterTranslation);
|
||||
dirty();
|
||||
}
|
||||
void setStringFilter(const QString &_src)
|
||||
{
|
||||
delete filterString;
|
||||
filterString = new FilterString(_src);
|
||||
dirty();
|
||||
}
|
||||
void setCardNameSet(const QSet<QString> &_cardNameSet)
|
||||
{
|
||||
cardNameSet = _cardNameSet;
|
||||
dirty();
|
||||
}
|
||||
|
||||
void dirty()
|
||||
{
|
||||
dirtyTimer.start(20);
|
||||
}
|
||||
void clearFilterAll();
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
static int lessThanNumerically(const QString &left, const QString &right);
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
bool rowMatchesCardName(CardInfoPtr info) const;
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
private slots:
|
||||
void filterTreeChanged();
|
||||
/** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */
|
||||
const QString sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table);
|
||||
};
|
||||
|
||||
class TokenDisplayModel : public CardDatabaseDisplayModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TokenDisplayModel(QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
class TokenEditModel : public CardDatabaseDisplayModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TokenEditModel(QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#include "card_database_parser.h"
|
||||
|
||||
SetNameMap ICardDatabaseParser::sets;
|
||||
|
||||
void ICardDatabaseParser::clearSetlist()
|
||||
{
|
||||
sets.clear();
|
||||
}
|
||||
|
||||
CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName,
|
||||
const QString &longName,
|
||||
const QString &setType,
|
||||
const QDate &releaseDate)
|
||||
{
|
||||
if (sets.contains(setName)) {
|
||||
return sets.value(setName);
|
||||
}
|
||||
|
||||
CardSetPtr newSet = CardSet::newInstance(setName);
|
||||
newSet->setLongName(longName);
|
||||
newSet->setSetType(setType);
|
||||
newSet->setReleaseDate(releaseDate);
|
||||
|
||||
sets.insert(setName, newSet);
|
||||
emit addSet(newSet);
|
||||
return newSet;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef CARDDATABASE_PARSER_H
|
||||
#define CARDDATABASE_PARSER_H
|
||||
|
||||
#include "../card_database.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QString>
|
||||
|
||||
#define COCKATRICE_XML_XSI_NAMESPACE "http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
||||
class ICardDatabaseParser : public QObject
|
||||
{
|
||||
public:
|
||||
~ICardDatabaseParser() override = default;
|
||||
|
||||
virtual bool getCanParseFile(const QString &name, QIODevice &device) = 0;
|
||||
virtual void parseFile(QIODevice &device) = 0;
|
||||
virtual bool saveToFile(SetNameMap sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl = "unknown",
|
||||
const QString &sourceVersion = "unknown") = 0;
|
||||
static void clearSetlist();
|
||||
|
||||
protected:
|
||||
/*
|
||||
* A cached list of the available sets, needed to cross-reference sets from cards.
|
||||
* Shared between all parsers
|
||||
*/
|
||||
static SetNameMap sets;
|
||||
|
||||
CardSetPtr internalAddSet(const QString &setName,
|
||||
const QString &longName = "",
|
||||
const QString &setType = "",
|
||||
const QDate &releaseDate = QDate());
|
||||
signals:
|
||||
virtual void addCard(CardInfoPtr card) = 0;
|
||||
virtual void addSet(CardSetPtr set) = 0;
|
||||
};
|
||||
|
||||
Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser")
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,462 @@
|
|||
#include "cockatrice_xml_3.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <version_string.h>
|
||||
|
||||
#define COCKATRICE_XML3_TAGNAME "cockatrice_carddatabase"
|
||||
#define COCKATRICE_XML3_TAGVER 3
|
||||
#define COCKATRICE_XML3_SCHEMALOCATION \
|
||||
"https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v3/cards.xsd"
|
||||
|
||||
bool CockatriceXml3Parser::getCanParseFile(const QString &fileName, QIODevice &device)
|
||||
{
|
||||
qDebug() << "[CockatriceXml3Parser] Trying to parse: " << fileName;
|
||||
|
||||
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
|
||||
qDebug() << "[CockatriceXml3Parser] Parsing failed: wrong extension";
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
if (xml.name().toString() == COCKATRICE_XML3_TAGNAME) {
|
||||
int version = xml.attributes().value("version").toString().toInt();
|
||||
if (version == COCKATRICE_XML3_TAGVER) {
|
||||
return true;
|
||||
} else {
|
||||
qDebug() << "[CockatriceXml3Parser] Parsing failed: wrong version" << version;
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
qDebug() << "[CockatriceXml3Parser] Parsing failed: wrong element tag" << xml.name();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CockatriceXml3Parser::parseFile(QIODevice &device)
|
||||
{
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto name = xml.name().toString();
|
||||
if (name == "sets") {
|
||||
loadSetsFromXml(xml);
|
||||
} else if (name == "cards") {
|
||||
loadCardsFromXml(xml);
|
||||
} else if (!name.isEmpty()) {
|
||||
qDebug() << "[CockatriceXml3Parser] Unknown item" << name << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CockatriceXml3Parser::loadSetsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto name = xml.name().toString();
|
||||
if (name == "set") {
|
||||
QString shortName, longName, setType;
|
||||
QDate releaseDate;
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
name = xml.name().toString();
|
||||
|
||||
if (name == "name") {
|
||||
shortName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (name == "longname") {
|
||||
longName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (name == "settype") {
|
||||
setType = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (name == "releasedate") {
|
||||
releaseDate =
|
||||
QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate);
|
||||
} else if (!name.isEmpty()) {
|
||||
qDebug() << "[CockatriceXml3Parser] Unknown set property" << name << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
internalAddSet(shortName, longName, setType, releaseDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString CockatriceXml3Parser::getMainCardType(QString &type)
|
||||
{
|
||||
QString result = type;
|
||||
/*
|
||||
Legendary Artifact Creature - Golem
|
||||
Instant // Instant
|
||||
*/
|
||||
|
||||
int pos;
|
||||
if ((pos = result.indexOf('-')) != -1) {
|
||||
result.remove(pos, result.length());
|
||||
}
|
||||
|
||||
if ((pos = result.indexOf("—")) != -1) {
|
||||
result.remove(pos, result.length());
|
||||
}
|
||||
|
||||
if ((pos = result.indexOf("//")) != -1) {
|
||||
result.remove(pos, result.length());
|
||||
}
|
||||
|
||||
result = result.simplified();
|
||||
/*
|
||||
Legendary Artifact Creature
|
||||
Instant
|
||||
*/
|
||||
|
||||
if ((pos = result.lastIndexOf(' ')) != -1) {
|
||||
result = result.mid(pos + 1);
|
||||
}
|
||||
/*
|
||||
Creature
|
||||
Instant
|
||||
*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (xmlName == "card") {
|
||||
QString name = QString("");
|
||||
QString text = QString("");
|
||||
QVariantHash properties = QVariantHash();
|
||||
QString colors = QString("");
|
||||
QList<CardRelation *> relatedCards, reverseRelatedCards;
|
||||
auto _sets = CardInfoPerSetMap();
|
||||
int tableRow = 0;
|
||||
bool cipt = false;
|
||||
bool isToken = false;
|
||||
bool upsideDown = false;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
xmlName = xml.name().toString();
|
||||
|
||||
// variable - assigned properties
|
||||
if (xmlName == "name") {
|
||||
name = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "text") {
|
||||
text = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "color") {
|
||||
colors.append(xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "token") {
|
||||
isToken = static_cast<bool>(xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt());
|
||||
// generic properties
|
||||
} else if (xmlName == "manacost") {
|
||||
properties.insert("manacost", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "cmc") {
|
||||
properties.insert("cmc", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "type") {
|
||||
QString type = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
properties.insert("type", type);
|
||||
properties.insert("maintype", getMainCardType(type));
|
||||
} else if (xmlName == "pt") {
|
||||
properties.insert("pt", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
} else if (xmlName == "loyalty") {
|
||||
properties.insert("loyalty", xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
// positioning info
|
||||
} else if (xmlName == "tablerow") {
|
||||
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
|
||||
} else if (xmlName == "cipt") {
|
||||
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "upsidedown") {
|
||||
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
// sets
|
||||
} else if (xmlName == "set") {
|
||||
// NOTE: attributes must be read before readElementText()
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString setName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
CardInfoPerSet setInfo(internalAddSet(setName));
|
||||
if (attrs.hasAttribute("muId")) {
|
||||
setInfo.setProperty("muid", attrs.value("muId").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("muId")) {
|
||||
setInfo.setProperty("uuid", attrs.value("uuId").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("picURL")) {
|
||||
setInfo.setProperty("picurl", attrs.value("picURL").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("num")) {
|
||||
setInfo.setProperty("num", attrs.value("num").toString());
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("rarity")) {
|
||||
setInfo.setProperty("rarity", attrs.value("rarity").toString());
|
||||
}
|
||||
_sets.insert(setName, setInfo);
|
||||
// related cards
|
||||
} else if (xmlName == "related" || xmlName == "reverse-related") {
|
||||
CardRelation::AttachType attach = CardRelation::DoesNotAttach;
|
||||
bool exclude = false;
|
||||
bool variable = false;
|
||||
int count = 1;
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
if (attrs.hasAttribute("count")) {
|
||||
if (attrs.value("count").toString().indexOf("x=") == 0) {
|
||||
variable = true;
|
||||
count = attrs.value("count").toString().remove(0, 2).toInt();
|
||||
} else if (attrs.value("count").toString().indexOf("x") == 0) {
|
||||
variable = true;
|
||||
} else {
|
||||
count = attrs.value("count").toString().toInt();
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("attach")) {
|
||||
attach = CardRelation::AttachTo;
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("exclude")) {
|
||||
exclude = true;
|
||||
}
|
||||
|
||||
auto *relation = new CardRelation(cardName, attach, exclude, variable, count);
|
||||
if (xmlName == "reverse-related") {
|
||||
reverseRelatedCards << relation;
|
||||
} else {
|
||||
relatedCards << relation;
|
||||
}
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qDebug() << "[CockatriceXml3Parser] Unknown card property" << xmlName
|
||||
<< ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
properties.insert("colors", colors);
|
||||
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
|
||||
reverseRelatedCards, _sets, cipt, tableRow, upsideDown);
|
||||
emit addCard(newCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
|
||||
{
|
||||
if (set.isNull()) {
|
||||
qDebug() << "&operator<< set is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
xml.writeStartElement("set");
|
||||
xml.writeTextElement("name", set->getShortName());
|
||||
xml.writeTextElement("longname", set->getLongName());
|
||||
xml.writeTextElement("settype", set->getSetType());
|
||||
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
|
||||
xml.writeEndElement();
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info)
|
||||
{
|
||||
if (info.isNull()) {
|
||||
qDebug() << "operator<< info is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
QString tmpString;
|
||||
|
||||
xml.writeStartElement("card");
|
||||
|
||||
// variable - assigned properties
|
||||
xml.writeTextElement("name", info->getName());
|
||||
xml.writeTextElement("text", info->getText());
|
||||
if (info->getIsToken()) {
|
||||
xml.writeTextElement("token", "1");
|
||||
}
|
||||
|
||||
// generic properties
|
||||
xml.writeTextElement("manacost", info->getProperty("manacost"));
|
||||
xml.writeTextElement("cmc", info->getProperty("cmc"));
|
||||
xml.writeTextElement("type", info->getProperty("type"));
|
||||
|
||||
int colorSize = info->getColors().size();
|
||||
for (int i = 0; i < colorSize; ++i) {
|
||||
xml.writeTextElement("color", info->getColors().at(i));
|
||||
}
|
||||
|
||||
tmpString = info->getProperty("pt");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeTextElement("pt", tmpString);
|
||||
}
|
||||
|
||||
tmpString = info->getProperty("loyalty");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeTextElement("loyalty", tmpString);
|
||||
}
|
||||
|
||||
// sets
|
||||
const CardInfoPerSetMap sets = info->getSets();
|
||||
for (CardInfoPerSet set : sets) {
|
||||
xml.writeStartElement("set");
|
||||
xml.writeAttribute("rarity", set.getProperty("rarity"));
|
||||
xml.writeAttribute("muId", set.getProperty("muid"));
|
||||
xml.writeAttribute("uuId", set.getProperty("uuid"));
|
||||
|
||||
tmpString = set.getProperty("num");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeAttribute("num", tmpString);
|
||||
}
|
||||
|
||||
tmpString = set.getProperty("picurl");
|
||||
if (!tmpString.isEmpty()) {
|
||||
xml.writeAttribute("picURL", tmpString);
|
||||
}
|
||||
|
||||
xml.writeCharacters(set.getPtr()->getShortName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
// related cards
|
||||
const QList<CardRelation *> related = info->getRelatedCards();
|
||||
for (auto i : related) {
|
||||
xml.writeStartElement("related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", "attach");
|
||||
}
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
const QList<CardRelation *> reverseRelated = info->getReverseRelatedCards();
|
||||
for (auto i : reverseRelated) {
|
||||
xml.writeStartElement("reverse-related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", "attach");
|
||||
}
|
||||
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
// positioning
|
||||
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
|
||||
if (info->getCipt()) {
|
||||
xml.writeTextElement("cipt", "1");
|
||||
}
|
||||
if (info->getUpsideDownArt()) {
|
||||
xml.writeTextElement("upsidedown", "1");
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // card
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
bool CockatriceXml3Parser::saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl,
|
||||
const QString &sourceVersion)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamWriter xml(&file);
|
||||
|
||||
xml.setAutoFormatting(true);
|
||||
xml.writeStartDocument();
|
||||
xml.writeStartElement(COCKATRICE_XML3_TAGNAME);
|
||||
xml.writeAttribute("version", QString::number(COCKATRICE_XML3_TAGVER));
|
||||
xml.writeAttribute("xmlns:xsi", COCKATRICE_XML_XSI_NAMESPACE);
|
||||
xml.writeAttribute("xsi:schemaLocation", COCKATRICE_XML3_SCHEMALOCATION);
|
||||
|
||||
xml.writeStartElement("info");
|
||||
xml.writeTextElement("author", QCoreApplication::applicationName() + QString(" %1").arg(VERSION_STRING));
|
||||
xml.writeTextElement("createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
xml.writeTextElement("sourceUrl", sourceUrl);
|
||||
xml.writeTextElement("sourceVersion", sourceVersion);
|
||||
xml.writeEndElement();
|
||||
|
||||
if (_sets.count() > 0) {
|
||||
xml.writeStartElement("sets");
|
||||
for (CardSetPtr set : _sets) {
|
||||
xml << set;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
if (cards.count() > 0) {
|
||||
xml.writeStartElement("cards");
|
||||
for (CardInfoPtr card : cards) {
|
||||
xml << card;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // cockatrice_carddatabase
|
||||
xml.writeEndDocument();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef COCKATRICE_XML3_H
|
||||
#define COCKATRICE_XML3_H
|
||||
|
||||
#include "card_database_parser.h"
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
class CockatriceXml3Parser : public ICardDatabaseParser
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(ICardDatabaseParser)
|
||||
public:
|
||||
CockatriceXml3Parser() = default;
|
||||
~CockatriceXml3Parser() override = default;
|
||||
bool getCanParseFile(const QString &name, QIODevice &device) override;
|
||||
void parseFile(QIODevice &device) override;
|
||||
bool saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl = "unknown",
|
||||
const QString &sourceVersion = "unknown") override;
|
||||
|
||||
private:
|
||||
void loadCardsFromXml(QXmlStreamReader &xml);
|
||||
void loadSetsFromXml(QXmlStreamReader &xml);
|
||||
QString getMainCardType(QString &type);
|
||||
signals:
|
||||
void addCard(CardInfoPtr card) override;
|
||||
void addSet(CardSetPtr set) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,406 @@
|
|||
#include "cockatrice_xml_4.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <version_string.h>
|
||||
|
||||
#define COCKATRICE_XML4_TAGNAME "cockatrice_carddatabase"
|
||||
#define COCKATRICE_XML4_TAGVER 4
|
||||
#define COCKATRICE_XML4_SCHEMALOCATION \
|
||||
"https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v4/cards.xsd"
|
||||
|
||||
bool CockatriceXml4Parser::getCanParseFile(const QString &fileName, QIODevice &device)
|
||||
{
|
||||
qDebug() << "[CockatriceXml4Parser] Trying to parse: " << fileName;
|
||||
|
||||
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
|
||||
qDebug() << "[CockatriceXml4Parser] Parsing failed: wrong extension";
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
if (xml.name().toString() == COCKATRICE_XML4_TAGNAME) {
|
||||
int version = xml.attributes().value("version").toString().toInt();
|
||||
if (version == COCKATRICE_XML4_TAGVER) {
|
||||
return true;
|
||||
} else {
|
||||
qDebug() << "[CockatriceXml4Parser] Parsing failed: wrong version" << version;
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
qDebug() << "[CockatriceXml4Parser] Parsing failed: wrong element tag" << xml.name();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CockatriceXml4Parser::parseFile(QIODevice &device)
|
||||
{
|
||||
QXmlStreamReader xml(&device);
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (xmlName == "sets") {
|
||||
loadSetsFromXml(xml);
|
||||
} else if (xmlName == "cards") {
|
||||
loadCardsFromXml(xml);
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qDebug() << "[CockatriceXml4Parser] Unknown item" << xmlName << ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (xmlName == "set") {
|
||||
QString shortName, longName, setType;
|
||||
QDate releaseDate;
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
xmlName = xml.name().toString();
|
||||
|
||||
if (xmlName == "name") {
|
||||
shortName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "longname") {
|
||||
longName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "settype") {
|
||||
setType = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "releasedate") {
|
||||
releaseDate =
|
||||
QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate);
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qDebug() << "[CockatriceXml4Parser] Unknown set property" << xmlName
|
||||
<< ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
internalAddSet(shortName, longName, setType, releaseDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariantHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
QVariantHash properties = QVariantHash();
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
if (!xmlName.isEmpty()) {
|
||||
properties.insert(xmlName, xml.readElementText(QXmlStreamReader::IncludeChildElements));
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto xmlName = xml.name().toString();
|
||||
|
||||
if (xmlName == "card") {
|
||||
QString name = QString("");
|
||||
QString text = QString("");
|
||||
QVariantHash properties = QVariantHash();
|
||||
QList<CardRelation *> relatedCards, reverseRelatedCards;
|
||||
auto _sets = CardInfoPerSetMap();
|
||||
int tableRow = 0;
|
||||
bool cipt = false;
|
||||
bool isToken = false;
|
||||
bool upsideDown = false;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
xmlName = xml.name().toString();
|
||||
|
||||
// variable - assigned properties
|
||||
if (xmlName == "name") {
|
||||
name = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "text") {
|
||||
text = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
} else if (xmlName == "token") {
|
||||
isToken = static_cast<bool>(xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt());
|
||||
// generic properties
|
||||
} else if (xmlName == "prop") {
|
||||
properties = loadCardPropertiesFromXml(xml);
|
||||
// positioning info
|
||||
} else if (xmlName == "tablerow") {
|
||||
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
|
||||
} else if (xmlName == "cipt") {
|
||||
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
} else if (xmlName == "upsidedown") {
|
||||
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
|
||||
// sets
|
||||
} else if (xmlName == "set") {
|
||||
// NOTE: attributes but be read before readElementText()
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString setName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
auto set = internalAddSet(setName);
|
||||
if (set->getEnabled()) {
|
||||
CardInfoPerSet setInfo(set);
|
||||
for (QXmlStreamAttribute attr : attrs) {
|
||||
QString attrName = attr.name().toString();
|
||||
if (attrName == "picURL")
|
||||
attrName = "picurl";
|
||||
setInfo.setProperty(attrName, attr.value().toString());
|
||||
}
|
||||
_sets.insert(setName, setInfo);
|
||||
}
|
||||
// related cards
|
||||
} else if (xmlName == "related" || xmlName == "reverse-related") {
|
||||
CardRelation::AttachType attachType = CardRelation::DoesNotAttach;
|
||||
bool exclude = false;
|
||||
bool variable = false;
|
||||
bool persistent = false;
|
||||
int count = 1;
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
if (attrs.hasAttribute("count")) {
|
||||
if (attrs.value("count").toString().indexOf("x=") == 0) {
|
||||
variable = true;
|
||||
count = attrs.value("count").toString().remove(0, 2).toInt();
|
||||
} else if (attrs.value("count").toString().indexOf("x") == 0) {
|
||||
variable = true;
|
||||
} else {
|
||||
count = attrs.value("count").toString().toInt();
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("attach")) {
|
||||
attachType = attrs.value("attach").toString() == "transform" ? CardRelation::TransformInto
|
||||
: CardRelation::AttachTo;
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("exclude")) {
|
||||
exclude = true;
|
||||
}
|
||||
|
||||
if (attrs.hasAttribute("persistent")) {
|
||||
persistent = true;
|
||||
}
|
||||
|
||||
auto *relation = new CardRelation(cardName, attachType, exclude, variable, count, persistent);
|
||||
if (xmlName == "reverse-related") {
|
||||
reverseRelatedCards << relation;
|
||||
} else {
|
||||
relatedCards << relation;
|
||||
}
|
||||
} else if (!xmlName.isEmpty()) {
|
||||
qDebug() << "[CockatriceXml4Parser] Unknown card property" << xmlName
|
||||
<< ", trying to continue anyway";
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
|
||||
reverseRelatedCards, _sets, cipt, tableRow, upsideDown);
|
||||
emit addCard(newCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
|
||||
{
|
||||
if (set.isNull()) {
|
||||
qDebug() << "&operator<< set is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
xml.writeStartElement("set");
|
||||
xml.writeTextElement("name", set->getShortName());
|
||||
xml.writeTextElement("longname", set->getLongName());
|
||||
xml.writeTextElement("settype", set->getSetType());
|
||||
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
|
||||
xml.writeEndElement();
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info)
|
||||
{
|
||||
if (info.isNull()) {
|
||||
qDebug() << "operator<< info is nullptr";
|
||||
return xml;
|
||||
}
|
||||
|
||||
QString tmpString;
|
||||
|
||||
xml.writeStartElement("card");
|
||||
|
||||
// variable - assigned properties
|
||||
xml.writeTextElement("name", info->getName());
|
||||
xml.writeTextElement("text", info->getText());
|
||||
if (info->getIsToken()) {
|
||||
xml.writeTextElement("token", "1");
|
||||
}
|
||||
|
||||
// generic properties
|
||||
xml.writeStartElement("prop");
|
||||
for (QString propName : info->getProperties()) {
|
||||
xml.writeTextElement(propName, info->getProperty(propName));
|
||||
}
|
||||
xml.writeEndElement();
|
||||
|
||||
// sets
|
||||
for (CardInfoPerSet set : info->getSets()) {
|
||||
xml.writeStartElement("set");
|
||||
for (QString propName : set.getProperties()) {
|
||||
xml.writeAttribute(propName, set.getProperty(propName));
|
||||
}
|
||||
|
||||
xml.writeCharacters(set.getPtr()->getShortName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
// related cards
|
||||
const QList<CardRelation *> related = info->getRelatedCards();
|
||||
for (auto i : related) {
|
||||
xml.writeStartElement("related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", i->getAttachTypeAsString());
|
||||
}
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
if (i->getIsPersistent()) {
|
||||
xml.writeAttribute("persistent", "persistent");
|
||||
}
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
const QList<CardRelation *> reverseRelated = info->getReverseRelatedCards();
|
||||
for (auto i : reverseRelated) {
|
||||
xml.writeStartElement("reverse-related");
|
||||
if (i->getDoesAttach()) {
|
||||
xml.writeAttribute("attach", i->getAttachTypeAsString());
|
||||
}
|
||||
|
||||
if (i->getIsCreateAllExclusion()) {
|
||||
xml.writeAttribute("exclude", "exclude");
|
||||
}
|
||||
|
||||
if (i->getIsPersistent()) {
|
||||
xml.writeAttribute("persistent", "persistent");
|
||||
}
|
||||
if (i->getIsVariable()) {
|
||||
if (1 == i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", "x");
|
||||
} else {
|
||||
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
|
||||
}
|
||||
} else if (1 != i->getDefaultCount()) {
|
||||
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
|
||||
}
|
||||
xml.writeCharacters(i->getName());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
// positioning
|
||||
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
|
||||
if (info->getCipt()) {
|
||||
xml.writeTextElement("cipt", "1");
|
||||
}
|
||||
if (info->getUpsideDownArt()) {
|
||||
xml.writeTextElement("upsidedown", "1");
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // card
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
bool CockatriceXml4Parser::saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl,
|
||||
const QString &sourceVersion)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamWriter xml(&file);
|
||||
|
||||
xml.setAutoFormatting(true);
|
||||
xml.writeStartDocument();
|
||||
xml.writeStartElement(COCKATRICE_XML4_TAGNAME);
|
||||
xml.writeAttribute("version", QString::number(COCKATRICE_XML4_TAGVER));
|
||||
xml.writeAttribute("xmlns:xsi", COCKATRICE_XML_XSI_NAMESPACE);
|
||||
xml.writeAttribute("xsi:schemaLocation", COCKATRICE_XML4_SCHEMALOCATION);
|
||||
|
||||
xml.writeStartElement("info");
|
||||
xml.writeTextElement("author", QCoreApplication::applicationName() + QString(" %1").arg(VERSION_STRING));
|
||||
xml.writeTextElement("createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
xml.writeTextElement("sourceUrl", sourceUrl);
|
||||
xml.writeTextElement("sourceVersion", sourceVersion);
|
||||
xml.writeEndElement();
|
||||
|
||||
if (_sets.count() > 0) {
|
||||
xml.writeStartElement("sets");
|
||||
for (CardSetPtr set : _sets) {
|
||||
xml << set;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
if (cards.count() > 0) {
|
||||
xml.writeStartElement("cards");
|
||||
for (CardInfoPtr card : cards) {
|
||||
xml << card;
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
xml.writeEndElement(); // cockatrice_carddatabase
|
||||
xml.writeEndDocument();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef COCKATRICE_XML4_H
|
||||
#define COCKATRICE_XML4_H
|
||||
|
||||
#include "card_database_parser.h"
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
class CockatriceXml4Parser : public ICardDatabaseParser
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(ICardDatabaseParser)
|
||||
public:
|
||||
CockatriceXml4Parser() = default;
|
||||
~CockatriceXml4Parser() override = default;
|
||||
bool getCanParseFile(const QString &name, QIODevice &device) override;
|
||||
void parseFile(QIODevice &device) override;
|
||||
bool saveToFile(SetNameMap _sets,
|
||||
CardNameMap cards,
|
||||
const QString &fileName,
|
||||
const QString &sourceUrl = "unknown",
|
||||
const QString &sourceVersion = "unknown") override;
|
||||
|
||||
private:
|
||||
QVariantHash loadCardPropertiesFromXml(QXmlStreamReader &xml);
|
||||
void loadCardsFromXml(QXmlStreamReader &xml);
|
||||
void loadSetsFromXml(QXmlStreamReader &xml);
|
||||
signals:
|
||||
void addCard(CardInfoPtr card) override;
|
||||
void addSet(CardSetPtr set) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
112
cockatrice/src/game/cards/card_drag_item.cpp
Normal file
112
cockatrice/src/game/cards/card_drag_item.cpp
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
#include "card_drag_item.h"
|
||||
|
||||
#include "../game_scene.h"
|
||||
#include "../zones/card_zone.h"
|
||||
#include "../zones/table_zone.h"
|
||||
#include "../zones/view_zone.h"
|
||||
#include "card_item.h"
|
||||
|
||||
#include <QCursor>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
CardDragItem::CardDragItem(CardItem *_item,
|
||||
int _id,
|
||||
const QPointF &_hotSpot,
|
||||
bool _faceDown,
|
||||
AbstractCardDragItem *parentDrag)
|
||||
: AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), 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;
|
||||
if (!cursorZone)
|
||||
return;
|
||||
currentZone = cursorZone;
|
||||
|
||||
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;
|
||||
CardZone *startZone = static_cast<CardItem *>(item)->getZone();
|
||||
if (currentZone && !(static_cast<CardItem *>(item)->getAttachedTo() && (startZone == currentZone)) && !occupied) {
|
||||
dragItemList.append(this);
|
||||
for (int i = 0; i < childDrags.size(); i++) {
|
||||
CardDragItem *c = static_cast<CardDragItem *>(childDrags[i]);
|
||||
if (!(static_cast<CardItem *>(c->item)->getAttachedTo() && (startZone == currentZone)) && !c->occupied)
|
||||
dragItemList.append(c);
|
||||
sc->removeItem(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentZone)
|
||||
currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint());
|
||||
|
||||
event->accept();
|
||||
}
|
||||
38
cockatrice/src/game/cards/card_drag_item.h
Normal file
38
cockatrice/src/game/cards/card_drag_item.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef CARDDRAGITEM_H
|
||||
#define CARDDRAGITEM_H
|
||||
|
||||
#include "abstract_card_drag_item.h"
|
||||
|
||||
class CardItem;
|
||||
|
||||
class CardDragItem : public AbstractCardDragItem
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
int id;
|
||||
bool faceDown;
|
||||
bool occupied;
|
||||
CardZone *currentZone;
|
||||
|
||||
public:
|
||||
CardDragItem(CardItem *_item,
|
||||
int _id,
|
||||
const QPointF &_hotSpot,
|
||||
bool _faceDown,
|
||||
AbstractCardDragItem *parentDrag = 0);
|
||||
int getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
bool getFaceDown() const
|
||||
{
|
||||
return faceDown;
|
||||
}
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
void updatePosition(const QPointF &cursorScenePos);
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
|
||||
};
|
||||
|
||||
#endif
|
||||
123
cockatrice/src/game/cards/card_frame.cpp
Normal file
123
cockatrice/src/game/cards/card_frame.cpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
#include "card_frame.h"
|
||||
|
||||
#include "../../main.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "card_info_picture.h"
|
||||
#include "card_info_text.h"
|
||||
#include "card_item.h"
|
||||
|
||||
#include <QSplitter>
|
||||
#include <QVBoxLayout>
|
||||
#include <utility>
|
||||
|
||||
CardFrame::CardFrame(const QString &cardName, QWidget *parent) : QTabWidget(parent), info(nullptr), cardTextOnly(false)
|
||||
{
|
||||
setContentsMargins(3, 3, 3, 3);
|
||||
pic = new CardInfoPicture();
|
||||
pic->setObjectName("pic");
|
||||
text = new CardInfoText();
|
||||
text->setObjectName("text");
|
||||
connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &)));
|
||||
|
||||
tab1 = new QWidget(this);
|
||||
tab2 = new QWidget(this);
|
||||
tab3 = new QWidget(this);
|
||||
|
||||
tab1->setObjectName("tab1");
|
||||
tab2->setObjectName("tab2");
|
||||
tab3->setObjectName("tab3");
|
||||
|
||||
insertTab(ImageOnlyView, tab1, QString());
|
||||
insertTab(TextOnlyView, tab2, QString());
|
||||
insertTab(ImageAndTextView, tab3, QString());
|
||||
connect(this, SIGNAL(currentChanged(int)), this, SLOT(setViewMode(int)));
|
||||
|
||||
tab1Layout = new QVBoxLayout();
|
||||
tab1Layout->setObjectName("tab1Layout");
|
||||
tab1Layout->setContentsMargins(0, 0, 0, 0);
|
||||
tab1Layout->setSpacing(0);
|
||||
tab1->setLayout(tab1Layout);
|
||||
|
||||
tab2Layout = new QVBoxLayout();
|
||||
tab2Layout->setObjectName("tab2Layout");
|
||||
tab2Layout->setContentsMargins(0, 0, 0, 0);
|
||||
tab2Layout->setSpacing(0);
|
||||
tab2->setLayout(tab2Layout);
|
||||
|
||||
splitter = new QSplitter();
|
||||
splitter->setObjectName("splitter");
|
||||
splitter->setOrientation(Qt::Vertical);
|
||||
|
||||
tab3Layout = new QVBoxLayout();
|
||||
tab3Layout->setObjectName("tab3Layout");
|
||||
tab3Layout->setContentsMargins(0, 0, 0, 0);
|
||||
tab3Layout->setSpacing(0);
|
||||
tab3Layout->addWidget(splitter);
|
||||
tab3->setLayout(tab3Layout);
|
||||
|
||||
setViewMode(SettingsCache::instance().getCardInfoViewMode());
|
||||
|
||||
setCard(db->getCard(cardName));
|
||||
}
|
||||
|
||||
void CardFrame::retranslateUi()
|
||||
{
|
||||
setTabText(ImageOnlyView, tr("Image"));
|
||||
setTabText(TextOnlyView, tr("Description"));
|
||||
setTabText(ImageAndTextView, tr("Both"));
|
||||
}
|
||||
|
||||
void CardFrame::setViewMode(int mode)
|
||||
{
|
||||
if (currentIndex() != mode)
|
||||
setCurrentIndex(mode);
|
||||
|
||||
switch (mode) {
|
||||
case ImageOnlyView:
|
||||
case TextOnlyView:
|
||||
tab1Layout->addWidget(pic);
|
||||
tab2Layout->addWidget(text);
|
||||
break;
|
||||
case ImageAndTextView:
|
||||
splitter->addWidget(pic);
|
||||
splitter->addWidget(text);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SettingsCache::instance().setCardInfoViewMode(mode);
|
||||
}
|
||||
|
||||
void CardFrame::setCard(CardInfoPtr card)
|
||||
{
|
||||
if (info) {
|
||||
disconnect(info.data(), nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
info = std::move(card);
|
||||
|
||||
if (info) {
|
||||
connect(info.data(), SIGNAL(destroyed()), this, SLOT(clearCard()));
|
||||
}
|
||||
|
||||
text->setCard(info);
|
||||
pic->setCard(info);
|
||||
}
|
||||
|
||||
void CardFrame::setCard(const QString &cardName)
|
||||
{
|
||||
setCard(db->guessCard(cardName));
|
||||
}
|
||||
|
||||
void CardFrame::setCard(AbstractCardItem *card)
|
||||
{
|
||||
if (card) {
|
||||
setCard(card->getInfo());
|
||||
}
|
||||
}
|
||||
|
||||
void CardFrame::clearCard()
|
||||
{
|
||||
setCard((CardInfoPtr) nullptr);
|
||||
}
|
||||
44
cockatrice/src/game/cards/card_frame.h
Normal file
44
cockatrice/src/game/cards/card_frame.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef CARDFRAME_H
|
||||
#define CARDFRAME_H
|
||||
|
||||
#include "card_database.h"
|
||||
|
||||
#include <QTabWidget>
|
||||
|
||||
class AbstractCardItem;
|
||||
class CardInfoPicture;
|
||||
class CardInfoText;
|
||||
class QVBoxLayout;
|
||||
class QSplitter;
|
||||
|
||||
class CardFrame : public QTabWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
CardInfoPtr info;
|
||||
CardInfoPicture *pic;
|
||||
CardInfoText *text;
|
||||
bool cardTextOnly;
|
||||
QWidget *tab1, *tab2, *tab3;
|
||||
QVBoxLayout *tab1Layout, *tab2Layout, *tab3Layout;
|
||||
QSplitter *splitter;
|
||||
|
||||
public:
|
||||
enum ViewMode
|
||||
{
|
||||
ImageOnlyView,
|
||||
TextOnlyView,
|
||||
ImageAndTextView
|
||||
};
|
||||
explicit CardFrame(const QString &cardName = QString(), QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
|
||||
public slots:
|
||||
void setCard(CardInfoPtr card);
|
||||
void setCard(const QString &cardName);
|
||||
void setCard(AbstractCardItem *card);
|
||||
void clearCard();
|
||||
void setViewMode(int mode);
|
||||
};
|
||||
|
||||
#endif
|
||||
66
cockatrice/src/game/cards/card_info_picture.cpp
Normal file
66
cockatrice/src/game/cards/card_info_picture.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#include "card_info_picture.h"
|
||||
|
||||
#include "../../client/ui/picture_loader.h"
|
||||
#include "../../main.h"
|
||||
#include "card_item.h"
|
||||
|
||||
#include <QStylePainter>
|
||||
#include <QWidget>
|
||||
|
||||
CardInfoPicture::CardInfoPicture(QWidget *parent) : QWidget(parent), info(nullptr), pixmapDirty(true)
|
||||
{
|
||||
setMinimumHeight(100);
|
||||
}
|
||||
|
||||
void CardInfoPicture::setCard(CardInfoPtr card)
|
||||
{
|
||||
if (info) {
|
||||
disconnect(info.data(), nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
info = card;
|
||||
|
||||
if (info) {
|
||||
connect(info.data(), SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap()));
|
||||
}
|
||||
|
||||
updatePixmap();
|
||||
}
|
||||
|
||||
void CardInfoPicture::resizeEvent(QResizeEvent *)
|
||||
{
|
||||
updatePixmap();
|
||||
}
|
||||
|
||||
void CardInfoPicture::updatePixmap()
|
||||
{
|
||||
pixmapDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
void CardInfoPicture::loadPixmap()
|
||||
{
|
||||
if (info)
|
||||
PictureLoader::getPixmap(resizedPixmap, info, size());
|
||||
else
|
||||
PictureLoader::getCardBackPixmap(resizedPixmap, size());
|
||||
}
|
||||
|
||||
void CardInfoPicture::paintEvent(QPaintEvent *)
|
||||
{
|
||||
if (width() == 0 || height() == 0)
|
||||
return;
|
||||
|
||||
if (pixmapDirty)
|
||||
loadPixmap();
|
||||
|
||||
QSize scaledSize = resizedPixmap.size().scaled(size(), Qt::KeepAspectRatio);
|
||||
QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2};
|
||||
qreal radius = 0.05 * scaledSize.width();
|
||||
|
||||
QStylePainter painter(this);
|
||||
QPainterPath shape;
|
||||
shape.addRoundedRect(QRect(topLeft, scaledSize), radius, radius);
|
||||
painter.setClipPath(shape);
|
||||
painter.drawItemPixmap(QRect(topLeft, scaledSize), Qt::AlignCenter, resizedPixmap);
|
||||
}
|
||||
31
cockatrice/src/game/cards/card_info_picture.h
Normal file
31
cockatrice/src/game/cards/card_info_picture.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef CARDINFOPICTURE_H
|
||||
#define CARDINFOPICTURE_H
|
||||
|
||||
#include "card_database.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class AbstractCardItem;
|
||||
|
||||
class CardInfoPicture : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CardInfoPtr info;
|
||||
QPixmap resizedPixmap;
|
||||
bool pixmapDirty;
|
||||
|
||||
public:
|
||||
CardInfoPicture(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event);
|
||||
void paintEvent(QPaintEvent *);
|
||||
void loadPixmap();
|
||||
public slots:
|
||||
void setCard(CardInfoPtr card);
|
||||
void updatePixmap();
|
||||
};
|
||||
|
||||
#endif
|
||||
81
cockatrice/src/game/cards/card_info_text.cpp
Normal file
81
cockatrice/src/game/cards/card_info_text.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#include "card_info_text.h"
|
||||
|
||||
#include "../../game/game_specific_terms.h"
|
||||
#include "../../main.h"
|
||||
#include "card_item.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QTextEdit>
|
||||
|
||||
CardInfoText::CardInfoText(QWidget *parent) : QFrame(parent), info(nullptr)
|
||||
{
|
||||
nameLabel = new QLabel;
|
||||
nameLabel->setOpenExternalLinks(false);
|
||||
nameLabel->setWordWrap(true);
|
||||
connect(nameLabel, SIGNAL(linkActivated(const QString &)), this, SIGNAL(linkActivated(const QString &)));
|
||||
|
||||
textLabel = new QTextEdit();
|
||||
textLabel->setReadOnly(true);
|
||||
|
||||
auto *grid = new QGridLayout(this);
|
||||
grid->addWidget(nameLabel, 0, 0);
|
||||
grid->addWidget(textLabel, 1, 0, -1, 2);
|
||||
grid->setRowStretch(1, 1);
|
||||
grid->setColumnStretch(1, 1);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void CardInfoText::setCard(CardInfoPtr card)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
nameLabel->setText("");
|
||||
textLabel->setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = "<table width=\"100%\" border=0 cellspacing=0 cellpadding=0>";
|
||||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
|
||||
.arg(tr("Name:"), card->getName().toHtmlEscaped());
|
||||
|
||||
QStringList cardProps = card->getProperties();
|
||||
for (const QString &key : cardProps) {
|
||||
if (key.contains("-"))
|
||||
continue;
|
||||
QString keyText = Mtg::getNicePropertyName(key).toHtmlEscaped() + ":";
|
||||
text +=
|
||||
QString("<tr><td>%1</td><td></td><td>%2</td></tr>").arg(keyText, card->getProperty(key).toHtmlEscaped());
|
||||
}
|
||||
|
||||
auto relatedCards = card->getAllRelatedCards();
|
||||
if (!relatedCards.empty()) {
|
||||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>").arg(tr("Related cards:"));
|
||||
|
||||
for (auto *relatedCard : relatedCards) {
|
||||
QString tmp = relatedCard->getName().toHtmlEscaped();
|
||||
text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>";
|
||||
}
|
||||
|
||||
text += "</td></tr>";
|
||||
}
|
||||
|
||||
text += "</table>";
|
||||
nameLabel->setText(text);
|
||||
textLabel->setText(card->getText());
|
||||
}
|
||||
|
||||
void CardInfoText::setInvalidCardName(const QString &cardName)
|
||||
{
|
||||
nameLabel->setText(tr("Unknown card:") + " " + cardName);
|
||||
textLabel->setText("");
|
||||
}
|
||||
|
||||
void CardInfoText::retranslateUi()
|
||||
{
|
||||
/*
|
||||
* There's no way we can really translate the text currently being rendered.
|
||||
* The best we can do is invalidate the current text.
|
||||
*/
|
||||
setInvalidCardName("");
|
||||
}
|
||||
30
cockatrice/src/game/cards/card_info_text.h
Normal file
30
cockatrice/src/game/cards/card_info_text.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef CARDINFOTEXT_H
|
||||
#define CARDINFOTEXT_H
|
||||
|
||||
#include "card_database.h"
|
||||
|
||||
#include <QFrame>
|
||||
class QLabel;
|
||||
class QTextEdit;
|
||||
|
||||
class CardInfoText : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QLabel *nameLabel;
|
||||
QTextEdit *textLabel;
|
||||
CardInfoPtr info;
|
||||
|
||||
public:
|
||||
explicit CardInfoText(QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
void setInvalidCardName(const QString &cardName);
|
||||
|
||||
signals:
|
||||
void linkActivated(const QString &link);
|
||||
public slots:
|
||||
void setCard(CardInfoPtr card);
|
||||
};
|
||||
|
||||
#endif
|
||||
73
cockatrice/src/game/cards/card_info_widget.cpp
Normal file
73
cockatrice/src/game/cards/card_info_widget.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#include "card_info_widget.h"
|
||||
|
||||
#include "../../main.h"
|
||||
#include "card_info_picture.h"
|
||||
#include "card_info_text.h"
|
||||
#include "card_item.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
#include <QVBoxLayout>
|
||||
#include <utility>
|
||||
|
||||
CardInfoWidget::CardInfoWidget(const QString &cardName, QWidget *parent, Qt::WindowFlags flags)
|
||||
: QFrame(parent, flags), aspectRatio((qreal)CARD_HEIGHT / (qreal)CARD_WIDTH), info(nullptr)
|
||||
{
|
||||
setContentsMargins(3, 3, 3, 3);
|
||||
pic = new CardInfoPicture();
|
||||
pic->setObjectName("pic");
|
||||
text = new CardInfoText();
|
||||
text->setObjectName("text");
|
||||
connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &)));
|
||||
|
||||
auto *layout = new QVBoxLayout();
|
||||
layout->setObjectName("layout");
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(pic, 0, Qt::AlignCenter);
|
||||
layout->addWidget(text, 0, Qt::AlignCenter);
|
||||
setLayout(layout);
|
||||
|
||||
setFrameStyle(QFrame::Panel | QFrame::Raised);
|
||||
|
||||
int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3;
|
||||
int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio);
|
||||
pic->setFixedWidth(pixmapWidth);
|
||||
pic->setFixedHeight(pixmapHeight);
|
||||
setFixedWidth(pixmapWidth + 150);
|
||||
|
||||
setCard(cardName);
|
||||
|
||||
// ensure our parent gets a valid size to position us correctly
|
||||
resize(width(), sizeHint().height());
|
||||
}
|
||||
|
||||
void CardInfoWidget::setCard(CardInfoPtr card)
|
||||
{
|
||||
if (info)
|
||||
disconnect(info.data(), nullptr, this, nullptr);
|
||||
info = std::move(card);
|
||||
if (info)
|
||||
connect(info.data(), SIGNAL(destroyed()), this, SLOT(clear()));
|
||||
|
||||
text->setCard(info);
|
||||
pic->setCard(info);
|
||||
}
|
||||
|
||||
void CardInfoWidget::setCard(const QString &cardName)
|
||||
{
|
||||
setCard(db->guessCard(cardName));
|
||||
if (info == nullptr) {
|
||||
text->setInvalidCardName(cardName);
|
||||
}
|
||||
}
|
||||
|
||||
void CardInfoWidget::setCard(AbstractCardItem *card)
|
||||
{
|
||||
setCard(card->getInfo());
|
||||
}
|
||||
|
||||
void CardInfoWidget::clear()
|
||||
{
|
||||
setCard((CardInfoPtr) nullptr);
|
||||
}
|
||||
36
cockatrice/src/game/cards/card_info_widget.h
Normal file
36
cockatrice/src/game/cards/card_info_widget.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef CARDINFOWIDGET_H
|
||||
#define CARDINFOWIDGET_H
|
||||
|
||||
#include "card_database.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFrame>
|
||||
#include <QStringList>
|
||||
|
||||
class CardInfoPicture;
|
||||
class CardInfoText;
|
||||
class AbstractCardItem;
|
||||
|
||||
class CardInfoWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
qreal aspectRatio;
|
||||
CardInfoPtr info;
|
||||
CardInfoPicture *pic;
|
||||
CardInfoText *text;
|
||||
|
||||
public:
|
||||
explicit CardInfoWidget(const QString &cardName, QWidget *parent = nullptr, Qt::WindowFlags f = {});
|
||||
|
||||
public slots:
|
||||
void setCard(CardInfoPtr card);
|
||||
void setCard(const QString &cardName);
|
||||
void setCard(AbstractCardItem *card);
|
||||
|
||||
private slots:
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif
|
||||
468
cockatrice/src/game/cards/card_item.cpp
Normal file
468
cockatrice/src/game/cards/card_item.cpp
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
#include "card_item.h"
|
||||
|
||||
#include "../../client/tabs/tab_game.h"
|
||||
#include "../../main.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../board/arrow_item.h"
|
||||
#include "../game_scene.h"
|
||||
#include "../player/player.h"
|
||||
#include "../zones/card_zone.h"
|
||||
#include "../zones/table_zone.h"
|
||||
#include "../zones/view_zone.h"
|
||||
#include "card_database.h"
|
||||
#include "card_drag_item.h"
|
||||
#include "pb/serverinfo_card.pb.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
|
||||
CardItem::CardItem(Player *_owner,
|
||||
const QString &_name,
|
||||
int _cardid,
|
||||
bool _revealedCard,
|
||||
QGraphicsItem *parent,
|
||||
CardZone *_zone)
|
||||
: AbstractCardItem(_name, _owner, _cardid, parent), zone(_zone), revealedCard(_revealedCard), attacking(false),
|
||||
destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
|
||||
{
|
||||
owner->addCard(this);
|
||||
|
||||
cardMenu = new QMenu;
|
||||
ptMenu = new QMenu;
|
||||
moveMenu = new QMenu;
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
CardItem::~CardItem()
|
||||
{
|
||||
prepareDelete();
|
||||
|
||||
if (scene())
|
||||
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
|
||||
|
||||
delete cardMenu;
|
||||
delete ptMenu;
|
||||
delete moveMenu;
|
||||
|
||||
deleteDragItem();
|
||||
}
|
||||
|
||||
void CardItem::prepareDelete()
|
||||
{
|
||||
if (owner != nullptr) {
|
||||
if (owner->getCardMenu() == cardMenu) {
|
||||
owner->setCardMenu(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 (attachedTo != nullptr) {
|
||||
attachedTo->removeAttachedCard(this);
|
||||
attachedTo = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CardItem::deleteLater()
|
||||
{
|
||||
prepareDelete();
|
||||
AbstractCardItem::deleteLater();
|
||||
}
|
||||
|
||||
void CardItem::setZone(CardZone *_zone)
|
||||
{
|
||||
zone = _zone;
|
||||
}
|
||||
|
||||
void CardItem::retranslateUi()
|
||||
{
|
||||
moveMenu->setTitle(tr("&Move to"));
|
||||
ptMenu->setTitle(tr("&Power / toughness"));
|
||||
}
|
||||
|
||||
void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
|
||||
{
|
||||
painter->save();
|
||||
AbstractCardItem::paint(painter, option, widget);
|
||||
|
||||
int i = 0;
|
||||
QMapIterator<int, int> counterIterator(counters);
|
||||
while (counterIterator.hasNext()) {
|
||||
counterIterator.next();
|
||||
QColor _color;
|
||||
_color.setHsv(counterIterator.key() * 60, 150, 255);
|
||||
|
||||
paintNumberEllipse(counterIterator.value(), 14, _color, i, counters.size(), painter);
|
||||
++i;
|
||||
}
|
||||
|
||||
QSizeF translatedSize = getTranslatedSize(painter);
|
||||
qreal scaleFactor = translatedSize.width() / boundingRect().width();
|
||||
|
||||
if (!pt.isEmpty()) {
|
||||
painter->save();
|
||||
transformPainter(painter, translatedSize, tapAngle);
|
||||
|
||||
if (!getFaceDown() && info && pt == info->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, pt);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
if (!annotation.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, annotation);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
if (getBeingPointedAt()) {
|
||||
painter->fillPath(shape(), QBrush(QColor(255, 0, 0, 100)));
|
||||
}
|
||||
|
||||
if (doesntUntap) {
|
||||
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)
|
||||
{
|
||||
attacking = _attacking;
|
||||
update();
|
||||
}
|
||||
|
||||
void CardItem::setCounter(int _id, int _value)
|
||||
{
|
||||
if (_value)
|
||||
counters.insert(_id, _value);
|
||||
else
|
||||
counters.remove(_id);
|
||||
update();
|
||||
}
|
||||
|
||||
void CardItem::setAnnotation(const QString &_annotation)
|
||||
{
|
||||
annotation = _annotation;
|
||||
update();
|
||||
}
|
||||
|
||||
void CardItem::setDoesntUntap(bool _doesntUntap)
|
||||
{
|
||||
doesntUntap = _doesntUntap;
|
||||
update();
|
||||
}
|
||||
|
||||
void CardItem::setPT(const QString &_pt)
|
||||
{
|
||||
pt = _pt;
|
||||
update();
|
||||
}
|
||||
|
||||
void CardItem::setAttachedTo(CardItem *_attachedTo)
|
||||
{
|
||||
if (attachedTo != nullptr) {
|
||||
attachedTo->removeAttachedCard(this);
|
||||
}
|
||||
|
||||
gridPoint.setX(-1);
|
||||
attachedTo = _attachedTo;
|
||||
if (attachedTo != nullptr) {
|
||||
setParentItem(attachedTo->getZone());
|
||||
attachedTo->addAttachedCard(this);
|
||||
if (zone != attachedTo->getZone()) {
|
||||
attachedTo->getZone()->reorganizeCards();
|
||||
}
|
||||
} else {
|
||||
setParentItem(zone);
|
||||
}
|
||||
|
||||
if (zone != nullptr) {
|
||||
zone->reorganizeCards();
|
||||
}
|
||||
}
|
||||
|
||||
void CardItem::resetState()
|
||||
{
|
||||
attacking = false;
|
||||
facedown = false;
|
||||
counters.clear();
|
||||
pt.clear();
|
||||
annotation.clear();
|
||||
attachedTo = 0;
|
||||
attachedCards.clear();
|
||||
setTapped(false, false);
|
||||
setDoesntUntap(false);
|
||||
if (scene())
|
||||
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
|
||||
update();
|
||||
}
|
||||
|
||||
void CardItem::processCardInfo(const ServerInfo_Card &_info)
|
||||
{
|
||||
counters.clear();
|
||||
const int counterListSize = _info.counter_list_size();
|
||||
for (int i = 0; i < counterListSize; ++i) {
|
||||
const ServerInfo_CardCounter &counterInfo = _info.counter_list(i);
|
||||
counters.insert(counterInfo.id(), counterInfo.value());
|
||||
}
|
||||
|
||||
setId(_info.id());
|
||||
setName(QString::fromStdString(_info.name()));
|
||||
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 faceDown)
|
||||
{
|
||||
deleteDragItem();
|
||||
dragItem = new CardDragItem(this, _id, _pos, faceDown);
|
||||
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 (static_cast<TabGame *>(owner->parent())->getSpectator())
|
||||
return;
|
||||
|
||||
Player *arrowOwner = static_cast<TabGame *>(owner->parent())->getActiveLocalPlayer();
|
||||
ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor);
|
||||
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() != zone)
|
||||
continue;
|
||||
|
||||
ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor);
|
||||
scene()->addItem(childArrow);
|
||||
arrow->addChildArrow(childArrow);
|
||||
}
|
||||
}
|
||||
|
||||
void CardItem::drawAttachArrow()
|
||||
{
|
||||
if (static_cast<TabGame *>(owner->parent())->getSpectator())
|
||||
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() != zone)
|
||||
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 (zone->getIsView()) {
|
||||
const ZoneViewZone *view = static_cast<const ZoneViewZone *>(zone);
|
||||
if (view->getRevealZone() && !view->getWriteableRevealZone())
|
||||
return;
|
||||
} else if (!owner->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(), facedown || forceFaceDown);
|
||||
dragItem->grabMouse();
|
||||
|
||||
int childIndex = 0;
|
||||
for (const auto &item : scene()->selectedItems()) {
|
||||
CardItem *card = static_cast<CardItem *>(item);
|
||||
if ((card == this) || (card->getZone() != zone))
|
||||
continue;
|
||||
++childIndex;
|
||||
QPointF childPos;
|
||||
if (zone->getHasCardAttr())
|
||||
childPos = card->pos() - pos();
|
||||
else
|
||||
childPos = QPointF(childIndex * CARD_WIDTH / 2, 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->getLocalOrJudge())
|
||||
return;
|
||||
|
||||
TableZone *tz = qobject_cast<TableZone *>(zone);
|
||||
if (tz)
|
||||
tz->toggleTapped();
|
||||
else
|
||||
zone->getPlayer()->playCard(this, faceDown, info ? info->getCipt() : false);
|
||||
}
|
||||
|
||||
void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::RightButton) {
|
||||
if (cardMenu != nullptr && !cardMenu->isEmpty() && owner != nullptr) {
|
||||
cardMenu->popup(event->screenPos());
|
||||
return;
|
||||
}
|
||||
} else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
|
||||
(!SettingsCache::instance().getDoubleClickToPlay())) {
|
||||
bool hideCard = false;
|
||||
if (zone && zone->getIsView()) {
|
||||
auto *view = static_cast<ZoneViewZone *>(zone);
|
||||
if (view->getRevealZone() && !view->getWriteableRevealZone())
|
||||
hideCard = true;
|
||||
}
|
||||
if (zone && hideCard) {
|
||||
zone->removeCard(this);
|
||||
} else {
|
||||
playCard(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) && (SettingsCache::instance().getDoubleClickToPlay()) &&
|
||||
(event->buttons() == Qt::LeftButton)) {
|
||||
if (revealedCard)
|
||||
zone->removeCard(this);
|
||||
else
|
||||
playCard(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(CARD_WIDTH_HALF, CARD_HEIGHT_HALF)
|
||||
.rotate(tapAngle)
|
||||
.translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF));
|
||||
setHovered(false);
|
||||
update();
|
||||
|
||||
return animationIncomplete;
|
||||
}
|
||||
|
||||
QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value)
|
||||
{
|
||||
if ((change == ItemSelectedHasChanged) && owner != nullptr) {
|
||||
if (value == true) {
|
||||
owner->setCardMenu(cardMenu);
|
||||
owner->getGame()->setActiveCard(this);
|
||||
} else if (owner->getCardMenu() == cardMenu) {
|
||||
owner->setCardMenu(nullptr);
|
||||
owner->getGame()->setActiveCard(nullptr);
|
||||
}
|
||||
}
|
||||
return QGraphicsItem::itemChange(change, value);
|
||||
}
|
||||
170
cockatrice/src/game/cards/card_item.h
Normal file
170
cockatrice/src/game/cards/card_item.h
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
#ifndef CARDITEM_H
|
||||
#define CARDITEM_H
|
||||
|
||||
#include "abstract_card_item.h"
|
||||
#include "server_card.h"
|
||||
|
||||
class CardDatabase;
|
||||
class CardDragItem;
|
||||
class CardZone;
|
||||
class ServerInfo_Card;
|
||||
class Player;
|
||||
class QAction;
|
||||
class QColor;
|
||||
|
||||
const int MAX_COUNTERS_ON_CARD = 999;
|
||||
const float CARD_WIDTH_HALF = CARD_WIDTH / 2;
|
||||
const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2;
|
||||
const int ROTATION_DEGREES_PER_FRAME = 10;
|
||||
|
||||
class CardItem : public AbstractCardItem
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
CardZone *zone;
|
||||
bool revealedCard;
|
||||
bool attacking;
|
||||
QMap<int, int> counters;
|
||||
QString annotation;
|
||||
QString pt;
|
||||
bool destroyOnZoneChange;
|
||||
bool doesntUntap;
|
||||
QPoint gridPoint;
|
||||
CardDragItem *dragItem;
|
||||
CardItem *attachedTo;
|
||||
QList<CardItem *> attachedCards;
|
||||
|
||||
QMenu *cardMenu, *ptMenu, *moveMenu;
|
||||
|
||||
void prepareDelete();
|
||||
public slots:
|
||||
void deleteLater();
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
Type = typeCard
|
||||
};
|
||||
int type() const
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
CardItem(Player *_owner,
|
||||
const QString &_name = QString(),
|
||||
int _cardid = -1,
|
||||
bool revealedCard = false,
|
||||
QGraphicsItem *parent = nullptr,
|
||||
CardZone *_zone = nullptr);
|
||||
~CardItem();
|
||||
void retranslateUi();
|
||||
CardZone *getZone() const
|
||||
{
|
||||
return zone;
|
||||
}
|
||||
void setZone(CardZone *_zone);
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
QPoint getGridPoint() const
|
||||
{
|
||||
return gridPoint;
|
||||
}
|
||||
void setGridPoint(const QPoint &_gridPoint)
|
||||
{
|
||||
gridPoint = _gridPoint;
|
||||
}
|
||||
QPoint getGridPos() const
|
||||
{
|
||||
return gridPoint;
|
||||
}
|
||||
Player *getOwner() const
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
void setOwner(Player *_owner)
|
||||
{
|
||||
owner = _owner;
|
||||
}
|
||||
bool getRevealedCard() const
|
||||
{
|
||||
return revealedCard;
|
||||
}
|
||||
bool getAttacking() const
|
||||
{
|
||||
return attacking;
|
||||
}
|
||||
void setAttacking(bool _attacking);
|
||||
const QMap<int, int> &getCounters() const
|
||||
{
|
||||
return counters;
|
||||
}
|
||||
void setCounter(int _id, int _value);
|
||||
QString getAnnotation() const
|
||||
{
|
||||
return annotation;
|
||||
}
|
||||
void setAnnotation(const QString &_annotation);
|
||||
bool getDoesntUntap() const
|
||||
{
|
||||
return doesntUntap;
|
||||
}
|
||||
void setDoesntUntap(bool _doesntUntap);
|
||||
QString getPT() const
|
||||
{
|
||||
return pt;
|
||||
}
|
||||
void setPT(const QString &_pt);
|
||||
bool getDestroyOnZoneChange() const
|
||||
{
|
||||
return destroyOnZoneChange;
|
||||
}
|
||||
void setDestroyOnZoneChange(bool _destroy)
|
||||
{
|
||||
destroyOnZoneChange = _destroy;
|
||||
}
|
||||
CardItem *getAttachedTo() const
|
||||
{
|
||||
return attachedTo;
|
||||
}
|
||||
void setAttachedTo(CardItem *_attachedTo);
|
||||
void addAttachedCard(CardItem *card)
|
||||
{
|
||||
attachedCards.append(card);
|
||||
}
|
||||
void removeAttachedCard(CardItem *card)
|
||||
{
|
||||
attachedCards.removeOne(card);
|
||||
}
|
||||
const QList<CardItem *> &getAttachedCards() const
|
||||
{
|
||||
return attachedCards;
|
||||
}
|
||||
void resetState();
|
||||
void processCardInfo(const ServerInfo_Card &_info);
|
||||
|
||||
QMenu *getCardMenu() const
|
||||
{
|
||||
return cardMenu;
|
||||
}
|
||||
QMenu *getPTMenu() const
|
||||
{
|
||||
return ptMenu;
|
||||
}
|
||||
QMenu *getMoveMenu() const
|
||||
{
|
||||
return moveMenu;
|
||||
}
|
||||
|
||||
bool animationEvent();
|
||||
CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown);
|
||||
void deleteDragItem();
|
||||
void drawArrow(const QColor &arrowColor);
|
||||
void drawAttachArrow();
|
||||
void playCard(bool faceDown);
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
|
||||
};
|
||||
|
||||
#endif
|
||||
63
cockatrice/src/game/cards/card_list.cpp
Normal file
63
cockatrice/src/game/cards/card_list.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#include "card_list.h"
|
||||
|
||||
#include "card_database.h"
|
||||
#include "card_item.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
CardList::CardList(bool _contentsKnown) : QList<CardItem *>(), contentsKnown(_contentsKnown)
|
||||
{
|
||||
}
|
||||
|
||||
CardItem *CardList::findCard(const int id, const bool remove, int *position)
|
||||
{
|
||||
if (!contentsKnown) {
|
||||
if (empty())
|
||||
return 0;
|
||||
CardItem *temp = at(0);
|
||||
if (remove)
|
||||
removeAt(0);
|
||||
if (position)
|
||||
*position = id;
|
||||
return temp;
|
||||
} else
|
||||
for (int i = 0; i < size(); i++) {
|
||||
CardItem *temp = at(i);
|
||||
if (temp->getId() == id) {
|
||||
if (remove)
|
||||
removeAt(i);
|
||||
if (position)
|
||||
*position = i;
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class CardList::compareFunctor
|
||||
{
|
||||
private:
|
||||
int flags;
|
||||
|
||||
public:
|
||||
explicit compareFunctor(int _flags) : flags(_flags)
|
||||
{
|
||||
}
|
||||
inline bool operator()(CardItem *a, CardItem *b) const
|
||||
{
|
||||
if (flags & SortByType) {
|
||||
QString t1 = a->getInfo() ? a->getInfo()->getMainCardType() : QString();
|
||||
QString t2 = b->getInfo() ? b->getInfo()->getMainCardType() : QString();
|
||||
if ((t1 == t2) && (flags & SortByName))
|
||||
return a->getName() < b->getName();
|
||||
return t1 < t2;
|
||||
} else
|
||||
return a->getName() < b->getName();
|
||||
}
|
||||
};
|
||||
|
||||
void CardList::sort(int flags)
|
||||
{
|
||||
compareFunctor cf(flags);
|
||||
std::sort(begin(), end(), cf);
|
||||
}
|
||||
31
cockatrice/src/game/cards/card_list.h
Normal file
31
cockatrice/src/game/cards/card_list.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef CARDLIST_H
|
||||
#define CARDLIST_H
|
||||
|
||||
#include <QList>
|
||||
|
||||
class CardItem;
|
||||
|
||||
class CardList : public QList<CardItem *>
|
||||
{
|
||||
private:
|
||||
class compareFunctor;
|
||||
|
||||
protected:
|
||||
bool contentsKnown;
|
||||
|
||||
public:
|
||||
enum SortFlags
|
||||
{
|
||||
SortByName = 1,
|
||||
SortByType = 2
|
||||
};
|
||||
CardList(bool _contentsKnown);
|
||||
CardItem *findCard(const int id, const bool remove, int *position = NULL);
|
||||
bool getContentsKnown() const
|
||||
{
|
||||
return contentsKnown;
|
||||
}
|
||||
void sort(int flags = SortByName);
|
||||
};
|
||||
|
||||
#endif
|
||||
78
cockatrice/src/game/filters/filter_builder.cpp
Normal file
78
cockatrice/src/game/filters/filter_builder.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#include "filter_builder.h"
|
||||
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "filter_card.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
|
||||
FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
filterCombo = new QComboBox;
|
||||
filterCombo->setObjectName("filterCombo");
|
||||
for (int i = 0; i < CardFilter::AttrEnd; i++)
|
||||
filterCombo->addItem(CardFilter::attrName(static_cast<CardFilter::Attr>(i)), QVariant(i));
|
||||
|
||||
typeCombo = new QComboBox;
|
||||
typeCombo->setObjectName("typeCombo");
|
||||
for (int i = 0; i < CardFilter::TypeEnd; i++)
|
||||
typeCombo->addItem(CardFilter::typeName(static_cast<CardFilter::Type>(i)), QVariant(i));
|
||||
|
||||
QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString());
|
||||
ok->setObjectName("ok");
|
||||
ok->setMaximumSize(20, 20);
|
||||
|
||||
edit = new LineEditUnfocusable;
|
||||
edit->setObjectName("edit");
|
||||
edit->setPlaceholderText(tr("Type your filter here"));
|
||||
edit->setClearButtonEnabled(true);
|
||||
edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
QGridLayout *layout = new QGridLayout;
|
||||
layout->setObjectName("layout");
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
layout->addWidget(typeCombo, 0, 0, 1, 2);
|
||||
layout->addWidget(filterCombo, 0, 2, 1, 2);
|
||||
layout->addWidget(edit, 1, 0, 1, 3);
|
||||
layout->addWidget(ok, 1, 3);
|
||||
|
||||
setLayout(layout);
|
||||
|
||||
connect(filterCombo, SIGNAL(activated(int)), edit, SLOT(setFocus()));
|
||||
connect(edit, SIGNAL(returnPressed()), this, SLOT(emit_add()));
|
||||
connect(ok, SIGNAL(released()), this, SLOT(emit_add()));
|
||||
fltr = NULL;
|
||||
}
|
||||
|
||||
FilterBuilder::~FilterBuilder()
|
||||
{
|
||||
destroyFilter();
|
||||
}
|
||||
|
||||
void FilterBuilder::destroyFilter()
|
||||
{
|
||||
if (fltr)
|
||||
delete fltr;
|
||||
}
|
||||
|
||||
static int comboCurrentIntData(const QComboBox *combo)
|
||||
{
|
||||
return combo->itemData(combo->currentIndex()).toInt();
|
||||
}
|
||||
|
||||
void FilterBuilder::emit_add()
|
||||
{
|
||||
QString txt;
|
||||
|
||||
txt = edit->text();
|
||||
if (txt.length() < 1)
|
||||
return;
|
||||
|
||||
destroyFilter();
|
||||
fltr = new CardFilter(txt, static_cast<CardFilter::Type>(comboCurrentIntData(typeCombo)),
|
||||
static_cast<CardFilter::Attr>(comboCurrentIntData(filterCombo)));
|
||||
emit add(fltr);
|
||||
edit->clear();
|
||||
}
|
||||
37
cockatrice/src/game/filters/filter_builder.h
Normal file
37
cockatrice/src/game/filters/filter_builder.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef FILTERBUILDER_H
|
||||
#define FILTERBUILDER_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class LineEditUnfocusable;
|
||||
class CardFilter;
|
||||
|
||||
class FilterBuilder : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QComboBox *typeCombo;
|
||||
QComboBox *filterCombo;
|
||||
LineEditUnfocusable *edit;
|
||||
CardFilter *fltr;
|
||||
|
||||
void destroyFilter();
|
||||
|
||||
public:
|
||||
FilterBuilder(QWidget *parent = nullptr);
|
||||
~FilterBuilder();
|
||||
|
||||
signals:
|
||||
void add(const CardFilter *f);
|
||||
|
||||
public slots:
|
||||
private slots:
|
||||
void emit_add();
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
#endif
|
||||
49
cockatrice/src/game/filters/filter_card.cpp
Normal file
49
cockatrice/src/game/filters/filter_card.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#include "filter_card.h"
|
||||
|
||||
const QString CardFilter::typeName(Type t)
|
||||
{
|
||||
switch (t) {
|
||||
case TypeAnd:
|
||||
return tr("AND", "Logical conjunction operator used in card filter");
|
||||
case TypeOr:
|
||||
return tr("OR", "Logical disjunction operator used in card filter");
|
||||
case TypeAndNot:
|
||||
return tr("AND NOT", "Negated logical conjunction operator used in card filter");
|
||||
case TypeOrNot:
|
||||
return tr("OR NOT", "Negated logical disjunction operator used in card filter");
|
||||
default:
|
||||
return QString("");
|
||||
}
|
||||
}
|
||||
|
||||
const QString CardFilter::attrName(Attr a)
|
||||
{
|
||||
switch (a) {
|
||||
case AttrName:
|
||||
return tr("Name");
|
||||
case AttrType:
|
||||
return tr("Type");
|
||||
case AttrColor:
|
||||
return tr("Color");
|
||||
case AttrText:
|
||||
return tr("Text");
|
||||
case AttrSet:
|
||||
return tr("Set");
|
||||
case AttrManaCost:
|
||||
return tr("Mana Cost");
|
||||
case AttrCmc:
|
||||
return tr("Mana Value");
|
||||
case AttrRarity:
|
||||
return tr("Rarity");
|
||||
case AttrPow:
|
||||
return tr("Power");
|
||||
case AttrTough:
|
||||
return tr("Toughness");
|
||||
case AttrLoyalty:
|
||||
return tr("Loyalty");
|
||||
case AttrFormat:
|
||||
return tr("Format");
|
||||
default:
|
||||
return QString("");
|
||||
}
|
||||
}
|
||||
66
cockatrice/src/game/filters/filter_card.h
Normal file
66
cockatrice/src/game/filters/filter_card.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#ifndef CARDFILTER_H
|
||||
#define CARDFILTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <utility>
|
||||
|
||||
class CardFilter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
TypeAnd = 0,
|
||||
TypeOr,
|
||||
TypeAndNot,
|
||||
TypeOrNot,
|
||||
TypeEnd
|
||||
};
|
||||
|
||||
/* if you add an attribute here you also need to
|
||||
* add its string representation in attrName */
|
||||
enum Attr
|
||||
{
|
||||
AttrCmc = 0,
|
||||
AttrColor,
|
||||
AttrLoyalty,
|
||||
AttrManaCost,
|
||||
AttrName,
|
||||
AttrPow,
|
||||
AttrRarity,
|
||||
AttrSet,
|
||||
AttrText,
|
||||
AttrTough,
|
||||
AttrType,
|
||||
AttrFormat,
|
||||
AttrEnd
|
||||
};
|
||||
|
||||
private:
|
||||
QString trm;
|
||||
enum Type t;
|
||||
enum Attr a;
|
||||
|
||||
public:
|
||||
CardFilter(QString &term, Type type, Attr attr) : trm(term), t(type), a(attr){};
|
||||
|
||||
Type type() const
|
||||
{
|
||||
return t;
|
||||
}
|
||||
const QString &term() const
|
||||
{
|
||||
return trm;
|
||||
}
|
||||
Attr attr() const
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
static const QString typeName(Type t);
|
||||
static const QString attrName(Attr a);
|
||||
};
|
||||
|
||||
#endif
|
||||
371
cockatrice/src/game/filters/filter_string.cpp
Normal file
371
cockatrice/src/game/filters/filter_string.cpp
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
#include "filter_string.h"
|
||||
|
||||
#include "../../../../common/lib/peglib.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
|
||||
peg::parser search(R"(
|
||||
Start <- QueryPartList
|
||||
~ws <- [ ]+
|
||||
QueryPartList <- ComplexQueryPart ( ws ("and" ws)? ComplexQueryPart)* ws*
|
||||
|
||||
ComplexQueryPart <- SomewhatComplexQueryPart ws $or<[oO][rR]> ws ComplexQueryPart / SomewhatComplexQueryPart
|
||||
|
||||
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
|
||||
|
||||
QueryPart <- NotQuery / SetQuery / RarityQuery / CMCQuery / FormatQuery / PowerQuery / ToughnessQuery / ColorQuery / TypeQuery / OracleQuery / FieldQuery / GenericQuery
|
||||
|
||||
NotQuery <- ('not' ws/'-') SomewhatComplexQueryPart
|
||||
SetQuery <- ('e'/'set') [:] FlexStringValue
|
||||
OracleQuery <- 'o' [:] RegexString
|
||||
|
||||
|
||||
CMCQuery <- ('cmc'/'mv') ws? NumericExpression
|
||||
PowerQuery <- [Pp] 'ow' 'er'? ws? NumericExpression
|
||||
ToughnessQuery <- [Tt] 'ou' 'ghness'? ws? NumericExpression
|
||||
RarityQuery <- [rR] ':' RegexString
|
||||
|
||||
FormatQuery <- 'f' ':' Format / Legality ':' Format
|
||||
Format <- [a-zA-Z] [a-z]*
|
||||
Legality <- [Ll] 'egal'? / [Bb] 'anned'? / [Rr] 'estricted'
|
||||
|
||||
|
||||
TypeQuery <- [tT] 'ype'? [:] StringValue
|
||||
|
||||
Color <- < [Ww] 'hite'? / [Uu] / [Bb] 'lack'? / [Rr] 'ed'? / [Gg] 'reen'? / [Bb] 'lue'? >
|
||||
ColorEx <- Color / [mc]
|
||||
|
||||
ColorQuery <- [cC] 'olor'? <[iI]?> <[:!]> ColorEx*
|
||||
|
||||
FieldQuery <- String [:] RegexString / String ws? NumericExpression
|
||||
|
||||
NonDoubleQuoteUnlessEscaped <- !["]. / '\"'.
|
||||
NonSingleQuoteUnlessEscaped <- ![']. / "\'".
|
||||
UnescapedStringListPart <- !['":<>=! ].
|
||||
String <- UnescapedStringListPart+ / ["] <NonDoubleQuoteUnlessEscaped*> ["] / ['] <NonSingleQuoteUnlessEscaped*> [']
|
||||
StringValue <- String / [(] StringList [)]
|
||||
StringList <- StringListString (ws? [,] ws? StringListString)*
|
||||
StringListString <- UnescapedStringListPart+
|
||||
GenericQuery <- RegexString
|
||||
RegexString <- String
|
||||
|
||||
FlexStringValue <- CompactStringSet / String / [(] StringList [)]
|
||||
CompactStringSet <- StringListString ([,+] StringListString)+
|
||||
|
||||
NumericExpression <- NumericOperator ws? NumericValue
|
||||
NumericOperator <- [=:] / <[><!][=]?>
|
||||
NumericValue <- [0-9]+
|
||||
|
||||
)");
|
||||
|
||||
std::once_flag init;
|
||||
|
||||
static void setupParserRules()
|
||||
{
|
||||
auto passthru = [](const peg::SemanticValues &sv) -> Filter { return !sv.empty() ? sv[0].get<Filter>() : nullptr; };
|
||||
|
||||
search["Start"] = passthru;
|
||||
search["QueryPartList"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
return [=](CardData x) {
|
||||
for (int i = 0; i < static_cast<int>(sv.size()); ++i) {
|
||||
if (!sv[i].get<Filter>()(x))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
};
|
||||
search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
return [=](CardData x) {
|
||||
for (int i = 0; i < static_cast<int>(sv.size()); ++i) {
|
||||
if (sv[i].get<Filter>()(x))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
search["SomewhatComplexQueryPart"] = passthru;
|
||||
search["QueryPart"] = passthru;
|
||||
search["NotQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
Filter dependent = sv[0].get<Filter>();
|
||||
return [=](CardData x) -> bool { return !dependent(x); };
|
||||
};
|
||||
search["TypeQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
StringMatcher matcher = sv[0].get<StringMatcher>();
|
||||
return [=](CardData x) -> bool { return matcher(x->getCardType()); };
|
||||
};
|
||||
search["SetQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
StringMatcher matcher = sv[0].get<StringMatcher>();
|
||||
return [=](CardData x) -> bool {
|
||||
for (const auto &set : x->getSets().keys()) {
|
||||
if (matcher(set))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
search["RarityQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
StringMatcher matcher = sv[0].get<StringMatcher>();
|
||||
return [=](CardData x) -> bool {
|
||||
for (const auto &set : x->getSets().values()) {
|
||||
if (matcher(set.getProperty("rarity")))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
search["FormatQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
if (sv.choice() == 0) {
|
||||
QString format = sv[0].get<QString>();
|
||||
return [=](CardData x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == "legal"; };
|
||||
} else {
|
||||
QString format = sv[1].get<QString>();
|
||||
QString legality = sv[0].get<QString>();
|
||||
return [=](CardData x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == legality; };
|
||||
}
|
||||
};
|
||||
search["Legality"] = [](const peg::SemanticValues &sv) -> QString {
|
||||
switch (tolower(sv.str()[0])) {
|
||||
case 'l':
|
||||
return "legal";
|
||||
case 'b':
|
||||
return "banned";
|
||||
case 'r':
|
||||
return "restricted";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
search["Format"] = [](const peg::SemanticValues &sv) -> QString {
|
||||
if (sv.length() == 1) {
|
||||
switch (tolower(sv.str()[0])) {
|
||||
case 'm':
|
||||
return "modern";
|
||||
case 's':
|
||||
return "standard";
|
||||
case 'v':
|
||||
return "vintage";
|
||||
case 'l':
|
||||
return "legacy";
|
||||
case 'c':
|
||||
return "commander";
|
||||
case 'p':
|
||||
return "pioneer";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
return QString::fromStdString(sv.str()).toLower();
|
||||
}
|
||||
};
|
||||
search["StringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher {
|
||||
if (sv.choice() == 0) {
|
||||
auto target = sv[0].get<QString>();
|
||||
return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); };
|
||||
} else {
|
||||
auto target = sv[0].get<QStringList>();
|
||||
return [=](const QString &s) {
|
||||
for (const QString &str : target) {
|
||||
if (s.split(" ").contains(str, Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
search["String"] = [](const peg::SemanticValues &sv) -> QString {
|
||||
if (sv.choice() == 0) {
|
||||
return QString::fromStdString(sv.str());
|
||||
} else {
|
||||
return QString::fromStdString(sv.token(0));
|
||||
}
|
||||
};
|
||||
search["FlexStringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher {
|
||||
if (sv.choice() != 1) {
|
||||
auto target = sv[0].get<QStringList>();
|
||||
return [=](const QString &s) {
|
||||
for (const QString &str : target) {
|
||||
if (s.split(" ").contains(str, Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
} else {
|
||||
auto target = sv[0].get<QString>();
|
||||
return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); };
|
||||
}
|
||||
};
|
||||
search["CompactStringSet"] = search["StringList"] = [](const peg::SemanticValues &sv) -> QStringList {
|
||||
QStringList result;
|
||||
for (int i = 0; i < static_cast<int>(sv.size()); ++i) {
|
||||
result.append(sv[i].get<QString>());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
search["StringListString"] = [](const peg::SemanticValues &sv) -> QString {
|
||||
return QString::fromStdString(sv.str());
|
||||
};
|
||||
|
||||
search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher {
|
||||
auto arg = sv[1].get<int>();
|
||||
auto op = sv[0].get<QString>();
|
||||
|
||||
if (op == ">")
|
||||
return [=](int s) { return s > arg; };
|
||||
if (op == ">=")
|
||||
return [=](int s) { return s >= arg; };
|
||||
if (op == "<")
|
||||
return [=](int s) { return s < arg; };
|
||||
if (op == "<=")
|
||||
return [=](int s) { return s <= arg; };
|
||||
if (op == "=")
|
||||
return [=](int s) { return s == arg; };
|
||||
if (op == ":")
|
||||
return [=](int s) { return s == arg; };
|
||||
if (op == "!=")
|
||||
return [=](int s) { return s != arg; };
|
||||
return [](int) { return false; };
|
||||
};
|
||||
|
||||
search["NumericValue"] = [](const peg::SemanticValues &sv) -> int {
|
||||
return QString::fromStdString(sv.str()).toInt();
|
||||
};
|
||||
|
||||
search["NumericOperator"] = [](const peg::SemanticValues &sv) -> QString {
|
||||
return QString::fromStdString(sv.str());
|
||||
};
|
||||
|
||||
search["RegexString"] = [](const peg::SemanticValues &sv) -> StringMatcher {
|
||||
auto target = sv[0].get<QString>();
|
||||
return [=](const QString &s) {
|
||||
auto sanitizedTarget = QString(target);
|
||||
sanitizedTarget.replace("\\\"", "\"");
|
||||
sanitizedTarget.replace("\\'", "'");
|
||||
return s.QString::contains(sanitizedTarget, Qt::CaseInsensitive);
|
||||
};
|
||||
};
|
||||
|
||||
search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
StringMatcher matcher = sv[0].get<StringMatcher>();
|
||||
return [=](CardData x) { return matcher(x->getText()); };
|
||||
};
|
||||
|
||||
search["ColorQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
QString parts;
|
||||
for (int i = 0; i < static_cast<int>(sv.size()); ++i) {
|
||||
parts += sv[i].get<char>();
|
||||
}
|
||||
bool idenity = sv.tokens[0].first[0] != 'i';
|
||||
if (sv.tokens[1].first[0] == ':') {
|
||||
return [=](CardData x) {
|
||||
QString match = idenity ? x->getColors() : x->getProperty("coloridentity");
|
||||
if (parts.contains("m") && match.length() < 2) {
|
||||
return false;
|
||||
} else if (parts == "m") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parts.contains("c") && match.length() == 0)
|
||||
return true;
|
||||
|
||||
for (const auto &i : match) {
|
||||
if (parts.contains(i))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
} else {
|
||||
return [=](CardData x) {
|
||||
QString match = idenity ? x->getColors() : x->getProperty("colorIdentity");
|
||||
if (parts.contains("m") && match.length() < 2)
|
||||
return false;
|
||||
|
||||
if (parts.contains("c") && match.length() != 0)
|
||||
return false;
|
||||
|
||||
for (const auto &part : parts) {
|
||||
if (!match.contains(part))
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &i : match) {
|
||||
if (!parts.contains(i))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
search["CMCQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
NumberMatcher matcher = sv[0].get<NumberMatcher>();
|
||||
return [=](CardData x) -> bool { return matcher(x->getProperty("cmc").toInt()); };
|
||||
};
|
||||
search["PowerQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
NumberMatcher matcher = sv[0].get<NumberMatcher>();
|
||||
return [=](CardData x) -> bool { return matcher(x->getPowTough().split("/")[0].toInt()); };
|
||||
};
|
||||
search["ToughnessQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
NumberMatcher matcher = sv[0].get<NumberMatcher>();
|
||||
return [=](CardData x) -> bool {
|
||||
auto parts = x->getPowTough().split("/");
|
||||
return matcher(parts.length() == 2 ? parts[1].toInt() : 0);
|
||||
};
|
||||
};
|
||||
search["FieldQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
QString field = sv[0].get<QString>();
|
||||
if (sv.choice() == 0) {
|
||||
StringMatcher matcher = sv[1].get<StringMatcher>();
|
||||
return [=](CardData x) -> bool { return x->hasProperty(field) ? matcher(x->getProperty(field)) : false; };
|
||||
} else {
|
||||
NumberMatcher matcher = sv[1].get<NumberMatcher>();
|
||||
return [=](CardData x) -> bool {
|
||||
return x->hasProperty(field) ? matcher(x->getProperty(field).toInt()) : false;
|
||||
};
|
||||
}
|
||||
};
|
||||
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
StringMatcher matcher = sv[0].get<StringMatcher>();
|
||||
return [=](CardData x) { return matcher(x->getName()); };
|
||||
};
|
||||
|
||||
search["Color"] = [](const peg::SemanticValues &sv) -> char { return "WUBRGU"[sv.choice()]; };
|
||||
search["ColorEx"] = [](const peg::SemanticValues &sv) -> char {
|
||||
return sv.choice() == 0 ? sv[0].get<char>() : *sv.c_str();
|
||||
};
|
||||
}
|
||||
|
||||
FilterString::FilterString()
|
||||
{
|
||||
result = [](CardData) -> bool { return false; };
|
||||
_error = "Not initialized";
|
||||
}
|
||||
|
||||
FilterString::FilterString(const QString &expr)
|
||||
{
|
||||
QByteArray ba = expr.simplified().toUtf8();
|
||||
|
||||
std::call_once(init, setupParserRules);
|
||||
|
||||
_error = QString();
|
||||
|
||||
if (ba.isEmpty()) {
|
||||
result = [](CardData) -> bool { return true; };
|
||||
return;
|
||||
}
|
||||
|
||||
search.log = [&](size_t ln, size_t col, const std::string &msg) {
|
||||
_error = QString("%1:%2: %3").arg(ln).arg(col).arg(QString::fromStdString(msg));
|
||||
};
|
||||
|
||||
if (!search.parse(ba.data(), result)) {
|
||||
qDebug() << "Filter string error" << _error;
|
||||
result = [](CardData) -> bool { return false; };
|
||||
}
|
||||
}
|
||||
49
cockatrice/src/game/filters/filter_string.h
Normal file
49
cockatrice/src/game/filters/filter_string.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef FILTER_STRING_H
|
||||
#define FILTER_STRING_H
|
||||
|
||||
#include "../cards/card_database.h"
|
||||
#include "filter_tree.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
typedef CardInfoPtr CardData;
|
||||
typedef std::function<bool(const CardData &)> Filter;
|
||||
typedef std::function<bool(const QString &)> StringMatcher;
|
||||
typedef std::function<bool(int)> NumberMatcher;
|
||||
|
||||
namespace peg
|
||||
{
|
||||
template <typename Annotation> struct AstBase;
|
||||
struct EmptyType;
|
||||
typedef AstBase<EmptyType> Ast;
|
||||
} // namespace peg
|
||||
|
||||
class FilterString
|
||||
{
|
||||
public:
|
||||
FilterString();
|
||||
explicit FilterString(const QString &exp);
|
||||
bool check(const CardData &card)
|
||||
{
|
||||
return result(card);
|
||||
}
|
||||
|
||||
bool valid()
|
||||
{
|
||||
return _error.isEmpty();
|
||||
}
|
||||
|
||||
QString error()
|
||||
{
|
||||
return _error;
|
||||
}
|
||||
|
||||
private:
|
||||
QString _error;
|
||||
Filter result;
|
||||
};
|
||||
|
||||
#endif
|
||||
508
cockatrice/src/game/filters/filter_tree.cpp
Normal file
508
cockatrice/src/game/filters/filter_tree.cpp
Normal file
|
|
@ -0,0 +1,508 @@
|
|||
#include "filter_tree.h"
|
||||
|
||||
#include "filter_card.h"
|
||||
|
||||
#include <QList>
|
||||
|
||||
template <class T> FilterTreeNode *FilterTreeBranch<T>::nodeAt(int i) const
|
||||
{
|
||||
return (childNodes.size() > i) ? childNodes.at(i) : nullptr;
|
||||
}
|
||||
|
||||
template <class T> void FilterTreeBranch<T>::deleteAt(int i)
|
||||
{
|
||||
preRemoveChild(this, i);
|
||||
delete childNodes.takeAt(i);
|
||||
postRemoveChild(this, i);
|
||||
nodeChanged();
|
||||
}
|
||||
|
||||
template <class T> int FilterTreeBranch<T>::childIndex(const FilterTreeNode *node) const
|
||||
{
|
||||
auto *unconst = const_cast<FilterTreeNode *>(node);
|
||||
auto downcasted = dynamic_cast<T>(unconst);
|
||||
return (downcasted) ? childNodes.indexOf(downcasted) : -1;
|
||||
}
|
||||
|
||||
template <class T> FilterTreeBranch<T>::~FilterTreeBranch()
|
||||
{
|
||||
while (!childNodes.isEmpty()) {
|
||||
delete childNodes.takeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
const FilterItemList *LogicMap::findTypeList(CardFilter::Type type) const
|
||||
{
|
||||
QList<FilterItemList *>::const_iterator i;
|
||||
|
||||
for (i = childNodes.constBegin(); i != childNodes.constEnd(); ++i) {
|
||||
if ((*i)->type == type) {
|
||||
return *i;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FilterItemList *LogicMap::typeList(CardFilter::Type type)
|
||||
{
|
||||
QList<FilterItemList *>::iterator i;
|
||||
int count = 0;
|
||||
|
||||
for (i = childNodes.begin(); i != childNodes.end(); ++i) {
|
||||
if ((*i)->type == type) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
if (i == childNodes.end()) {
|
||||
preInsertChild(this, count);
|
||||
i = childNodes.insert(i, new FilterItemList(type, this));
|
||||
postInsertChild(this, count);
|
||||
nodeChanged();
|
||||
}
|
||||
|
||||
return *i;
|
||||
}
|
||||
|
||||
FilterTreeNode *LogicMap::parent() const
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
int FilterItemList::termIndex(const QString &term) const
|
||||
{
|
||||
for (int i = 0; i < childNodes.count(); i++) {
|
||||
if ((childNodes.at(i))->term == term) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
FilterTreeNode *FilterItemList::termNode(const QString &term)
|
||||
{
|
||||
int i = termIndex(term);
|
||||
if (i < 0) {
|
||||
FilterItem *fi = new FilterItem(term, this);
|
||||
int count = childNodes.count();
|
||||
|
||||
preInsertChild(this, count);
|
||||
childNodes.append(fi);
|
||||
postInsertChild(this, count);
|
||||
nodeChanged();
|
||||
|
||||
return fi;
|
||||
}
|
||||
|
||||
return childNodes.at(i);
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeAnd(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
|
||||
if (!(*i)->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(*i)->acceptCardAttr(info, attr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeAndNot(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
// if any one in the list is true, return false
|
||||
return !testTypeOr(info, attr);
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
bool noChildEnabledChild = true;
|
||||
|
||||
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
|
||||
if (!(*i)->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (noChildEnabledChild) {
|
||||
noChildEnabledChild = false;
|
||||
}
|
||||
|
||||
if ((*i)->acceptCardAttr(info, attr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return noChildEnabledChild;
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeOrNot(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
// if any one in the list is false, return true
|
||||
return !testTypeAnd(info, attr);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptName(const CardInfoPtr info) const
|
||||
{
|
||||
return info->getName().contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptType(const CardInfoPtr info) const
|
||||
{
|
||||
return info->getCardType().contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptColor(const CardInfoPtr info) const
|
||||
{
|
||||
QString converted_term = term.trimmed();
|
||||
|
||||
converted_term.replace("green", "g", Qt::CaseInsensitive);
|
||||
converted_term.replace("grn", "g", Qt::CaseInsensitive);
|
||||
converted_term.replace("blue", "u", Qt::CaseInsensitive);
|
||||
converted_term.replace("blu", "u", Qt::CaseInsensitive);
|
||||
converted_term.replace("black", "b", Qt::CaseInsensitive);
|
||||
converted_term.replace("blk", "b", Qt::CaseInsensitive);
|
||||
converted_term.replace("red", "r", Qt::CaseInsensitive);
|
||||
converted_term.replace("white", "w", Qt::CaseInsensitive);
|
||||
converted_term.replace("wht", "w", Qt::CaseInsensitive);
|
||||
converted_term.replace("colorless", "c", Qt::CaseInsensitive);
|
||||
converted_term.replace("colourless", "c", Qt::CaseInsensitive);
|
||||
converted_term.replace("none", "c", Qt::CaseInsensitive);
|
||||
|
||||
converted_term.replace(QString(" "), QString(""), Qt::CaseInsensitive);
|
||||
|
||||
// Colorless card filter
|
||||
if (converted_term.toLower() == "c" && info->getColors().length() < 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a tricky part, if the filter has multiple colors in it, like UGW,
|
||||
* then we should match all of them to the card's colors
|
||||
*/
|
||||
int match_count = 0;
|
||||
for (auto &it : converted_term) {
|
||||
if (info->getColors().contains(it, Qt::CaseInsensitive))
|
||||
match_count++;
|
||||
}
|
||||
|
||||
return match_count == converted_term.length();
|
||||
}
|
||||
|
||||
bool FilterItem::acceptText(const CardInfoPtr info) const
|
||||
{
|
||||
return info->getText().contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptSet(const CardInfoPtr info) const
|
||||
{
|
||||
bool status = false;
|
||||
for (const auto &set : info->getSets()) {
|
||||
if (set.getPtr()->getShortName().compare(term, Qt::CaseInsensitive) == 0 ||
|
||||
set.getPtr()->getLongName().compare(term, Qt::CaseInsensitive) == 0) {
|
||||
status = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptManaCost(const CardInfoPtr info) const
|
||||
{
|
||||
QString partialCost = term.toUpper();
|
||||
|
||||
// Sort the mana cost so it will be easy to find
|
||||
std::sort(partialCost.begin(), partialCost.end());
|
||||
|
||||
// Try to seperate the mana cost in case it's a split card
|
||||
// if it's not a split card the loop will run only once
|
||||
for (QString fullManaCost : info->getManaCost().split("//")) {
|
||||
std::sort(fullManaCost.begin(), fullManaCost.end());
|
||||
|
||||
// If the partial is found in the full, return true
|
||||
if (fullManaCost.contains(partialCost)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptCmc(const CardInfoPtr info) const
|
||||
{
|
||||
bool convertSuccess;
|
||||
int cmcInt = info->getCmc().toInt(&convertSuccess);
|
||||
// if conversion failed, check for the "//" separator used in split cards
|
||||
if (!convertSuccess) {
|
||||
int cmcSum = 0;
|
||||
for (const QString &cmc : info->getCmc().split("//")) {
|
||||
cmcInt = cmc.toInt();
|
||||
cmcSum += cmcInt;
|
||||
if (relationCheck(cmcInt)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return relationCheck(cmcSum);
|
||||
} else {
|
||||
return relationCheck(cmcInt);
|
||||
}
|
||||
}
|
||||
|
||||
bool FilterItem::acceptFormat(const CardInfoPtr info) const
|
||||
{
|
||||
return info->getProperty(QString("format-%1").arg(term.toLower())) == "legal";
|
||||
}
|
||||
|
||||
bool FilterItem::acceptLoyalty(const CardInfoPtr info) const
|
||||
{
|
||||
if (info->getLoyalty().isEmpty()) {
|
||||
return false;
|
||||
} else {
|
||||
bool success;
|
||||
// if loyalty can't be converted to "int" it must be "X"
|
||||
int loyalty = info->getLoyalty().toInt(&success);
|
||||
if (success) {
|
||||
return relationCheck(loyalty);
|
||||
} else {
|
||||
return term.trimmed().toUpper() == info->getLoyalty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FilterItem::acceptPowerToughness(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
int slash = info->getPowTough().indexOf("/");
|
||||
if (slash == -1) {
|
||||
return false;
|
||||
}
|
||||
QString valueString;
|
||||
if (attr == CardFilter::AttrPow) {
|
||||
valueString = info->getPowTough().mid(0, slash);
|
||||
} else {
|
||||
valueString = info->getPowTough().mid(slash + 1);
|
||||
}
|
||||
if (term == valueString) {
|
||||
return true;
|
||||
}
|
||||
// advanced filtering should only happen after fast string comparison failed
|
||||
bool conversion;
|
||||
int value = valueString.toInt(&conversion);
|
||||
return conversion ? relationCheck(value) : false;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptRarity(const CardInfoPtr info) const
|
||||
{
|
||||
QString converted_term = term.trimmed();
|
||||
|
||||
/*
|
||||
* The purpose of this loop is to only apply one of the replacement
|
||||
* policies and then escape. If we attempt to layer them on top of
|
||||
* each other, we will get awkward results (i.e. comythic rare mythic rareon)
|
||||
* Conditional statement will exit once a case is successful in
|
||||
* replacement OR we go through all possible cases.
|
||||
* Will also need to replace just "mythic"
|
||||
*/
|
||||
for (int i = 0; converted_term.length() <= 3 && i <= 6; i++) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
converted_term.replace("mr", "mythic", Qt::CaseInsensitive);
|
||||
break;
|
||||
case 1:
|
||||
converted_term.replace("m r", "mythic", Qt::CaseInsensitive);
|
||||
break;
|
||||
case 2:
|
||||
converted_term.replace("m", "mythic", Qt::CaseInsensitive);
|
||||
break;
|
||||
case 3:
|
||||
converted_term.replace("c", "common", Qt::CaseInsensitive);
|
||||
break;
|
||||
case 4:
|
||||
converted_term.replace("u", "uncommon", Qt::CaseInsensitive);
|
||||
break;
|
||||
case 5:
|
||||
converted_term.replace("r", "rare", Qt::CaseInsensitive);
|
||||
break;
|
||||
case 6:
|
||||
converted_term.replace("s", "special", Qt::CaseInsensitive);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &set : info->getSets()) {
|
||||
if (set.getProperty("rarity").compare(converted_term, Qt::CaseInsensitive) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterItem::relationCheck(int cardInfo) const
|
||||
{
|
||||
bool result, conversion;
|
||||
|
||||
// if int conversion fails, there's probably an operator at the start
|
||||
result = (cardInfo == term.toInt(&conversion));
|
||||
if (!conversion) {
|
||||
// leading whitespaces could cause indexing to fail
|
||||
QString trimmedTerm = term.trimmed();
|
||||
// check whether it's a 2 char operator (<=, >=, or ==)
|
||||
if (trimmedTerm[1] == '=') {
|
||||
int termInt = trimmedTerm.mid(2).toInt();
|
||||
if (trimmedTerm.startsWith('<')) {
|
||||
result = (cardInfo <= termInt);
|
||||
} else if (trimmedTerm.startsWith('>')) {
|
||||
result = (cardInfo >= termInt);
|
||||
} else {
|
||||
result = (cardInfo == termInt);
|
||||
}
|
||||
} else {
|
||||
int termInt = trimmedTerm.mid(1).toInt();
|
||||
if (trimmedTerm.startsWith('<')) {
|
||||
result = (cardInfo < termInt);
|
||||
} else if (trimmedTerm.startsWith('>')) {
|
||||
result = (cardInfo > termInt);
|
||||
} else if (trimmedTerm.startsWith("=")) {
|
||||
result = (cardInfo == termInt);
|
||||
} else {
|
||||
// the int conversion hasn't failed due to an operator at the start
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
switch (attr) {
|
||||
case CardFilter::AttrName:
|
||||
return acceptName(info);
|
||||
case CardFilter::AttrType:
|
||||
return acceptType(info);
|
||||
case CardFilter::AttrColor:
|
||||
return acceptColor(info);
|
||||
case CardFilter::AttrText:
|
||||
return acceptText(info);
|
||||
case CardFilter::AttrSet:
|
||||
return acceptSet(info);
|
||||
case CardFilter::AttrManaCost:
|
||||
return acceptManaCost(info);
|
||||
case CardFilter::AttrCmc:
|
||||
return acceptCmc(info);
|
||||
case CardFilter::AttrRarity:
|
||||
return acceptRarity(info);
|
||||
case CardFilter::AttrPow:
|
||||
case CardFilter::AttrTough:
|
||||
// intentional fallthrough
|
||||
return acceptPowerToughness(info, attr);
|
||||
case CardFilter::AttrLoyalty:
|
||||
return acceptLoyalty(info);
|
||||
case CardFilter::AttrFormat:
|
||||
return acceptFormat(info);
|
||||
default:
|
||||
return true; /* ignore this attribute */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Need to define these here to make QT happy, otherwise
|
||||
* moc doesnt find some of the FilterTreeBranch symbols.
|
||||
*/
|
||||
FilterTree::FilterTree() = default;
|
||||
FilterTree::~FilterTree() = default;
|
||||
|
||||
LogicMap *FilterTree::attrLogicMap(CardFilter::Attr attr)
|
||||
{
|
||||
QList<LogicMap *>::iterator i;
|
||||
|
||||
int count = 0;
|
||||
for (i = childNodes.begin(); i != childNodes.end(); i++) {
|
||||
if ((*i)->attr == attr) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
if (i == childNodes.end()) {
|
||||
preInsertChild(this, count);
|
||||
i = childNodes.insert(i, new LogicMap(attr, this));
|
||||
postInsertChild(this, count);
|
||||
nodeChanged();
|
||||
}
|
||||
|
||||
return *i;
|
||||
}
|
||||
|
||||
FilterItemList *FilterTree::attrTypeList(CardFilter::Attr attr, CardFilter::Type type)
|
||||
{
|
||||
return attrLogicMap(attr)->typeList(type);
|
||||
}
|
||||
|
||||
FilterTreeNode *FilterTree::termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term)
|
||||
{
|
||||
return attrTypeList(attr, type)->termNode(term);
|
||||
}
|
||||
|
||||
FilterTreeNode *FilterTree::termNode(const CardFilter *f)
|
||||
{
|
||||
return termNode(f->attr(), f->type(), f->term());
|
||||
}
|
||||
|
||||
bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm) const
|
||||
{
|
||||
const FilterItemList *fil;
|
||||
bool status = true;
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeAnd);
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAnd(info, lm->attr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeAndNot);
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAndNot(info, lm->attr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeOr);
|
||||
if (fil && fil->isEnabled()) {
|
||||
status = false;
|
||||
|
||||
// if this is true we can return because it is OR'd with the OrNot list
|
||||
if (fil->testTypeOr(info, lm->attr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeOrNot);
|
||||
if (fil && fil->isEnabled() && fil->testTypeOrNot(info, lm->attr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool FilterTree::acceptsCard(const CardInfoPtr info) const
|
||||
{
|
||||
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
|
||||
if ((*i)->isEnabled() && !testAttr(info, *i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FilterTree::clear()
|
||||
{
|
||||
while (childCount() > 0) {
|
||||
deleteAt(0);
|
||||
}
|
||||
}
|
||||
272
cockatrice/src/game/filters/filter_tree.h
Normal file
272
cockatrice/src/game/filters/filter_tree.h
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
#ifndef FILTERTREE_H
|
||||
#define FILTERTREE_H
|
||||
|
||||
#include "../cards/card_database.h"
|
||||
#include "filter_card.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <utility>
|
||||
|
||||
class FilterTreeNode
|
||||
{
|
||||
private:
|
||||
bool enabled;
|
||||
|
||||
public:
|
||||
FilterTreeNode() : enabled(true)
|
||||
{
|
||||
}
|
||||
virtual bool isEnabled() const
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
virtual void enable()
|
||||
{
|
||||
enabled = true;
|
||||
nodeChanged();
|
||||
}
|
||||
virtual void disable()
|
||||
{
|
||||
enabled = false;
|
||||
nodeChanged();
|
||||
}
|
||||
virtual FilterTreeNode *parent() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual FilterTreeNode *nodeAt(int /* i */) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual void deleteAt(int /* i */)
|
||||
{
|
||||
}
|
||||
virtual int childCount() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
virtual int childIndex(const FilterTreeNode * /* node */) const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
virtual int index() const
|
||||
{
|
||||
return (parent() != nullptr) ? parent()->childIndex(this) : -1;
|
||||
}
|
||||
virtual const QString text() const
|
||||
{
|
||||
return QString("");
|
||||
}
|
||||
virtual bool isLeaf() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual void nodeChanged() const
|
||||
{
|
||||
if (parent() != nullptr)
|
||||
parent()->nodeChanged();
|
||||
}
|
||||
virtual void preInsertChild(const FilterTreeNode *p, int i) const
|
||||
{
|
||||
if (parent() != nullptr)
|
||||
parent()->preInsertChild(p, i);
|
||||
}
|
||||
virtual void postInsertChild(const FilterTreeNode *p, int i) const
|
||||
{
|
||||
if (parent() != nullptr)
|
||||
parent()->postInsertChild(p, i);
|
||||
}
|
||||
virtual void preRemoveChild(const FilterTreeNode *p, int i) const
|
||||
{
|
||||
if (parent() != nullptr)
|
||||
parent()->preRemoveChild(p, i);
|
||||
}
|
||||
virtual void postRemoveChild(const FilterTreeNode *p, int i) const
|
||||
{
|
||||
if (parent() != nullptr)
|
||||
parent()->postRemoveChild(p, i);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> class FilterTreeBranch : public FilterTreeNode
|
||||
{
|
||||
protected:
|
||||
QList<T> childNodes;
|
||||
|
||||
public:
|
||||
virtual ~FilterTreeBranch();
|
||||
FilterTreeNode *nodeAt(int i) const override;
|
||||
void deleteAt(int i) override;
|
||||
int childCount() const override
|
||||
{
|
||||
return childNodes.size();
|
||||
}
|
||||
int childIndex(const FilterTreeNode *node) const override;
|
||||
};
|
||||
|
||||
class FilterItemList;
|
||||
class FilterTree;
|
||||
class LogicMap : public FilterTreeBranch<FilterItemList *>
|
||||
{
|
||||
|
||||
private:
|
||||
FilterTree *const p;
|
||||
|
||||
public:
|
||||
const CardFilter::Attr attr;
|
||||
|
||||
LogicMap(CardFilter::Attr a, FilterTree *parent) : p(parent), attr(a)
|
||||
{
|
||||
}
|
||||
const FilterItemList *findTypeList(CardFilter::Type type) const;
|
||||
FilterItemList *typeList(CardFilter::Type type);
|
||||
FilterTreeNode *parent() const override;
|
||||
const QString text() const override
|
||||
{
|
||||
return CardFilter::attrName(attr);
|
||||
}
|
||||
};
|
||||
|
||||
class FilterItem;
|
||||
class FilterItemList : public FilterTreeBranch<FilterItem *>
|
||||
{
|
||||
private:
|
||||
LogicMap *const p;
|
||||
|
||||
public:
|
||||
const CardFilter::Type type;
|
||||
|
||||
FilterItemList(CardFilter::Type t, LogicMap *parent) : p(parent), type(t)
|
||||
{
|
||||
}
|
||||
CardFilter::Attr attr() const
|
||||
{
|
||||
return p->attr;
|
||||
}
|
||||
FilterTreeNode *parent() const override
|
||||
{
|
||||
return p;
|
||||
}
|
||||
int termIndex(const QString &term) const;
|
||||
FilterTreeNode *termNode(const QString &term);
|
||||
const QString text() const override
|
||||
{
|
||||
return CardFilter::typeName(type);
|
||||
}
|
||||
|
||||
bool testTypeAnd(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
bool testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
bool testTypeOr(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
bool testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
};
|
||||
|
||||
class FilterItem : public FilterTreeNode
|
||||
{
|
||||
private:
|
||||
FilterItemList *const p;
|
||||
|
||||
public:
|
||||
const QString term;
|
||||
|
||||
FilterItem(QString trm, FilterItemList *parent) : p(parent), term(std::move(trm))
|
||||
{
|
||||
}
|
||||
virtual ~FilterItem() = default;
|
||||
|
||||
CardFilter::Attr attr() const
|
||||
{
|
||||
return p->attr();
|
||||
}
|
||||
CardFilter::Type type() const
|
||||
{
|
||||
return p->type;
|
||||
}
|
||||
FilterTreeNode *parent() const override
|
||||
{
|
||||
return p;
|
||||
}
|
||||
const QString text() const override
|
||||
{
|
||||
return term;
|
||||
}
|
||||
bool isLeaf() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool acceptName(CardInfoPtr info) const;
|
||||
bool acceptType(CardInfoPtr info) const;
|
||||
bool acceptColor(CardInfoPtr info) const;
|
||||
bool acceptText(CardInfoPtr info) const;
|
||||
bool acceptSet(CardInfoPtr info) const;
|
||||
bool acceptManaCost(CardInfoPtr info) const;
|
||||
bool acceptCmc(CardInfoPtr info) const;
|
||||
bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
bool acceptLoyalty(CardInfoPtr info) const;
|
||||
bool acceptRarity(CardInfoPtr info) const;
|
||||
bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
bool acceptFormat(CardInfoPtr info) const;
|
||||
bool relationCheck(int cardInfo) const;
|
||||
};
|
||||
|
||||
class FilterTree : public QObject, public FilterTreeBranch<LogicMap *>
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void preInsertRow(const FilterTreeNode *parent, int i) const;
|
||||
void postInsertRow(const FilterTreeNode *parent, int i) const;
|
||||
void preRemoveRow(const FilterTreeNode *parent, int i) const;
|
||||
void postRemoveRow(const FilterTreeNode *parent, int i) const;
|
||||
void changed() const;
|
||||
|
||||
private:
|
||||
LogicMap *attrLogicMap(CardFilter::Attr attr);
|
||||
FilterItemList *attrTypeList(CardFilter::Attr attr, CardFilter::Type type);
|
||||
|
||||
bool testAttr(CardInfoPtr info, const LogicMap *lm) const;
|
||||
|
||||
void nodeChanged() const override
|
||||
{
|
||||
emit changed();
|
||||
}
|
||||
void preInsertChild(const FilterTreeNode *p, int i) const override
|
||||
{
|
||||
emit preInsertRow(p, i);
|
||||
}
|
||||
void postInsertChild(const FilterTreeNode *p, int i) const override
|
||||
{
|
||||
emit postInsertRow(p, i);
|
||||
}
|
||||
void preRemoveChild(const FilterTreeNode *p, int i) const override
|
||||
{
|
||||
emit preRemoveRow(p, i);
|
||||
}
|
||||
void postRemoveChild(const FilterTreeNode *p, int i) const override
|
||||
{
|
||||
emit postRemoveRow(p, i);
|
||||
}
|
||||
|
||||
public:
|
||||
FilterTree();
|
||||
~FilterTree() override;
|
||||
|
||||
FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term);
|
||||
FilterTreeNode *termNode(const CardFilter *f);
|
||||
|
||||
const QString text() const override
|
||||
{
|
||||
return QString("root");
|
||||
}
|
||||
int index() const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool acceptsCard(CardInfoPtr info) const;
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif
|
||||
263
cockatrice/src/game/filters/filter_tree_model.cpp
Normal file
263
cockatrice/src/game/filters/filter_tree_model.cpp
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#include "filter_tree_model.h"
|
||||
|
||||
#include "filter_card.h"
|
||||
#include "filter_tree.h"
|
||||
|
||||
#include <QFont>
|
||||
|
||||
FilterTreeModel::FilterTreeModel(QObject *parent) : QAbstractItemModel(parent)
|
||||
{
|
||||
fTree = new FilterTree;
|
||||
connect(fTree, SIGNAL(preInsertRow(const FilterTreeNode *, int)), this,
|
||||
SLOT(proxyBeginInsertRow(const FilterTreeNode *, int)));
|
||||
connect(fTree, SIGNAL(postInsertRow(const FilterTreeNode *, int)), this,
|
||||
SLOT(proxyEndInsertRow(const FilterTreeNode *, int)));
|
||||
connect(fTree, SIGNAL(preRemoveRow(const FilterTreeNode *, int)), this,
|
||||
SLOT(proxyBeginRemoveRow(const FilterTreeNode *, int)));
|
||||
connect(fTree, SIGNAL(postRemoveRow(const FilterTreeNode *, int)), this,
|
||||
SLOT(proxyEndRemoveRow(const FilterTreeNode *, int)));
|
||||
}
|
||||
|
||||
FilterTreeModel::~FilterTreeModel()
|
||||
{
|
||||
delete fTree;
|
||||
}
|
||||
|
||||
void FilterTreeModel::proxyBeginInsertRow(const FilterTreeNode *node, int i)
|
||||
{
|
||||
int idx;
|
||||
|
||||
idx = node->index();
|
||||
if (idx >= 0)
|
||||
beginInsertRows(createIndex(idx, 0, (void *)node), i, i);
|
||||
}
|
||||
|
||||
void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int)
|
||||
{
|
||||
int idx;
|
||||
|
||||
idx = node->index();
|
||||
if (idx >= 0)
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i)
|
||||
{
|
||||
int idx;
|
||||
|
||||
idx = node->index();
|
||||
if (idx >= 0)
|
||||
beginRemoveRows(createIndex(idx, 0, (void *)node), i, i);
|
||||
}
|
||||
|
||||
void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int)
|
||||
{
|
||||
int idx;
|
||||
|
||||
idx = node->index();
|
||||
if (idx >= 0)
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const
|
||||
{
|
||||
void *ip;
|
||||
FilterTreeNode *node;
|
||||
|
||||
if (!idx.isValid())
|
||||
return fTree;
|
||||
|
||||
ip = idx.internalPointer();
|
||||
if (ip == NULL)
|
||||
return fTree;
|
||||
|
||||
node = static_cast<FilterTreeNode *>(ip);
|
||||
return node;
|
||||
}
|
||||
|
||||
void FilterTreeModel::addFilter(const CardFilter *f)
|
||||
{
|
||||
emit layoutAboutToBeChanged();
|
||||
fTree->termNode(f);
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
int FilterTreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
const FilterTreeNode *node;
|
||||
int result;
|
||||
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
|
||||
node = indexToNode(parent);
|
||||
if (node)
|
||||
result = node->childCount();
|
||||
else
|
||||
result = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int FilterTreeModel::columnCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
const FilterTreeNode *node;
|
||||
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
if (index.column() >= columnCount())
|
||||
return QVariant();
|
||||
|
||||
node = indexToNode(index);
|
||||
if (node == NULL)
|
||||
return QVariant();
|
||||
|
||||
switch (role) {
|
||||
case Qt::FontRole:
|
||||
if (!node->isLeaf()) {
|
||||
QFont f;
|
||||
f.setBold(true);
|
||||
return f;
|
||||
}
|
||||
break;
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole:
|
||||
case Qt::ToolTipRole:
|
||||
case Qt::StatusTipRole:
|
||||
case Qt::WhatsThisRole:
|
||||
return node->text();
|
||||
case Qt::CheckStateRole:
|
||||
if (node->isEnabled())
|
||||
return Qt::Checked;
|
||||
else
|
||||
return Qt::Unchecked;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool FilterTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
FilterTreeNode *node;
|
||||
|
||||
if (!index.isValid())
|
||||
return false;
|
||||
if (index.column() >= columnCount())
|
||||
return false;
|
||||
if (role != Qt::CheckStateRole)
|
||||
return false;
|
||||
|
||||
node = indexToNode(index);
|
||||
if (node == NULL || node == fTree)
|
||||
return false;
|
||||
|
||||
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
|
||||
if (state == Qt::Checked)
|
||||
node->enable();
|
||||
else
|
||||
node->disable();
|
||||
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags FilterTreeModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
const FilterTreeNode *node;
|
||||
Qt::ItemFlags result;
|
||||
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
|
||||
node = indexToNode(index);
|
||||
if (node == NULL)
|
||||
return Qt::NoItemFlags;
|
||||
|
||||
result = Qt::ItemIsEnabled;
|
||||
if (node == fTree)
|
||||
return result;
|
||||
|
||||
result |= Qt::ItemIsSelectable;
|
||||
result |= Qt::ItemIsUserCheckable;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QModelIndex FilterTreeModel::nodeIndex(const FilterTreeNode *node, int row, int column) const
|
||||
{
|
||||
FilterTreeNode *child;
|
||||
|
||||
if (column > 0 || row >= node->childCount())
|
||||
return QModelIndex();
|
||||
|
||||
child = node->nodeAt(row);
|
||||
return createIndex(row, column, child);
|
||||
}
|
||||
|
||||
QModelIndex FilterTreeModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
const FilterTreeNode *node;
|
||||
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
node = indexToNode(parent);
|
||||
if (node == NULL)
|
||||
return QModelIndex();
|
||||
|
||||
return nodeIndex(node, row, column);
|
||||
}
|
||||
|
||||
QModelIndex FilterTreeModel::parent(const QModelIndex &ind) const
|
||||
{
|
||||
const FilterTreeNode *node;
|
||||
FilterTreeNode *parent;
|
||||
QModelIndex idx;
|
||||
|
||||
if (!ind.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
node = indexToNode(ind);
|
||||
if (node == NULL || node == fTree)
|
||||
return QModelIndex();
|
||||
|
||||
parent = node->parent();
|
||||
if (parent) {
|
||||
int row = parent->index();
|
||||
if (row < 0)
|
||||
return QModelIndex();
|
||||
idx = createIndex(row, 0, parent);
|
||||
return idx;
|
||||
}
|
||||
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
bool FilterTreeModel::removeRows(int row, int count, const QModelIndex &parent)
|
||||
{
|
||||
FilterTreeNode *node;
|
||||
int i, last;
|
||||
|
||||
last = row + count - 1;
|
||||
if (!parent.isValid() || count < 1 || row < 0)
|
||||
return false;
|
||||
|
||||
node = indexToNode(parent);
|
||||
if (node == NULL || last >= node->childCount())
|
||||
return false;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
node->deleteAt(row);
|
||||
|
||||
if (node != fTree && node->childCount() < 1)
|
||||
return removeRow(parent.row(), parent.parent());
|
||||
|
||||
return true;
|
||||
}
|
||||
46
cockatrice/src/game/filters/filter_tree_model.h
Normal file
46
cockatrice/src/game/filters/filter_tree_model.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#ifndef FILTERTREEMODEL_H
|
||||
#define FILTERTREEMODEL_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
class FilterTree;
|
||||
class CardFilter;
|
||||
class FilterTreeNode;
|
||||
|
||||
class FilterTreeModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
FilterTree *fTree;
|
||||
|
||||
public slots:
|
||||
void addFilter(const CardFilter *f);
|
||||
|
||||
private slots:
|
||||
void proxyBeginInsertRow(const FilterTreeNode *, int);
|
||||
void proxyEndInsertRow(const FilterTreeNode *, int);
|
||||
void proxyBeginRemoveRow(const FilterTreeNode *, int);
|
||||
void proxyEndRemoveRow(const FilterTreeNode *, int);
|
||||
|
||||
private:
|
||||
FilterTreeNode *indexToNode(const QModelIndex &idx) const;
|
||||
QModelIndex nodeIndex(const FilterTreeNode *node, int row, int column) const;
|
||||
|
||||
public:
|
||||
FilterTreeModel(QObject *parent = nullptr);
|
||||
~FilterTreeModel();
|
||||
FilterTree *filterTree() const
|
||||
{
|
||||
return fTree;
|
||||
}
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const;
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QModelIndex parent(const QModelIndex &ind) const;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent) const;
|
||||
bool removeRows(int row, int count, const QModelIndex &parent);
|
||||
};
|
||||
|
||||
#endif
|
||||
331
cockatrice/src/game/game_scene.cpp
Normal file
331
cockatrice/src/game/game_scene.cpp
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
#include "game_scene.h"
|
||||
|
||||
#include "../client/ui/phases_toolbar.h"
|
||||
#include "../settings/cache_settings.h"
|
||||
#include "cards/card_item.h"
|
||||
#include "player/player.h"
|
||||
#include "zones/view_zone.h"
|
||||
#include "zones/view_zone_widget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QBasicTimer>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGraphicsView>
|
||||
#include <QSet>
|
||||
#include <QtMath>
|
||||
|
||||
GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
|
||||
: QGraphicsScene(parent), phasesToolbar(_phasesToolbar), viewSize(QSize()), playerRotation(0)
|
||||
{
|
||||
animationTimer = new QBasicTimer;
|
||||
addItem(phasesToolbar);
|
||||
connect(&SettingsCache::instance(), SIGNAL(minPlayersForMultiColumnLayoutChanged()), this, SLOT(rearrange()));
|
||||
|
||||
rearrange();
|
||||
}
|
||||
|
||||
GameScene::~GameScene()
|
||||
{
|
||||
delete animationTimer;
|
||||
}
|
||||
|
||||
void GameScene::retranslateUi()
|
||||
{
|
||||
for (int i = 0; i < zoneViews.size(); ++i)
|
||||
zoneViews[i]->retranslateUi();
|
||||
}
|
||||
|
||||
void GameScene::addPlayer(Player *player)
|
||||
{
|
||||
qDebug() << "GameScene::addPlayer name=" << player->getName();
|
||||
players << player;
|
||||
addItem(player);
|
||||
connect(player, SIGNAL(sizeChanged()), this, SLOT(rearrange()));
|
||||
connect(player, SIGNAL(playerCountChanged()), this, SLOT(rearrange()));
|
||||
}
|
||||
|
||||
void GameScene::removePlayer(Player *player)
|
||||
{
|
||||
qDebug() << "GameScene::removePlayer name=" << player->getName();
|
||||
for (ZoneViewWidget *zone : zoneViews) {
|
||||
if (zone->getPlayer() == player) {
|
||||
zone->close();
|
||||
}
|
||||
}
|
||||
players.removeOne(player);
|
||||
removeItem(player);
|
||||
rearrange();
|
||||
}
|
||||
|
||||
void GameScene::adjustPlayerRotation(int rotationAdjustment)
|
||||
{
|
||||
playerRotation += rotationAdjustment;
|
||||
rearrange();
|
||||
}
|
||||
|
||||
void GameScene::rearrange()
|
||||
{
|
||||
playersByColumn.clear();
|
||||
|
||||
// Create the list of players playing, noting the first player's index.
|
||||
QList<Player *> playersPlaying;
|
||||
int firstPlayerIndex = 0;
|
||||
bool firstPlayerFound = false;
|
||||
QListIterator<Player *> playersIter(players);
|
||||
while (playersIter.hasNext()) {
|
||||
Player *p = playersIter.next();
|
||||
if (!p->getConceded()) {
|
||||
playersPlaying.append(p);
|
||||
if (!firstPlayerFound && (p->getLocal())) {
|
||||
firstPlayerIndex = playersPlaying.size() - 1;
|
||||
firstPlayerFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate the players playing list so that first player is first, then
|
||||
// adjust by the additional rotation setting.
|
||||
if (!playersPlaying.isEmpty()) {
|
||||
int totalRotation = firstPlayerIndex + playerRotation;
|
||||
while (totalRotation < 0)
|
||||
totalRotation += playersPlaying.size();
|
||||
for (int i = 0; i < totalRotation; ++i) {
|
||||
playersPlaying.append(playersPlaying.takeFirst());
|
||||
}
|
||||
}
|
||||
|
||||
const int playersCount = playersPlaying.size();
|
||||
const int columns = playersCount < SettingsCache::instance().getMinPlayersForMultiColumnLayout() ? 1 : 2;
|
||||
const int rows = qCeil((qreal)playersCount / columns);
|
||||
qreal sceneHeight = 0, sceneWidth = -playerAreaSpacing;
|
||||
QList<int> columnWidth;
|
||||
|
||||
QListIterator<Player *> playersPlayingIter(playersPlaying);
|
||||
for (int col = 0; col < columns; ++col) {
|
||||
playersByColumn.append(QList<Player *>());
|
||||
columnWidth.append(0);
|
||||
qreal thisColumnHeight = -playerAreaSpacing;
|
||||
const int rowsInColumn = rows - (playersCount % columns) * col; // only correct for max. 2 cols
|
||||
for (int j = 0; j < rowsInColumn; ++j) {
|
||||
Player *player = playersPlayingIter.next();
|
||||
if (col == 0)
|
||||
playersByColumn[col].prepend(player);
|
||||
else
|
||||
playersByColumn[col].append(player);
|
||||
thisColumnHeight += player->boundingRect().height() + playerAreaSpacing;
|
||||
if (player->boundingRect().width() > columnWidth[col])
|
||||
columnWidth[col] = player->boundingRect().width();
|
||||
}
|
||||
if (thisColumnHeight > sceneHeight)
|
||||
sceneHeight = thisColumnHeight;
|
||||
sceneWidth += columnWidth[col] + playerAreaSpacing;
|
||||
}
|
||||
|
||||
phasesToolbar->setHeight(sceneHeight);
|
||||
qreal phasesWidth = phasesToolbar->getWidth();
|
||||
sceneWidth += phasesWidth;
|
||||
|
||||
qreal x = phasesWidth;
|
||||
for (int col = 0; col < columns; ++col) {
|
||||
qreal y = 0;
|
||||
for (int row = 0; row < playersByColumn[col].size(); ++row) {
|
||||
Player *player = playersByColumn[col][row];
|
||||
player->setPos(x, y);
|
||||
player->setMirrored(row != rows - 1);
|
||||
y += player->boundingRect().height() + playerAreaSpacing;
|
||||
}
|
||||
x += columnWidth[col] + playerAreaSpacing;
|
||||
}
|
||||
|
||||
setSceneRect(sceneRect().x(), sceneRect().y(), sceneWidth, sceneHeight);
|
||||
processViewSizeChange(viewSize);
|
||||
}
|
||||
|
||||
void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numberCards)
|
||||
{
|
||||
for (auto &view : zoneViews) {
|
||||
ZoneViewZone *temp = view->getZone();
|
||||
if (temp->getName() == zoneName && temp->getPlayer() == player && temp->getNumberCards() == numberCards) {
|
||||
view->close();
|
||||
}
|
||||
}
|
||||
|
||||
ZoneViewWidget *item = new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false);
|
||||
zoneViews.append(item);
|
||||
connect(item, SIGNAL(closePressed(ZoneViewWidget *)), this, SLOT(removeZoneView(ZoneViewWidget *)));
|
||||
addItem(item);
|
||||
if (zoneName == "grave") {
|
||||
item->setPos(360, 100);
|
||||
} else if (zoneName == "rfg") {
|
||||
item->setPos(380, 120);
|
||||
} else {
|
||||
item->setPos(340, 80);
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::addRevealedZoneView(Player *player,
|
||||
CardZone *zone,
|
||||
const QList<const ServerInfo_Card *> &cardList,
|
||||
bool withWritePermission)
|
||||
{
|
||||
ZoneViewWidget *item = new ZoneViewWidget(player, zone, -2, true, withWritePermission, cardList);
|
||||
zoneViews.append(item);
|
||||
connect(item, SIGNAL(closePressed(ZoneViewWidget *)), this, SLOT(removeZoneView(ZoneViewWidget *)));
|
||||
addItem(item);
|
||||
item->setPos(600, 80);
|
||||
}
|
||||
|
||||
void GameScene::removeZoneView(ZoneViewWidget *item)
|
||||
{
|
||||
zoneViews.removeOne(item);
|
||||
removeItem(item);
|
||||
}
|
||||
|
||||
void GameScene::clearViews()
|
||||
{
|
||||
while (!zoneViews.isEmpty())
|
||||
zoneViews.first()->close();
|
||||
}
|
||||
|
||||
void GameScene::closeMostRecentZoneView()
|
||||
{
|
||||
if (!zoneViews.isEmpty())
|
||||
zoneViews.last()->close();
|
||||
}
|
||||
|
||||
QTransform GameScene::getViewTransform() const
|
||||
{
|
||||
return views().at(0)->transform();
|
||||
}
|
||||
|
||||
QTransform GameScene::getViewportTransform() const
|
||||
{
|
||||
return views().at(0)->viewportTransform();
|
||||
}
|
||||
|
||||
void GameScene::processViewSizeChange(const QSize &newSize)
|
||||
{
|
||||
viewSize = newSize;
|
||||
|
||||
qreal newRatio = ((qreal)newSize.width()) / newSize.height();
|
||||
qreal minWidth = 0;
|
||||
QList<qreal> minWidthByColumn;
|
||||
for (int col = 0; col < playersByColumn.size(); ++col) {
|
||||
minWidthByColumn.append(0);
|
||||
for (int row = 0; row < playersByColumn[col].size(); ++row) {
|
||||
qreal w = playersByColumn[col][row]->getMinimumWidth();
|
||||
if (w > minWidthByColumn[col])
|
||||
minWidthByColumn[col] = w;
|
||||
}
|
||||
minWidth += minWidthByColumn[col];
|
||||
}
|
||||
minWidth += phasesToolbar->getWidth();
|
||||
|
||||
qreal minRatio = minWidth / sceneRect().height();
|
||||
qreal newWidth;
|
||||
if (minRatio > newRatio) {
|
||||
// Aspect ratio is dominated by table width.
|
||||
newWidth = minWidth;
|
||||
} else {
|
||||
// Aspect ratio is dominated by window dimensions.
|
||||
newWidth = newRatio * sceneRect().height();
|
||||
}
|
||||
setSceneRect(0, 0, newWidth, sceneRect().height());
|
||||
|
||||
qreal extraWidthPerColumn = (newWidth - minWidth) / playersByColumn.size();
|
||||
qreal newx = phasesToolbar->getWidth();
|
||||
for (int col = 0; col < playersByColumn.size(); ++col) {
|
||||
for (int row = 0; row < playersByColumn[col].size(); ++row) {
|
||||
playersByColumn[col][row]->processSceneSizeChange(minWidthByColumn[col] + extraWidthPerColumn);
|
||||
playersByColumn[col][row]->setPos(newx, playersByColumn[col][row]->y());
|
||||
}
|
||||
newx += minWidthByColumn[col] + extraWidthPerColumn;
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::updateHover(const QPointF &scenePos)
|
||||
{
|
||||
QList<QGraphicsItem *> itemList =
|
||||
items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform());
|
||||
|
||||
// Search for the topmost zone and ignore all cards not belonging to that zone.
|
||||
CardZone *zone = 0;
|
||||
for (int i = 0; i < itemList.size(); ++i)
|
||||
if ((zone = qgraphicsitem_cast<CardZone *>(itemList[i])))
|
||||
break;
|
||||
|
||||
CardItem *maxZCard = 0;
|
||||
if (zone) {
|
||||
qreal maxZ = -1;
|
||||
for (int i = 0; i < itemList.size(); ++i) {
|
||||
CardItem *card = qgraphicsitem_cast<CardItem *>(itemList[i]);
|
||||
if (!card)
|
||||
continue;
|
||||
if (card->getAttachedTo()) {
|
||||
if (card->getAttachedTo()->getZone() != zone)
|
||||
continue;
|
||||
} else if (card->getZone() != zone)
|
||||
continue;
|
||||
|
||||
if (card->getRealZValue() > maxZ) {
|
||||
maxZ = card->getRealZValue();
|
||||
maxZCard = card;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hoveredCard && (maxZCard != hoveredCard))
|
||||
hoveredCard->setHovered(false);
|
||||
if (maxZCard && (maxZCard != hoveredCard))
|
||||
maxZCard->setHovered(true);
|
||||
hoveredCard = maxZCard;
|
||||
}
|
||||
|
||||
bool GameScene::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::GraphicsSceneMouseMove)
|
||||
updateHover(static_cast<QGraphicsSceneMouseEvent *>(event)->scenePos());
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void GameScene::startRubberBand(const QPointF &selectionOrigin)
|
||||
{
|
||||
emit sigStartRubberBand(selectionOrigin);
|
||||
}
|
||||
|
||||
void GameScene::resizeRubberBand(const QPointF &cursorPoint)
|
||||
{
|
||||
emit sigResizeRubberBand(cursorPoint);
|
||||
}
|
||||
|
||||
void GameScene::stopRubberBand()
|
||||
{
|
||||
emit sigStopRubberBand();
|
||||
}
|
||||
72
cockatrice/src/game/game_scene.h
Normal file
72
cockatrice/src/game/game_scene.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#ifndef GAMESCENE_H
|
||||
#define GAMESCENE_H
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
|
||||
class Player;
|
||||
class ZoneViewWidget;
|
||||
class CardZone;
|
||||
class AbstractCardItem;
|
||||
class CardItem;
|
||||
class ServerInfo_Card;
|
||||
class PhasesToolbar;
|
||||
class QBasicTimer;
|
||||
|
||||
class GameScene : public QGraphicsScene
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
static const int playerAreaSpacing = 5;
|
||||
|
||||
PhasesToolbar *phasesToolbar;
|
||||
QList<Player *> players;
|
||||
QList<QList<Player *>> playersByColumn;
|
||||
QList<ZoneViewWidget *> zoneViews;
|
||||
QSize viewSize;
|
||||
QPointer<CardItem> hoveredCard;
|
||||
QBasicTimer *animationTimer;
|
||||
QSet<CardItem *> cardsToAnimate;
|
||||
int playerRotation;
|
||||
void updateHover(const QPointF &scenePos);
|
||||
|
||||
public:
|
||||
GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr);
|
||||
~GameScene();
|
||||
void retranslateUi();
|
||||
void processViewSizeChange(const QSize &newSize);
|
||||
QTransform getViewTransform() const;
|
||||
QTransform getViewportTransform() const;
|
||||
|
||||
void startRubberBand(const QPointF &selectionOrigin);
|
||||
void resizeRubberBand(const QPointF &cursorPoint);
|
||||
void stopRubberBand();
|
||||
|
||||
void registerAnimationItem(AbstractCardItem *item);
|
||||
void unregisterAnimationItem(AbstractCardItem *card);
|
||||
public slots:
|
||||
void toggleZoneView(Player *player, const QString &zoneName, int numberCards);
|
||||
void addRevealedZoneView(Player *player,
|
||||
CardZone *zone,
|
||||
const QList<const ServerInfo_Card *> &cardList,
|
||||
bool withWritePermission);
|
||||
void removeZoneView(ZoneViewWidget *item);
|
||||
void addPlayer(Player *player);
|
||||
void removePlayer(Player *player);
|
||||
void clearViews();
|
||||
void closeMostRecentZoneView();
|
||||
void adjustPlayerRotation(int rotationAdjustment);
|
||||
void rearrange();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event);
|
||||
void timerEvent(QTimerEvent *event);
|
||||
signals:
|
||||
void sigStartRubberBand(const QPointF &selectionOrigin);
|
||||
void sigResizeRubberBand(const QPointF &cursorPoint);
|
||||
void sigStopRubberBand();
|
||||
};
|
||||
|
||||
#endif
|
||||
345
cockatrice/src/game/game_selector.cpp
Normal file
345
cockatrice/src/game/game_selector.cpp
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
#include "game_selector.h"
|
||||
|
||||
#include "../client/game_logic/abstract_client.h"
|
||||
#include "../client/get_text_with_max.h"
|
||||
#include "../client/tabs/tab_account.h"
|
||||
#include "../client/tabs/tab_game.h"
|
||||
#include "../client/tabs/tab_room.h"
|
||||
#include "../client/tabs/tab_supervisor.h"
|
||||
#include "../dialogs/dlg_create_game.h"
|
||||
#include "../dialogs/dlg_filter_games.h"
|
||||
#include "../server/pending_command.h"
|
||||
#include "games_model.h"
|
||||
#include "pb/response.pb.h"
|
||||
#include "pb/room_commands.pb.h"
|
||||
#include "pb/serverinfo_game.pb.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
GameSelector::GameSelector(AbstractClient *_client,
|
||||
TabSupervisor *_tabSupervisor,
|
||||
TabRoom *_room,
|
||||
const QMap<int, QString> &_rooms,
|
||||
const QMap<int, GameTypeMap> &_gameTypes,
|
||||
const bool restoresettings,
|
||||
const bool _showfilters,
|
||||
QWidget *parent)
|
||||
: QGroupBox(parent), client(_client), tabSupervisor(_tabSupervisor), room(_room), showFilters(_showfilters)
|
||||
{
|
||||
gameListView = new QTreeView;
|
||||
gameListModel = new GamesModel(_rooms, _gameTypes, this);
|
||||
if (showFilters) {
|
||||
gameListProxyModel = new GamesProxyModel(this, tabSupervisor);
|
||||
gameListProxyModel->setSourceModel(gameListModel);
|
||||
gameListProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
gameListView->setModel(gameListProxyModel);
|
||||
} else {
|
||||
gameListView->setModel(gameListModel);
|
||||
}
|
||||
gameListView->setSortingEnabled(true);
|
||||
gameListView->sortByColumn(gameListModel->startTimeColIndex(), Qt::AscendingOrder);
|
||||
gameListView->setAlternatingRowColors(true);
|
||||
gameListView->setRootIsDecorated(true);
|
||||
// game created width
|
||||
gameListView->setColumnWidth(1, gameListView->columnWidth(2) * 0.7);
|
||||
// players width
|
||||
gameListView->resizeColumnToContents(6);
|
||||
// description width
|
||||
gameListView->setColumnWidth(2, gameListView->columnWidth(2) * 1.7);
|
||||
// creator width
|
||||
gameListView->setColumnWidth(3, gameListView->columnWidth(3) * 1.2);
|
||||
// game type width
|
||||
gameListView->setColumnWidth(4, gameListView->columnWidth(4) * 1.4);
|
||||
if (_room)
|
||||
gameListView->header()->hideSection(gameListModel->roomColIndex());
|
||||
|
||||
if (room)
|
||||
gameTypeMap = gameListModel->getGameTypes().value(room->getRoomId());
|
||||
|
||||
if (showFilters && restoresettings)
|
||||
gameListProxyModel->loadFilterParameters(gameTypeMap);
|
||||
|
||||
gameListView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
|
||||
filterButton = new QPushButton;
|
||||
filterButton->setIcon(QPixmap("theme:icons/search"));
|
||||
connect(filterButton, SIGNAL(clicked()), this, SLOT(actSetFilter()));
|
||||
clearFilterButton = new QPushButton;
|
||||
clearFilterButton->setIcon(QPixmap("theme:icons/clearsearch"));
|
||||
bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults();
|
||||
clearFilterButton->setEnabled(!filtersSetToDefault);
|
||||
connect(clearFilterButton, SIGNAL(clicked()), this, SLOT(actClearFilter()));
|
||||
|
||||
if (room) {
|
||||
createButton = new QPushButton;
|
||||
connect(createButton, SIGNAL(clicked()), this, SLOT(actCreate()));
|
||||
} else {
|
||||
createButton = nullptr;
|
||||
}
|
||||
joinButton = new QPushButton;
|
||||
spectateButton = new QPushButton;
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
if (showFilters) {
|
||||
buttonLayout->addWidget(filterButton);
|
||||
buttonLayout->addWidget(clearFilterButton);
|
||||
}
|
||||
buttonLayout->addStretch();
|
||||
if (room)
|
||||
buttonLayout->addWidget(createButton);
|
||||
buttonLayout->addWidget(joinButton);
|
||||
buttonLayout->addWidget(spectateButton);
|
||||
buttonLayout->setAlignment(Qt::AlignTop);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(gameListView);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
|
||||
retranslateUi();
|
||||
setLayout(mainLayout);
|
||||
|
||||
setMinimumWidth((qreal)(gameListView->columnWidth(0) * gameListModel->columnCount()) / 1.5);
|
||||
setMinimumHeight(200);
|
||||
|
||||
connect(joinButton, SIGNAL(clicked()), this, SLOT(actJoin()));
|
||||
connect(spectateButton, SIGNAL(clicked()), this, SLOT(actJoin()));
|
||||
connect(gameListView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
|
||||
SLOT(actSelectedGameChanged(const QModelIndex &, const QModelIndex &)));
|
||||
connect(gameListView, SIGNAL(activated(const QModelIndex &)), this, SLOT(actJoin()));
|
||||
|
||||
connect(client, SIGNAL(ignoreListReceived(const QList<ServerInfo_User> &)), this,
|
||||
SLOT(ignoreListReceived(const QList<ServerInfo_User> &)));
|
||||
connect(client, SIGNAL(addToListEventReceived(const Event_AddToList &)), this,
|
||||
SLOT(processAddToListEvent(const Event_AddToList &)));
|
||||
connect(client, SIGNAL(removeFromListEventReceived(const Event_RemoveFromList &)), this,
|
||||
SLOT(processRemoveFromListEvent(const Event_RemoveFromList &)));
|
||||
}
|
||||
|
||||
void GameSelector::ignoreListReceived(const QList<ServerInfo_User> &)
|
||||
{
|
||||
gameListProxyModel->refresh();
|
||||
}
|
||||
|
||||
void GameSelector::processAddToListEvent(const Event_AddToList &event)
|
||||
{
|
||||
if (event.list_name() == "ignore") {
|
||||
gameListProxyModel->refresh();
|
||||
}
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::processRemoveFromListEvent(const Event_RemoveFromList &event)
|
||||
{
|
||||
if (event.list_name() == "ignore") {
|
||||
gameListProxyModel->refresh();
|
||||
}
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::actSetFilter()
|
||||
{
|
||||
DlgFilterGames dlg(gameTypeMap, gameListProxyModel, this);
|
||||
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
gameListProxyModel->setShowBuddiesOnlyGames(dlg.getShowBuddiesOnlyGames());
|
||||
gameListProxyModel->setShowFullGames(dlg.getShowFullGames());
|
||||
gameListProxyModel->setShowGamesThatStarted(dlg.getShowGamesThatStarted());
|
||||
gameListProxyModel->setShowPasswordProtectedGames(dlg.getShowPasswordProtectedGames());
|
||||
gameListProxyModel->setHideIgnoredUserGames(dlg.getHideIgnoredUserGames());
|
||||
gameListProxyModel->setGameNameFilter(dlg.getGameNameFilter());
|
||||
gameListProxyModel->setCreatorNameFilter(dlg.getCreatorNameFilter());
|
||||
gameListProxyModel->setGameTypeFilter(dlg.getGameTypeFilter());
|
||||
gameListProxyModel->setMaxPlayersFilter(dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax());
|
||||
gameListProxyModel->setMaxGameAge(dlg.getMaxGameAge());
|
||||
gameListProxyModel->setShowOnlyIfSpectatorsCanWatch(dlg.getShowOnlyIfSpectatorsCanWatch());
|
||||
gameListProxyModel->setShowSpectatorPasswordProtected(dlg.getShowSpectatorPasswordProtected());
|
||||
gameListProxyModel->setShowOnlyIfSpectatorsCanChat(dlg.getShowOnlyIfSpectatorsCanChat());
|
||||
gameListProxyModel->setShowOnlyIfSpectatorsCanSeeHands(dlg.getShowOnlyIfSpectatorsCanSeeHands());
|
||||
gameListProxyModel->saveFilterParameters(gameTypeMap);
|
||||
|
||||
clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults());
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::actClearFilter()
|
||||
{
|
||||
clearFilterButton->setEnabled(false);
|
||||
|
||||
gameListProxyModel->resetFilterParameters();
|
||||
gameListProxyModel->saveFilterParameters(gameTypeMap);
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::actCreate()
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qWarning() << "Attempted to create game, but the room was null";
|
||||
return;
|
||||
}
|
||||
|
||||
DlgCreateGame dlg(room, room->getGameTypes(), this);
|
||||
dlg.exec();
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::checkResponse(const Response &response)
|
||||
{
|
||||
// NB: We re-enable buttons for the currently selected game, which may not
|
||||
// be the same game as the one for which we are processing a response.
|
||||
// This could lead to situations where we join a game, select a different
|
||||
// game, join the new game, then receive the response for the original
|
||||
// join, which would re-activate the buttons for the second game too soon.
|
||||
// However, that is better than doing things the other ways, because then
|
||||
// the response to the first game could lock us out of join/join as
|
||||
// spectator for the second game!
|
||||
//
|
||||
// Ideally we should have a way to figure out if the current game is the
|
||||
// same as the one we are getting a response for, but it's probably not
|
||||
// worth the trouble.
|
||||
enableButtons();
|
||||
|
||||
switch (response.response_code()) {
|
||||
case Response::RespNotInRoom:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please join the appropriate room first."));
|
||||
break;
|
||||
case Response::RespWrongPassword:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Wrong password."));
|
||||
break;
|
||||
case Response::RespSpectatorsNotAllowed:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Spectators are not allowed in this game."));
|
||||
break;
|
||||
case Response::RespGameFull:
|
||||
QMessageBox::critical(this, tr("Error"), tr("The game is already full."));
|
||||
break;
|
||||
case Response::RespNameNotFound:
|
||||
QMessageBox::critical(this, tr("Error"), tr("The game does not exist any more."));
|
||||
break;
|
||||
case Response::RespUserLevelTooLow:
|
||||
QMessageBox::critical(this, tr("Error"), tr("This game is only open to registered users."));
|
||||
break;
|
||||
case Response::RespOnlyBuddies:
|
||||
QMessageBox::critical(this, tr("Error"), tr("This game is only open to its creator's buddies."));
|
||||
break;
|
||||
case Response::RespInIgnoreList:
|
||||
QMessageBox::critical(this, tr("Error"), tr("You are being ignored by the creator of this game."));
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void GameSelector::actJoin()
|
||||
{
|
||||
QModelIndex ind = gameListView->currentIndex();
|
||||
if (!ind.isValid())
|
||||
return;
|
||||
const ServerInfo_Game &game = gameListModel->getGame(ind.data(Qt::UserRole).toInt());
|
||||
if (tabSupervisor->switchToGameTabIfAlreadyExists(game.game_id())) {
|
||||
return;
|
||||
}
|
||||
bool spectator = sender() == spectateButton || game.player_count() == game.max_players();
|
||||
bool overrideRestrictions = !tabSupervisor->getAdminLocked();
|
||||
QString password;
|
||||
if (game.with_password() && !(spectator && !game.spectators_need_password()) && !overrideRestrictions) {
|
||||
bool ok;
|
||||
password = getTextWithMax(this, tr("Join game"), tr("Password:"), QLineEdit::Password, QString(), &ok);
|
||||
if (!ok)
|
||||
return;
|
||||
}
|
||||
|
||||
Command_JoinGame cmd;
|
||||
cmd.set_game_id(game.game_id());
|
||||
cmd.set_password(password.toStdString());
|
||||
cmd.set_spectator(spectator);
|
||||
cmd.set_override_restrictions(overrideRestrictions);
|
||||
cmd.set_join_as_judge((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0);
|
||||
|
||||
TabRoom *r = tabSupervisor->getRoomTabs().value(game.room_id());
|
||||
if (!r) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please join the respective room first."));
|
||||
return;
|
||||
}
|
||||
|
||||
PendingCommand *pend = r->prepareRoomCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(checkResponse(Response)));
|
||||
r->sendRoomCommand(pend);
|
||||
|
||||
disableButtons();
|
||||
}
|
||||
|
||||
void GameSelector::disableButtons()
|
||||
{
|
||||
if (createButton)
|
||||
createButton->setEnabled(false);
|
||||
|
||||
joinButton->setEnabled(false);
|
||||
spectateButton->setEnabled(false);
|
||||
}
|
||||
|
||||
void GameSelector::enableButtons()
|
||||
{
|
||||
if (createButton)
|
||||
createButton->setEnabled(true);
|
||||
|
||||
// Enable buttons for the currently selected game
|
||||
enableButtonsForIndex(gameListView->currentIndex());
|
||||
}
|
||||
|
||||
void GameSelector::enableButtonsForIndex(const QModelIndex ¤t)
|
||||
{
|
||||
if (!current.isValid())
|
||||
return;
|
||||
|
||||
const ServerInfo_Game &game = gameListModel->getGame(current.data(Qt::UserRole).toInt());
|
||||
bool overrideRestrictions = !tabSupervisor->getAdminLocked();
|
||||
|
||||
spectateButton->setEnabled(game.spectators_allowed() || overrideRestrictions);
|
||||
joinButton->setEnabled(game.player_count() < game.max_players() || overrideRestrictions);
|
||||
}
|
||||
|
||||
void GameSelector::retranslateUi()
|
||||
{
|
||||
filterButton->setText(tr("&Filter games"));
|
||||
clearFilterButton->setText(tr("C&lear filter"));
|
||||
if (createButton)
|
||||
createButton->setText(tr("C&reate"));
|
||||
joinButton->setText(tr("&Join"));
|
||||
spectateButton->setText(tr("J&oin as spectator"));
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::processGameInfo(const ServerInfo_Game &info)
|
||||
{
|
||||
gameListModel->updateGameList(info);
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::actSelectedGameChanged(const QModelIndex ¤t, const QModelIndex & /* previous */)
|
||||
{
|
||||
enableButtonsForIndex(current);
|
||||
}
|
||||
|
||||
void GameSelector::updateTitle()
|
||||
{
|
||||
if (showFilters) {
|
||||
const int totalGames = gameListModel->rowCount();
|
||||
const int shownGames = totalGames - gameListProxyModel->getNumFilteredGames();
|
||||
setTitle(tr("Games shown: %1 / %2").arg(shownGames).arg(totalGames));
|
||||
} else {
|
||||
setTitle(tr("Games"));
|
||||
}
|
||||
}
|
||||
69
cockatrice/src/game/game_selector.h
Normal file
69
cockatrice/src/game/game_selector.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef GAMESELECTOR_H
|
||||
#define GAMESELECTOR_H
|
||||
|
||||
#include "game_type_map.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <common/pb/event_add_to_list.pb.h>
|
||||
#include <common/pb/event_remove_from_list.pb.h>
|
||||
|
||||
class QTreeView;
|
||||
class GamesModel;
|
||||
class GamesProxyModel;
|
||||
class QPushButton;
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
class AbstractClient;
|
||||
class TabSupervisor;
|
||||
class TabRoom;
|
||||
class ServerInfo_Game;
|
||||
class Response;
|
||||
|
||||
class GameSelector : public QGroupBox
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void actSetFilter();
|
||||
void actClearFilter();
|
||||
void actCreate();
|
||||
void actJoin();
|
||||
void actSelectedGameChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void checkResponse(const Response &response);
|
||||
|
||||
void ignoreListReceived(const QList<ServerInfo_User> &_ignoreList);
|
||||
void processAddToListEvent(const Event_AddToList &event);
|
||||
void processRemoveFromListEvent(const Event_RemoveFromList &event);
|
||||
signals:
|
||||
void gameJoined(int gameId);
|
||||
|
||||
private:
|
||||
AbstractClient *client;
|
||||
TabSupervisor *tabSupervisor;
|
||||
TabRoom *room;
|
||||
|
||||
QTreeView *gameListView;
|
||||
GamesModel *gameListModel;
|
||||
GamesProxyModel *gameListProxyModel;
|
||||
QPushButton *filterButton, *clearFilterButton, *createButton, *joinButton, *spectateButton;
|
||||
const bool showFilters;
|
||||
GameTypeMap gameTypeMap;
|
||||
|
||||
void updateTitle();
|
||||
void disableButtons();
|
||||
void enableButtons();
|
||||
void enableButtonsForIndex(const QModelIndex ¤t);
|
||||
|
||||
public:
|
||||
GameSelector(AbstractClient *_client,
|
||||
TabSupervisor *_tabSupervisor,
|
||||
TabRoom *_room,
|
||||
const QMap<int, QString> &_rooms,
|
||||
const QMap<int, GameTypeMap> &_gameTypes,
|
||||
const bool restoresettings,
|
||||
const bool _showfilters,
|
||||
QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
void processGameInfo(const ServerInfo_Game &info);
|
||||
};
|
||||
|
||||
#endif
|
||||
52
cockatrice/src/game/game_specific_terms.h
Normal file
52
cockatrice/src/game/game_specific_terms.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef GAME_SPECIFIC_TERMS_H
|
||||
#define GAME_SPECIFIC_TERMS_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
|
||||
/*
|
||||
* Collection of traslatable property names used in games,
|
||||
* so we can use Game::Property instead of hardcoding strings.
|
||||
* Note: Mtg = "Maybe that game"
|
||||
*/
|
||||
|
||||
namespace Mtg
|
||||
{
|
||||
QString const CardType("type");
|
||||
QString const ConvertedManaCost("cmc");
|
||||
QString const Colors("colors");
|
||||
QString const Loyalty("loyalty");
|
||||
QString const MainCardType("maintype");
|
||||
QString const ManaCost("manacost");
|
||||
QString const PowTough("pt");
|
||||
QString const Side("side");
|
||||
QString const Layout("layout");
|
||||
QString const ColorIdentity("coloridentity");
|
||||
|
||||
inline static const QString getNicePropertyName(QString key)
|
||||
{
|
||||
if (key == CardType)
|
||||
return QCoreApplication::translate("Mtg", "Card Type");
|
||||
if (key == ConvertedManaCost)
|
||||
return QCoreApplication::translate("Mtg", "Mana Value");
|
||||
if (key == Colors)
|
||||
return QCoreApplication::translate("Mtg", "Color(s)");
|
||||
if (key == Loyalty)
|
||||
return QCoreApplication::translate("Mtg", "Loyalty");
|
||||
if (key == MainCardType)
|
||||
return QCoreApplication::translate("Mtg", "Main Card Type");
|
||||
if (key == ManaCost)
|
||||
return QCoreApplication::translate("Mtg", "Mana Cost");
|
||||
if (key == PowTough)
|
||||
return QCoreApplication::translate("Mtg", "P/T");
|
||||
if (key == Side)
|
||||
return QCoreApplication::translate("Mtg", "Side");
|
||||
if (key == Layout)
|
||||
return QCoreApplication::translate("Mtg", "Layout");
|
||||
if (key == ColorIdentity)
|
||||
return QCoreApplication::translate("Mtg", "Color Identity");
|
||||
return key;
|
||||
}
|
||||
}; // namespace Mtg
|
||||
|
||||
#endif
|
||||
8
cockatrice/src/game/game_type_map.h
Normal file
8
cockatrice/src/game/game_type_map.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef GAMETYPEMAP_H
|
||||
#define GAMETYPEMAP_H
|
||||
|
||||
#include <QMap>
|
||||
|
||||
typedef QMap<int, QString> GameTypeMap;
|
||||
|
||||
#endif
|
||||
70
cockatrice/src/game/game_view.cpp
Normal file
70
cockatrice/src/game/game_view.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#include "game_view.h"
|
||||
|
||||
#include "../settings/cache_settings.h"
|
||||
#include "game_scene.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QResizeEvent>
|
||||
#include <QRubberBand>
|
||||
|
||||
GameView::GameView(QGraphicsScene *scene, QWidget *parent) : QGraphicsView(scene, parent), rubberBand(0)
|
||||
{
|
||||
setBackgroundBrush(QBrush(QColor(0, 0, 0)));
|
||||
setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing);
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
setViewportUpdateMode(BoundingRectViewportUpdate);
|
||||
|
||||
connect(scene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &)));
|
||||
|
||||
connect(scene, SIGNAL(sigStartRubberBand(const QPointF &)), this, SLOT(startRubberBand(const QPointF &)));
|
||||
connect(scene, SIGNAL(sigResizeRubberBand(const QPointF &)), this, SLOT(resizeRubberBand(const QPointF &)));
|
||||
connect(scene, SIGNAL(sigStopRubberBand()), this, SLOT(stopRubberBand()));
|
||||
|
||||
aCloseMostRecentZoneView = new QAction(this);
|
||||
|
||||
connect(aCloseMostRecentZoneView, SIGNAL(triggered()), scene, SLOT(closeMostRecentZoneView()));
|
||||
addAction(aCloseMostRecentZoneView);
|
||||
connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
|
||||
refreshShortcuts();
|
||||
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
|
||||
}
|
||||
|
||||
void GameView::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QGraphicsView::resizeEvent(event);
|
||||
|
||||
GameScene *s = dynamic_cast<GameScene *>(scene());
|
||||
if (s)
|
||||
s->processViewSizeChange(event->size());
|
||||
|
||||
updateSceneRect(scene()->sceneRect());
|
||||
}
|
||||
|
||||
void GameView::updateSceneRect(const QRectF &rect)
|
||||
{
|
||||
fitInView(rect, Qt::KeepAspectRatio);
|
||||
}
|
||||
|
||||
void GameView::startRubberBand(const QPointF &_selectionOrigin)
|
||||
{
|
||||
selectionOrigin = _selectionOrigin;
|
||||
rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0)));
|
||||
rubberBand->show();
|
||||
}
|
||||
|
||||
void GameView::resizeRubberBand(const QPointF &cursorPoint)
|
||||
{
|
||||
if (rubberBand)
|
||||
rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), cursorPoint.toPoint()).normalized());
|
||||
}
|
||||
|
||||
void GameView::stopRubberBand()
|
||||
{
|
||||
rubberBand->hide();
|
||||
}
|
||||
|
||||
void GameView::refreshShortcuts()
|
||||
{
|
||||
aCloseMostRecentZoneView->setShortcuts(
|
||||
SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView"));
|
||||
}
|
||||
30
cockatrice/src/game/game_view.h
Normal file
30
cockatrice/src/game/game_view.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef GAMEVIEW_H
|
||||
#define GAMEVIEW_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
|
||||
class QRubberBand;
|
||||
|
||||
class GameView : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QAction *aCloseMostRecentZoneView;
|
||||
QRubberBand *rubberBand;
|
||||
QPointF selectionOrigin;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event);
|
||||
private slots:
|
||||
void startRubberBand(const QPointF &selectionOrigin);
|
||||
void resizeRubberBand(const QPointF &cursorPoint);
|
||||
void stopRubberBand();
|
||||
void refreshShortcuts();
|
||||
public slots:
|
||||
void updateSceneRect(const QRectF &rect);
|
||||
|
||||
public:
|
||||
GameView(QGraphicsScene *scene, QWidget *parent = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
562
cockatrice/src/game/games_model.cpp
Normal file
562
cockatrice/src/game/games_model.cpp
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
#include "games_model.h"
|
||||
|
||||
#include "../client/tabs/tab_account.h"
|
||||
#include "../client/ui/pixel_map_generator.h"
|
||||
#include "../server/user/user_list.h"
|
||||
#include "../settings/cache_settings.h"
|
||||
#include "pb/serverinfo_game.pb.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QIcon>
|
||||
#include <QStringList>
|
||||
#include <QTime>
|
||||
|
||||
enum GameListColumn
|
||||
{
|
||||
ROOM,
|
||||
CREATED,
|
||||
DESCRIPTION,
|
||||
CREATOR,
|
||||
GAME_TYPE,
|
||||
RESTRICTIONS,
|
||||
PLAYERS,
|
||||
SPECTATORS
|
||||
};
|
||||
|
||||
const bool DEFAULT_SHOW_FULL_GAMES = false;
|
||||
const bool DEFAULT_SHOW_GAMES_THAT_STARTED = false;
|
||||
const bool DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES = true;
|
||||
const bool DEFAULT_SHOW_BUDDIES_ONLY_GAMES = true;
|
||||
const bool DEFAULT_HIDE_IGNORED_USER_GAMES = false;
|
||||
const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH = false;
|
||||
const bool DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED = true;
|
||||
const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT = false;
|
||||
const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS = false;
|
||||
const int DEFAULT_MAX_PLAYERS_MIN = 1;
|
||||
const int DEFAULT_MAX_PLAYERS_MAX = 99;
|
||||
constexpr QTime DEFAULT_MAX_GAME_AGE = QTime();
|
||||
|
||||
const QString GamesModel::getGameCreatedString(const int secs)
|
||||
{
|
||||
static const QTime zeroTime{0, 0};
|
||||
static const int halfHourSecs = zeroTime.secsTo(QTime(1, 0)) / 2;
|
||||
static const int halfMinSecs = zeroTime.secsTo(QTime(0, 1)) / 2;
|
||||
static const int wrapSeconds = zeroTime.secsTo(zeroTime.addSecs(-halfHourSecs)); // round up
|
||||
|
||||
if (secs >= wrapSeconds) { // QTime wraps after a day
|
||||
return tr(">1 day");
|
||||
}
|
||||
|
||||
QTime total = zeroTime.addSecs(secs);
|
||||
QTime totalRounded = total.addSecs(halfMinSecs); // round up
|
||||
QString form;
|
||||
int amount;
|
||||
if (totalRounded.hour()) {
|
||||
amount = total.addSecs(halfHourSecs).hour(); // round up separately
|
||||
form = tr("%1%2 hr", "short age in hours", amount);
|
||||
} else if (total.minute() < 2) { // games are new during their first minute
|
||||
return tr("new");
|
||||
} else {
|
||||
amount = totalRounded.minute();
|
||||
form = tr("%1%2 min", "short age in minutes", amount);
|
||||
}
|
||||
|
||||
for (int aggregate : {40, 20, 10, 5}) { // floor to values in this list
|
||||
if (amount >= aggregate) {
|
||||
return form.arg(">").arg(aggregate);
|
||||
}
|
||||
}
|
||||
return form.arg("").arg(amount);
|
||||
}
|
||||
|
||||
GamesModel::GamesModel(const QMap<int, QString> &_rooms, const QMap<int, GameTypeMap> &_gameTypes, QObject *parent)
|
||||
: QAbstractTableModel(parent), rooms(_rooms), gameTypes(_gameTypes)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant GamesModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
if (role == Qt::UserRole)
|
||||
return index.row();
|
||||
if (role != Qt::DisplayRole && role != SORT_ROLE && role != Qt::DecorationRole && role != Qt::TextAlignmentRole)
|
||||
return QVariant();
|
||||
if ((index.row() >= gameList.size()) || (index.column() >= columnCount()))
|
||||
return QVariant();
|
||||
|
||||
const ServerInfo_Game &gameentry = gameList[index.row()];
|
||||
switch (index.column()) {
|
||||
case ROOM:
|
||||
return rooms.value(gameentry.room_id());
|
||||
case CREATED: {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
QDateTime then = QDateTime::fromSecsSinceEpoch(gameentry.start_time(), Qt::UTC);
|
||||
int secs = then.secsTo(QDateTime::currentDateTimeUtc());
|
||||
return getGameCreatedString(secs);
|
||||
}
|
||||
case SORT_ROLE:
|
||||
return QVariant(-static_cast<qint64>(gameentry.start_time()));
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignCenter;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case DESCRIPTION:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole:
|
||||
return QString::fromStdString(gameentry.description());
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignLeft;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case CREATOR: {
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole:
|
||||
return QString::fromStdString(gameentry.creator_info().name());
|
||||
case Qt::DecorationRole: {
|
||||
QPixmap avatarPixmap = UserLevelPixmapGenerator::generatePixmap(
|
||||
13, (UserLevelFlags)gameentry.creator_info().user_level(), false,
|
||||
QString::fromStdString(gameentry.creator_info().privlevel()));
|
||||
return QIcon(avatarPixmap);
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case GAME_TYPE:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole: {
|
||||
QStringList result;
|
||||
GameTypeMap gameTypeMap = gameTypes.value(gameentry.room_id());
|
||||
for (int i = gameentry.game_types_size() - 1; i >= 0; --i)
|
||||
result.append(gameTypeMap.value(gameentry.game_types(i)));
|
||||
return result.join(", ");
|
||||
}
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignLeft;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case RESTRICTIONS:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole: {
|
||||
QStringList result;
|
||||
if (gameentry.with_password())
|
||||
result.append(tr("password"));
|
||||
if (gameentry.only_buddies())
|
||||
result.append(tr("buddies only"));
|
||||
if (gameentry.only_registered())
|
||||
result.append(tr("reg. users only"));
|
||||
return result.join(", ");
|
||||
}
|
||||
case Qt::DecorationRole: {
|
||||
return gameentry.with_password() ? QIcon(LockPixmapGenerator::generatePixmap(13)) : QVariant();
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignLeft;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case PLAYERS:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole:
|
||||
return QString("%1/%2").arg(gameentry.player_count()).arg(gameentry.max_players());
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignCenter;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case SPECTATORS:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole: {
|
||||
if (gameentry.spectators_allowed()) {
|
||||
QString result;
|
||||
result.append(QString::number(gameentry.spectators_count()));
|
||||
|
||||
if (gameentry.spectators_can_chat() && gameentry.spectators_omniscient()) {
|
||||
result.append(" (")
|
||||
.append(tr("can chat"))
|
||||
.append(" & ")
|
||||
.append(tr("see hands"))
|
||||
.append(")");
|
||||
} else if (gameentry.spectators_can_chat()) {
|
||||
result.append(" (").append(tr("can chat")).append(")");
|
||||
} else if (gameentry.spectators_omniscient()) {
|
||||
result.append(" (").append(tr("can see hands")).append(")");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return QVariant(tr("not allowed"));
|
||||
}
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignLeft;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant GamesModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const
|
||||
{
|
||||
if ((role != Qt::DisplayRole) && (role != Qt::TextAlignmentRole))
|
||||
return QVariant();
|
||||
switch (section) {
|
||||
case ROOM:
|
||||
return tr("Room");
|
||||
case CREATED: {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return tr("Age");
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignCenter;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case DESCRIPTION:
|
||||
return tr("Description");
|
||||
case CREATOR:
|
||||
return tr("Creator");
|
||||
case GAME_TYPE:
|
||||
return tr("Type");
|
||||
case RESTRICTIONS:
|
||||
return tr("Restrictions");
|
||||
case PLAYERS: {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return tr("Players");
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignCenter;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case SPECTATORS:
|
||||
return tr("Spectators");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
const ServerInfo_Game &GamesModel::getGame(int row)
|
||||
{
|
||||
Q_ASSERT(row < gameList.size());
|
||||
return gameList[row];
|
||||
}
|
||||
|
||||
void GamesModel::updateGameList(const ServerInfo_Game &game)
|
||||
{
|
||||
for (int i = 0; i < gameList.size(); i++) {
|
||||
if (gameList[i].game_id() == game.game_id()) {
|
||||
if (game.closed()) {
|
||||
beginRemoveRows(QModelIndex(), i, i);
|
||||
gameList.removeAt(i);
|
||||
endRemoveRows();
|
||||
} else {
|
||||
gameList[i].MergeFrom(game);
|
||||
emit dataChanged(index(i, 0), index(i, NUM_COLS - 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
beginInsertRows(QModelIndex(), gameList.size(), gameList.size());
|
||||
gameList.append(game);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
GamesProxyModel::GamesProxyModel(QObject *parent, const TabSupervisor *_tabSupervisor)
|
||||
: QSortFilterProxyModel(parent), ownUserIsRegistered(_tabSupervisor->isOwnUserRegistered()),
|
||||
tabSupervisor(_tabSupervisor)
|
||||
{
|
||||
resetFilterParameters();
|
||||
setSortRole(GamesModel::SORT_ROLE);
|
||||
setDynamicSortFilter(true);
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames)
|
||||
{
|
||||
showBuddiesOnlyGames = _showBuddiesOnlyGames;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setHideIgnoredUserGames(bool _hideIgnoredUserGames)
|
||||
{
|
||||
hideIgnoredUserGames = _hideIgnoredUserGames;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowFullGames(bool _showFullGames)
|
||||
{
|
||||
showFullGames = _showFullGames;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowGamesThatStarted(bool _showGamesThatStarted)
|
||||
{
|
||||
showGamesThatStarted = _showGamesThatStarted;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowPasswordProtectedGames(bool _showPasswordProtectedGames)
|
||||
{
|
||||
showPasswordProtectedGames = _showPasswordProtectedGames;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setGameNameFilter(const QString &_gameNameFilter)
|
||||
{
|
||||
gameNameFilter = _gameNameFilter;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setCreatorNameFilter(const QString &_creatorNameFilter)
|
||||
{
|
||||
creatorNameFilter = _creatorNameFilter;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setGameTypeFilter(const QSet<int> &_gameTypeFilter)
|
||||
{
|
||||
gameTypeFilter = _gameTypeFilter;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax)
|
||||
{
|
||||
maxPlayersFilterMin = _maxPlayersFilterMin;
|
||||
maxPlayersFilterMax = _maxPlayersFilterMax;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setMaxGameAge(const QTime &_maxGameAge)
|
||||
{
|
||||
maxGameAge = _maxGameAge;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowOnlyIfSpectatorsCanWatch(bool _showOnlyIfSpectatorsCanWatch)
|
||||
{
|
||||
showOnlyIfSpectatorsCanWatch = _showOnlyIfSpectatorsCanWatch;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowSpectatorPasswordProtected(bool _showSpectatorPasswordProtected)
|
||||
{
|
||||
showSpectatorPasswordProtected = _showSpectatorPasswordProtected;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowOnlyIfSpectatorsCanChat(bool _showOnlyIfSpectatorsCanChat)
|
||||
{
|
||||
showOnlyIfSpectatorsCanChat = _showOnlyIfSpectatorsCanChat;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowOnlyIfSpectatorsCanSeeHands(bool _showOnlyIfSpectatorsCanSeeHands)
|
||||
{
|
||||
showOnlyIfSpectatorsCanSeeHands = _showOnlyIfSpectatorsCanSeeHands;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
int GamesProxyModel::getNumFilteredGames() const
|
||||
{
|
||||
GamesModel *model = qobject_cast<GamesModel *>(sourceModel());
|
||||
if (!model)
|
||||
return 0;
|
||||
|
||||
int numFilteredGames = 0;
|
||||
for (int row = 0; row < model->rowCount(); ++row) {
|
||||
if (!filterAcceptsRow(row)) {
|
||||
++numFilteredGames;
|
||||
}
|
||||
}
|
||||
return numFilteredGames;
|
||||
}
|
||||
|
||||
void GamesProxyModel::resetFilterParameters()
|
||||
{
|
||||
showFullGames = DEFAULT_SHOW_FULL_GAMES;
|
||||
showGamesThatStarted = DEFAULT_SHOW_GAMES_THAT_STARTED;
|
||||
showPasswordProtectedGames = DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES;
|
||||
showBuddiesOnlyGames = DEFAULT_SHOW_BUDDIES_ONLY_GAMES;
|
||||
hideIgnoredUserGames = DEFAULT_HIDE_IGNORED_USER_GAMES;
|
||||
gameNameFilter = QString();
|
||||
creatorNameFilter = QString();
|
||||
gameTypeFilter.clear();
|
||||
maxPlayersFilterMin = DEFAULT_MAX_PLAYERS_MIN;
|
||||
maxPlayersFilterMax = DEFAULT_MAX_PLAYERS_MAX;
|
||||
maxGameAge = DEFAULT_MAX_GAME_AGE;
|
||||
showOnlyIfSpectatorsCanWatch = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH;
|
||||
showSpectatorPasswordProtected = DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED;
|
||||
showOnlyIfSpectatorsCanChat = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT;
|
||||
showOnlyIfSpectatorsCanSeeHands = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS;
|
||||
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool GamesProxyModel::areFilterParametersSetToDefaults() const
|
||||
{
|
||||
return showFullGames == DEFAULT_SHOW_FULL_GAMES && showGamesThatStarted == DEFAULT_SHOW_GAMES_THAT_STARTED &&
|
||||
showPasswordProtectedGames == DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES &&
|
||||
showBuddiesOnlyGames == DEFAULT_SHOW_BUDDIES_ONLY_GAMES &&
|
||||
hideIgnoredUserGames == DEFAULT_HIDE_IGNORED_USER_GAMES && gameNameFilter.isEmpty() &&
|
||||
creatorNameFilter.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN &&
|
||||
maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE &&
|
||||
showOnlyIfSpectatorsCanWatch == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH &&
|
||||
showSpectatorPasswordProtected == DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED &&
|
||||
showOnlyIfSpectatorsCanChat == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT &&
|
||||
showOnlyIfSpectatorsCanSeeHands == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS;
|
||||
}
|
||||
|
||||
void GamesProxyModel::loadFilterParameters(const QMap<int, QString> &allGameTypes)
|
||||
{
|
||||
GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters();
|
||||
showFullGames = gameFilters.isShowFullGames();
|
||||
showGamesThatStarted = gameFilters.isShowGamesThatStarted();
|
||||
showPasswordProtectedGames = gameFilters.isShowPasswordProtectedGames();
|
||||
hideIgnoredUserGames = gameFilters.isHideIgnoredUserGames();
|
||||
showBuddiesOnlyGames = gameFilters.isShowBuddiesOnlyGames();
|
||||
gameNameFilter = gameFilters.getGameNameFilter();
|
||||
creatorNameFilter = gameFilters.getCreatorNameFilter();
|
||||
maxPlayersFilterMin = gameFilters.getMinPlayers();
|
||||
maxPlayersFilterMax = gameFilters.getMaxPlayers();
|
||||
maxGameAge = gameFilters.getMaxGameAge();
|
||||
showOnlyIfSpectatorsCanWatch = gameFilters.isShowOnlyIfSpectatorsCanWatch();
|
||||
showSpectatorPasswordProtected = gameFilters.isShowSpectatorPasswordProtected();
|
||||
showOnlyIfSpectatorsCanChat = gameFilters.isShowOnlyIfSpectatorsCanChat();
|
||||
showOnlyIfSpectatorsCanSeeHands = gameFilters.isShowOnlyIfSpectatorsCanSeeHands();
|
||||
|
||||
QMapIterator<int, QString> gameTypesIterator(allGameTypes);
|
||||
while (gameTypesIterator.hasNext()) {
|
||||
gameTypesIterator.next();
|
||||
if (gameFilters.isGameTypeEnabled(gameTypesIterator.value())) {
|
||||
gameTypeFilter.insert(gameTypesIterator.key());
|
||||
}
|
||||
}
|
||||
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::saveFilterParameters(const QMap<int, QString> &allGameTypes)
|
||||
{
|
||||
GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters();
|
||||
gameFilters.setShowBuddiesOnlyGames(showBuddiesOnlyGames);
|
||||
gameFilters.setShowFullGames(showFullGames);
|
||||
gameFilters.setShowGamesThatStarted(showGamesThatStarted);
|
||||
gameFilters.setShowPasswordProtectedGames(showPasswordProtectedGames);
|
||||
gameFilters.setHideIgnoredUserGames(hideIgnoredUserGames);
|
||||
gameFilters.setGameNameFilter(gameNameFilter);
|
||||
gameFilters.setCreatorNameFilter(creatorNameFilter);
|
||||
|
||||
QMapIterator<int, QString> gameTypeIterator(allGameTypes);
|
||||
while (gameTypeIterator.hasNext()) {
|
||||
gameTypeIterator.next();
|
||||
bool enabled = gameTypeFilter.contains(gameTypeIterator.key());
|
||||
gameFilters.setGameTypeEnabled(gameTypeIterator.value(), enabled);
|
||||
}
|
||||
|
||||
gameFilters.setMinPlayers(maxPlayersFilterMin);
|
||||
gameFilters.setMaxPlayers(maxPlayersFilterMax);
|
||||
gameFilters.setMaxGameAge(maxGameAge);
|
||||
|
||||
gameFilters.setShowOnlyIfSpectatorsCanWatch(showOnlyIfSpectatorsCanWatch);
|
||||
gameFilters.setShowSpectatorPasswordProtected(showSpectatorPasswordProtected);
|
||||
gameFilters.setShowOnlyIfSpectatorsCanChat(showOnlyIfSpectatorsCanChat);
|
||||
gameFilters.setShowOnlyIfSpectatorsCanSeeHands(showOnlyIfSpectatorsCanSeeHands);
|
||||
}
|
||||
|
||||
bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
return filterAcceptsRow(sourceRow);
|
||||
}
|
||||
|
||||
bool GamesProxyModel::filterAcceptsRow(int sourceRow) const
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
|
||||
static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, Qt::UTC).date();
|
||||
#else
|
||||
static const QDate epochDate = QDateTime::fromTime_t(0, Qt::UTC).date();
|
||||
#endif
|
||||
auto *model = qobject_cast<GamesModel *>(sourceModel());
|
||||
if (!model)
|
||||
return false;
|
||||
|
||||
const ServerInfo_Game &game = model->getGame(sourceRow);
|
||||
|
||||
if (!showBuddiesOnlyGames && game.only_buddies()) {
|
||||
return false;
|
||||
}
|
||||
if (hideIgnoredUserGames && tabSupervisor->getUserListsTab()->getIgnoreList()->getUsers().contains(
|
||||
QString::fromStdString(game.creator_info().name()))) {
|
||||
return false;
|
||||
}
|
||||
if (!showFullGames && game.player_count() == game.max_players())
|
||||
return false;
|
||||
if (!showGamesThatStarted && game.started())
|
||||
return false;
|
||||
if (!ownUserIsRegistered)
|
||||
if (game.only_registered())
|
||||
return false;
|
||||
if (!showPasswordProtectedGames && game.with_password())
|
||||
return false;
|
||||
if (!gameNameFilter.isEmpty())
|
||||
if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive))
|
||||
return false;
|
||||
if (!creatorNameFilter.isEmpty())
|
||||
if (!QString::fromStdString(game.creator_info().name()).contains(creatorNameFilter, Qt::CaseInsensitive))
|
||||
return false;
|
||||
|
||||
QSet<int> gameTypes;
|
||||
for (int i = 0; i < game.game_types_size(); ++i)
|
||||
gameTypes.insert(game.game_types(i));
|
||||
if (!gameTypeFilter.isEmpty() && gameTypes.intersect(gameTypeFilter).isEmpty())
|
||||
return false;
|
||||
|
||||
if (game.max_players() < maxPlayersFilterMin)
|
||||
return false;
|
||||
if (game.max_players() > maxPlayersFilterMax)
|
||||
return false;
|
||||
|
||||
if (maxGameAge.isValid()) {
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
qint64 signed_start_time = game.start_time(); // cast to 64 bit value to allow signed value
|
||||
QDateTime total = now.addSecs(-signed_start_time); // a 32 bit value would wrap at 2038-1-19
|
||||
// games shouldn't have negative ages but we'll not filter them
|
||||
// because qtime wraps after a day we consider all games older than a day to be too old
|
||||
if (total.isValid() && total.date() >= epochDate && (total.date() > epochDate || total.time() > maxGameAge)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (showOnlyIfSpectatorsCanWatch) {
|
||||
if (!game.spectators_allowed())
|
||||
return false;
|
||||
if (!showSpectatorPasswordProtected && game.spectators_need_password())
|
||||
return false;
|
||||
if (showOnlyIfSpectatorsCanChat && !game.spectators_can_chat())
|
||||
return false;
|
||||
if (showOnlyIfSpectatorsCanSeeHands && !game.spectators_omniscient())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GamesProxyModel::refresh()
|
||||
{
|
||||
invalidateFilter();
|
||||
}
|
||||
180
cockatrice/src/game/games_model.h
Normal file
180
cockatrice/src/game/games_model.h
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#ifndef GAMESMODEL_H
|
||||
#define GAMESMODEL_H
|
||||
|
||||
#include "../client/tabs/tab_supervisor.h"
|
||||
#include "game_type_map.h"
|
||||
#include "pb/serverinfo_game.pb.h"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStringList>
|
||||
#include <QTime>
|
||||
|
||||
class GamesModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QList<ServerInfo_Game> gameList;
|
||||
QMap<int, QString> rooms;
|
||||
QMap<int, GameTypeMap> gameTypes;
|
||||
|
||||
static const int NUM_COLS = 8;
|
||||
|
||||
public:
|
||||
static const int SORT_ROLE = Qt::UserRole + 1;
|
||||
|
||||
GamesModel(const QMap<int, QString> &_rooms, const QMap<int, GameTypeMap> &_gameTypes, QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const
|
||||
{
|
||||
return parent.isValid() ? 0 : gameList.size();
|
||||
}
|
||||
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const
|
||||
{
|
||||
return NUM_COLS;
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||
static const QString getGameCreatedString(const int secs);
|
||||
const ServerInfo_Game &getGame(int row);
|
||||
|
||||
/**
|
||||
* Update game list with a (possibly new) game.
|
||||
*/
|
||||
void updateGameList(const ServerInfo_Game &game);
|
||||
|
||||
int roomColIndex()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int startTimeColIndex()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
const QMap<int, GameTypeMap> &getGameTypes()
|
||||
{
|
||||
return gameTypes;
|
||||
}
|
||||
};
|
||||
|
||||
class ServerInfo_User;
|
||||
|
||||
class GamesProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
bool ownUserIsRegistered;
|
||||
const TabSupervisor *tabSupervisor;
|
||||
|
||||
// If adding any additional filters, make sure to update:
|
||||
// - GamesProxyModel()
|
||||
// - resetFilterParameters()
|
||||
// - areFilterParametersSetToDefaults()
|
||||
// - loadFilterParameters()
|
||||
// - saveFilterParameters()
|
||||
// - filterAcceptsRow()
|
||||
bool showBuddiesOnlyGames;
|
||||
bool hideIgnoredUserGames;
|
||||
bool showFullGames;
|
||||
bool showGamesThatStarted;
|
||||
bool showPasswordProtectedGames;
|
||||
QString gameNameFilter, creatorNameFilter;
|
||||
QSet<int> gameTypeFilter;
|
||||
quint32 maxPlayersFilterMin, maxPlayersFilterMax;
|
||||
QTime maxGameAge;
|
||||
bool showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, showOnlyIfSpectatorsCanChat,
|
||||
showOnlyIfSpectatorsCanSeeHands;
|
||||
|
||||
public:
|
||||
GamesProxyModel(QObject *parent = nullptr, const TabSupervisor *_tabSupervisor = nullptr);
|
||||
|
||||
bool getShowBuddiesOnlyGames() const
|
||||
{
|
||||
return showBuddiesOnlyGames;
|
||||
}
|
||||
void setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames);
|
||||
bool getHideIgnoredUserGames() const
|
||||
{
|
||||
return hideIgnoredUserGames;
|
||||
}
|
||||
void setHideIgnoredUserGames(bool _hideIgnoredUserGames);
|
||||
bool getShowFullGames() const
|
||||
{
|
||||
return showFullGames;
|
||||
}
|
||||
void setShowFullGames(bool _showFullGames);
|
||||
bool getShowGamesThatStarted() const
|
||||
{
|
||||
return showGamesThatStarted;
|
||||
}
|
||||
void setShowGamesThatStarted(bool _showGamesThatStarted);
|
||||
bool getShowPasswordProtectedGames() const
|
||||
{
|
||||
return showPasswordProtectedGames;
|
||||
}
|
||||
void setShowPasswordProtectedGames(bool _showPasswordProtectedGames);
|
||||
QString getGameNameFilter() const
|
||||
{
|
||||
return gameNameFilter;
|
||||
}
|
||||
void setGameNameFilter(const QString &_gameNameFilter);
|
||||
QString getCreatorNameFilter() const
|
||||
{
|
||||
return creatorNameFilter;
|
||||
}
|
||||
void setCreatorNameFilter(const QString &_creatorNameFilter);
|
||||
QSet<int> getGameTypeFilter() const
|
||||
{
|
||||
return gameTypeFilter;
|
||||
}
|
||||
void setGameTypeFilter(const QSet<int> &_gameTypeFilter);
|
||||
int getMaxPlayersFilterMin() const
|
||||
{
|
||||
return maxPlayersFilterMin;
|
||||
}
|
||||
int getMaxPlayersFilterMax() const
|
||||
{
|
||||
return maxPlayersFilterMax;
|
||||
}
|
||||
void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax);
|
||||
const QTime &getMaxGameAge() const
|
||||
{
|
||||
return maxGameAge;
|
||||
}
|
||||
void setMaxGameAge(const QTime &_maxGameAge);
|
||||
bool getShowOnlyIfSpectatorsCanWatch() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanWatch;
|
||||
}
|
||||
void setShowOnlyIfSpectatorsCanWatch(bool _showOnlyIfSpectatorsCanWatch);
|
||||
bool getShowSpectatorPasswordProtected() const
|
||||
{
|
||||
return showSpectatorPasswordProtected;
|
||||
}
|
||||
void setShowSpectatorPasswordProtected(bool _showSpectatorPasswordProtected);
|
||||
bool getShowOnlyIfSpectatorsCanChat() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanChat;
|
||||
}
|
||||
void setShowOnlyIfSpectatorsCanChat(bool _showOnlyIfSpectatorsCanChat);
|
||||
bool getShowOnlyIfSpectatorsCanSeeHands() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanSeeHands;
|
||||
}
|
||||
void setShowOnlyIfSpectatorsCanSeeHands(bool _showOnlyIfSpectatorsCanSeeHands);
|
||||
|
||||
int getNumFilteredGames() const;
|
||||
void resetFilterParameters();
|
||||
bool areFilterParametersSetToDefaults() const;
|
||||
void loadFilterParameters(const QMap<int, QString> &allGameTypes);
|
||||
void saveFilterParameters(const QMap<int, QString> &allGameTypes);
|
||||
void refresh();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
|
||||
bool filterAcceptsRow(int sourceRow) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
51
cockatrice/src/game/hand_counter.cpp
Normal file
51
cockatrice/src/game/hand_counter.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include "hand_counter.h"
|
||||
|
||||
#include "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<CardZone *>(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();
|
||||
}
|
||||
}
|
||||
39
cockatrice/src/game/hand_counter.h
Normal file
39
cockatrice/src/game/hand_counter.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef HANDCOUNTER_H
|
||||
#define HANDCOUNTER_H
|
||||
|
||||
#include "../game/board/abstract_graphics_item.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QPainter;
|
||||
class QPixmap;
|
||||
|
||||
class HandCounter : public AbstractGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
int number;
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event);
|
||||
public slots:
|
||||
void updateNumber();
|
||||
signals:
|
||||
void showContextMenu(const QPoint &screenPos);
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
Type = typeOther
|
||||
};
|
||||
int type() const
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
HandCounter(QGraphicsItem *parent = nullptr);
|
||||
~HandCounter();
|
||||
QRectF boundingRect() const;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
};
|
||||
|
||||
#endif
|
||||
29
cockatrice/src/game/phase.cpp
Normal file
29
cockatrice/src/game/phase.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include "phase.h"
|
||||
|
||||
Phase::Phase(const char *_name, QString _color, QString _soundFileName) : color(_color), soundFileName(_soundFileName)
|
||||
{
|
||||
name = tr(_name);
|
||||
}
|
||||
|
||||
Phase Phases::getPhase(int phase)
|
||||
{
|
||||
if (0 <= phase && phase < Phases::phaseTypesCount) {
|
||||
return phases[phase];
|
||||
} else {
|
||||
return unknownPhase;
|
||||
}
|
||||
}
|
||||
|
||||
const Phase Phases::unknownPhase(QT_TRANSLATE_NOOP("Phase", "Unknown Phase"), "black", "unknown_phase");
|
||||
const Phase Phases::phases[Phases::phaseTypesCount] = {
|
||||
{QT_TRANSLATE_NOOP("Phase", "Untap"), "green", "untap_step"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "Upkeep"), "green", "upkeep_step"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "Draw"), "green", "draw_step"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "First Main"), "blue", "main_1"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "Beginning of Combat"), "red", "start_combat"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "Declare Attackers"), "red", "attack_step"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "Declare Blockers"), "red", "block_step"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "Combat Damage"), "red", "damage_step"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "End of Combat"), "red", "end_combat"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "Second Main"), "blue", "main_2"},
|
||||
{QT_TRANSLATE_NOOP("Phase", "End/Cleanup"), "green", "end_step"}};
|
||||
25
cockatrice/src/game/phase.h
Normal file
25
cockatrice/src/game/phase.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef PHASE_H
|
||||
#define PHASE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QtCore>
|
||||
|
||||
class Phase
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(Phase)
|
||||
|
||||
public:
|
||||
QString name, color, soundFileName;
|
||||
Phase(const char *_name, QString _color, QString _soundFileName);
|
||||
};
|
||||
|
||||
struct Phases
|
||||
{
|
||||
const static int phaseTypesCount = 11;
|
||||
const static Phase unknownPhase;
|
||||
const static Phase phases[phaseTypesCount];
|
||||
|
||||
static Phase getPhase(int);
|
||||
};
|
||||
|
||||
#endif // PHASE_H
|
||||
3813
cockatrice/src/game/player/player.cpp
Normal file
3813
cockatrice/src/game/player/player.cpp
Normal file
File diff suppressed because it is too large
Load diff
494
cockatrice/src/game/player/player.h
Normal file
494
cockatrice/src/game/player/player.h
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
#ifndef PLAYER_H
|
||||
#define PLAYER_H
|
||||
|
||||
#include "../../client/tearoff_menu.h"
|
||||
#include "../board/abstract_graphics_item.h"
|
||||
#include "../cards/card_database.h"
|
||||
#include "../filters/filter_string.h"
|
||||
#include "pb/card_attributes.pb.h"
|
||||
#include "pb/game_event.pb.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QMap>
|
||||
#include <QPoint>
|
||||
#include <QTimer>
|
||||
|
||||
namespace google
|
||||
{
|
||||
namespace protobuf
|
||||
{
|
||||
class Message;
|
||||
}
|
||||
} // namespace google
|
||||
class AbstractCardItem;
|
||||
class AbstractCounter;
|
||||
class ArrowItem;
|
||||
class ArrowTarget;
|
||||
class CardDatabase;
|
||||
class CardItem;
|
||||
class CardZone;
|
||||
class CommandContainer;
|
||||
class Command_MoveCard;
|
||||
class DeckLoader;
|
||||
class Event_AttachCard;
|
||||
class Event_ChangeZoneProperties;
|
||||
class Event_CreateArrow;
|
||||
class Event_CreateCounter;
|
||||
class Event_CreateToken;
|
||||
class Event_DelCounter;
|
||||
class Event_DeleteArrow;
|
||||
class Event_DestroyCard;
|
||||
class Event_DrawCards;
|
||||
class Event_DumpZone;
|
||||
class Event_FlipCard;
|
||||
class Event_GameSay;
|
||||
class Event_MoveCard;
|
||||
class Event_RevealCards;
|
||||
class Event_RollDie;
|
||||
class Event_SetCardAttr;
|
||||
class Event_SetCardCounter;
|
||||
class Event_SetCounter;
|
||||
class Event_Shuffle;
|
||||
class GameCommand;
|
||||
class GameEvent;
|
||||
class GameEventContext;
|
||||
class HandZone;
|
||||
class PendingCommand;
|
||||
class PlayerTarget;
|
||||
class QAction;
|
||||
class QMenu;
|
||||
class ServerInfo_Arrow;
|
||||
class ServerInfo_Counter;
|
||||
class ServerInfo_Player;
|
||||
class ServerInfo_User;
|
||||
class StackZone;
|
||||
class TabGame;
|
||||
class TableZone;
|
||||
class ZoneViewZone;
|
||||
|
||||
const int MAX_TOKENS_PER_DIALOG = 99;
|
||||
|
||||
class PlayerArea : public QObject, public QGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsItem)
|
||||
private:
|
||||
QRectF bRect;
|
||||
int playerZoneId;
|
||||
private slots:
|
||||
void updateBg();
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
Type = typeOther
|
||||
};
|
||||
int type() const override
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
|
||||
explicit PlayerArea(QGraphicsItem *parent = nullptr);
|
||||
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);
|
||||
int getPlayerZoneId() const
|
||||
{
|
||||
return playerZoneId;
|
||||
}
|
||||
};
|
||||
|
||||
class Player : public QObject, public QGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsItem)
|
||||
signals:
|
||||
void openDeckEditor(const DeckLoader *deck);
|
||||
void newCardAdded(AbstractCardItem *card);
|
||||
// Log events
|
||||
void logSay(Player *player, QString message);
|
||||
void logShuffle(Player *player, CardZone *zone, int start, int end);
|
||||
void logRollDie(Player *player, int sides, const QList<uint> &rolls);
|
||||
void logCreateArrow(Player *player,
|
||||
Player *startPlayer,
|
||||
QString startCard,
|
||||
Player *targetPlayer,
|
||||
QString targetCard,
|
||||
bool _playerTarget);
|
||||
void logCreateToken(Player *player, QString cardName, QString pt);
|
||||
void logDrawCards(Player *player, int number, bool deckIsEmpty);
|
||||
void logUndoDraw(Player *player, QString cardName);
|
||||
void logMoveCard(Player *player, CardItem *card, CardZone *startZone, int oldX, CardZone *targetZone, int newX);
|
||||
void logFlipCard(Player *player, QString cardName, bool faceDown);
|
||||
void logDestroyCard(Player *player, QString cardName);
|
||||
void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName);
|
||||
void logUnattachCard(Player *player, QString cardName);
|
||||
void logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue);
|
||||
void logSetTapped(Player *player, CardItem *card, bool tapped);
|
||||
void logSetCounter(Player *player, QString counterName, int value, int oldValue);
|
||||
void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap);
|
||||
void logSetPT(Player *player, CardItem *card, QString newPT);
|
||||
void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation);
|
||||
void logDumpZone(Player *player, CardZone *zone, int numberCards);
|
||||
void logRevealCards(Player *player,
|
||||
CardZone *zone,
|
||||
int cardId,
|
||||
QString cardName,
|
||||
Player *otherPlayer,
|
||||
bool faceDown,
|
||||
int amount,
|
||||
bool isLentToAnotherPlayer = false);
|
||||
void logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal);
|
||||
void logAlwaysLookAtTopCard(Player *player, CardZone *zone, bool reveal);
|
||||
|
||||
void sizeChanged();
|
||||
void playerCountChanged();
|
||||
public slots:
|
||||
void actUntapAll();
|
||||
void actRollDie();
|
||||
void actCreateToken();
|
||||
void actCreateAnotherToken();
|
||||
void actShuffle();
|
||||
void actDrawCard();
|
||||
void actDrawCards();
|
||||
void actUndoDraw();
|
||||
void actMulligan();
|
||||
void actMoveTopCardToPlay();
|
||||
void actMoveTopCardToPlayFaceDown();
|
||||
void actMoveTopCardToGrave();
|
||||
void actMoveTopCardToExile();
|
||||
void actMoveTopCardsToGrave();
|
||||
void actMoveTopCardsToExile();
|
||||
void actMoveTopCardsUntil();
|
||||
void actMoveTopCardToBottom();
|
||||
void actDrawBottomCard();
|
||||
void actDrawBottomCards();
|
||||
void actMoveBottomCardToPlay();
|
||||
void actMoveBottomCardToPlayFaceDown();
|
||||
void actMoveBottomCardToGrave();
|
||||
void actMoveBottomCardToExile();
|
||||
void actMoveBottomCardsToGrave();
|
||||
void actMoveBottomCardsToExile();
|
||||
void actMoveBottomCardToTop();
|
||||
|
||||
void actViewLibrary();
|
||||
void actViewHand();
|
||||
void actViewTopCards();
|
||||
void actAlwaysRevealTopCard();
|
||||
void actAlwaysLookAtTopCard();
|
||||
void actViewGraveyard();
|
||||
void actRevealRandomGraveyardCard();
|
||||
void actViewRfg();
|
||||
void actViewSideboard();
|
||||
|
||||
void actSayMessage();
|
||||
private slots:
|
||||
void addPlayer(Player *player);
|
||||
void removePlayer(Player *player);
|
||||
void playerListActionTriggered();
|
||||
|
||||
void updateBoundingRect();
|
||||
void rearrangeZones();
|
||||
|
||||
void actOpenDeckInDeckEditor();
|
||||
void actCreatePredefinedToken();
|
||||
void actCreateRelatedCard();
|
||||
void actCreateAllRelatedCards();
|
||||
void cardMenuAction();
|
||||
void actMoveCardXCardsFromTop();
|
||||
void actCardCounterTrigger();
|
||||
void actAttach();
|
||||
void actUnattach();
|
||||
void actDrawArrow();
|
||||
void actIncPT(int deltaP, int deltaT);
|
||||
void actResetPT();
|
||||
void actSetPT();
|
||||
void actIncP();
|
||||
void actDecP();
|
||||
void actIncT();
|
||||
void actDecT();
|
||||
void actIncPT();
|
||||
void actDecPT();
|
||||
void actFlowP();
|
||||
void actFlowT();
|
||||
void actSetAnnotation();
|
||||
void actPlay();
|
||||
void actHide();
|
||||
void actPlayFacedown();
|
||||
void actReveal(QAction *action);
|
||||
void refreshShortcuts();
|
||||
|
||||
void initSayMenu();
|
||||
|
||||
private:
|
||||
TabGame *game;
|
||||
QMenu *sbMenu, *countersMenu, *sayMenu, *createPredefinedTokenMenu, *mRevealLibrary, *mLendLibrary, *mRevealTopCard,
|
||||
*mRevealHand, *mRevealRandomHandCard, *mRevealRandomGraveyardCard;
|
||||
TearOffMenu *moveGraveMenu, *moveRfgMenu, *graveMenu, *moveHandMenu, *handMenu, *libraryMenu, *topLibraryMenu,
|
||||
*bottomLibraryMenu, *rfgMenu, *playerMenu;
|
||||
QList<QMenu *> playerLists;
|
||||
QList<QMenu *> singlePlayerLists;
|
||||
QList<QAction *> allPlayersActions;
|
||||
QList<QPair<QString, int>> playersInfo;
|
||||
QAction *aMoveHandToTopLibrary, *aMoveHandToBottomLibrary, *aMoveHandToGrave, *aMoveHandToRfg,
|
||||
*aMoveGraveToTopLibrary, *aMoveGraveToBottomLibrary, *aMoveGraveToHand, *aMoveGraveToRfg, *aMoveRfgToTopLibrary,
|
||||
*aMoveRfgToBottomLibrary, *aMoveRfgToHand, *aMoveRfgToGrave, *aViewHand, *aViewLibrary, *aViewTopCards,
|
||||
*aAlwaysRevealTopCard, *aAlwaysLookAtTopCard, *aOpenDeckInDeckEditor, *aMoveTopCardToGraveyard,
|
||||
*aMoveTopCardToExile, *aMoveTopCardsToGraveyard, *aMoveTopCardsToExile, *aMoveTopCardsUntil,
|
||||
*aMoveTopCardToBottom, *aViewGraveyard, *aViewRfg, *aViewSideboard, *aDrawCard, *aDrawCards, *aUndoDraw,
|
||||
*aMulligan, *aShuffle, *aMoveTopToPlay, *aMoveTopToPlayFaceDown, *aUntapAll, *aRollDie, *aCreateToken,
|
||||
*aCreateAnotherToken, *aCardMenu, *aMoveBottomToPlay, *aMoveBottomToPlayFaceDown, *aMoveBottomCardToTop,
|
||||
*aMoveBottomCardToGraveyard, *aMoveBottomCardToExile, *aMoveBottomCardsToGraveyard, *aMoveBottomCardsToExile,
|
||||
*aDrawBottomCard, *aDrawBottomCards;
|
||||
|
||||
QList<QAction *> aAddCounter, aSetCounter, aRemoveCounter;
|
||||
QAction *aPlay, *aPlayFacedown, *aHide, *aTap, *aDoesntUntap, *aAttach, *aUnattach, *aDrawArrow, *aSetPT, *aResetPT,
|
||||
*aIncP, *aDecP, *aIncT, *aDecT, *aIncPT, *aDecPT, *aFlowP, *aFlowT, *aSetAnnotation, *aFlip, *aPeek, *aClone,
|
||||
*aMoveToTopLibrary, *aMoveToBottomLibrary, *aMoveToHand, *aMoveToGraveyard, *aMoveToExile,
|
||||
*aMoveToXfromTopOfLibrary;
|
||||
|
||||
bool movingCardsUntil;
|
||||
QTimer *moveTopCardTimer;
|
||||
QString previousMovingCardsUntilExpr = {};
|
||||
FilterString movingCardsUntilFilter;
|
||||
|
||||
bool shortcutsActive;
|
||||
int defaultNumberTopCards = 1;
|
||||
int defaultNumberTopCardsToPlaceBelow = 1;
|
||||
int defaultNumberBottomCards = 1;
|
||||
int defaultNumberDieRoll = 20;
|
||||
QString lastTokenName, lastTokenColor, lastTokenPT, lastTokenAnnotation;
|
||||
bool lastTokenDestroy;
|
||||
int lastTokenTableRow;
|
||||
ServerInfo_User *userInfo;
|
||||
int id;
|
||||
bool active;
|
||||
bool local;
|
||||
bool judge;
|
||||
bool mirrored;
|
||||
bool handVisible;
|
||||
bool conceded;
|
||||
int zoneId;
|
||||
|
||||
bool dialogSemaphore;
|
||||
bool clearCardsToDelete();
|
||||
QList<CardItem *> cardsToDelete;
|
||||
|
||||
DeckLoader *deck;
|
||||
QStringList predefinedTokens;
|
||||
|
||||
PlayerArea *playerArea;
|
||||
QMap<QString, CardZone *> zones;
|
||||
StackZone *stack;
|
||||
TableZone *table;
|
||||
HandZone *hand;
|
||||
PlayerTarget *playerTarget;
|
||||
|
||||
void setCardAttrHelper(const GameEventContext &context,
|
||||
CardItem *card,
|
||||
CardAttribute attribute,
|
||||
const QString &avalue,
|
||||
bool allCards);
|
||||
void addRelatedCardActions(const CardItem *card, QMenu *cardMenu);
|
||||
void addRelatedCardView(const CardItem *card, QMenu *cardMenu);
|
||||
void createCard(const CardItem *sourceCard,
|
||||
const QString &dbCardName,
|
||||
CardRelation::AttachType attach = CardRelation::DoesNotAttach,
|
||||
bool persistent = false);
|
||||
bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation);
|
||||
void moveOneCardUntil(const CardInfoPtr card);
|
||||
void addPlayerToList(QMenu *playerList, Player *player);
|
||||
static void removePlayerFromList(QMenu *playerList, Player *player);
|
||||
|
||||
QRectF bRect;
|
||||
|
||||
QMap<int, AbstractCounter *> counters;
|
||||
QMap<int, ArrowItem *> arrows;
|
||||
void rearrangeCounters();
|
||||
|
||||
void initContextualPlayersMenu(QMenu *menu);
|
||||
|
||||
// void eventConnectionStateChanged(const Event_ConnectionStateChanged &event);
|
||||
void eventGameSay(const Event_GameSay &event);
|
||||
void eventShuffle(const Event_Shuffle &event);
|
||||
void eventRollDie(const Event_RollDie &event);
|
||||
void eventCreateArrow(const Event_CreateArrow &event);
|
||||
void eventDeleteArrow(const Event_DeleteArrow &event);
|
||||
void eventCreateToken(const Event_CreateToken &event);
|
||||
void eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context);
|
||||
void eventSetCardCounter(const Event_SetCardCounter &event);
|
||||
void eventCreateCounter(const Event_CreateCounter &event);
|
||||
void eventSetCounter(const Event_SetCounter &event);
|
||||
void eventDelCounter(const Event_DelCounter &event);
|
||||
void eventDumpZone(const Event_DumpZone &event);
|
||||
void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context);
|
||||
void eventFlipCard(const Event_FlipCard &event);
|
||||
void eventDestroyCard(const Event_DestroyCard &event);
|
||||
void eventAttachCard(const Event_AttachCard &event);
|
||||
void eventDrawCards(const Event_DrawCards &event);
|
||||
void eventRevealCards(const Event_RevealCards &event);
|
||||
void eventChangeZoneProperties(const Event_ChangeZoneProperties &event);
|
||||
void cmdSetTopCard(Command_MoveCard &cmd);
|
||||
void cmdSetBottomCard(Command_MoveCard &cmd);
|
||||
|
||||
QVariantList parsePT(const QString &pt);
|
||||
|
||||
public:
|
||||
static const int counterAreaWidth = 55;
|
||||
enum CardMenuActionType
|
||||
{
|
||||
cmTap,
|
||||
cmUntap,
|
||||
cmDoesntUntap,
|
||||
cmFlip,
|
||||
cmPeek,
|
||||
cmClone,
|
||||
cmMoveToTopLibrary,
|
||||
cmMoveToBottomLibrary,
|
||||
cmMoveToHand,
|
||||
cmMoveToGraveyard,
|
||||
cmMoveToExile
|
||||
};
|
||||
enum CardsToReveal
|
||||
{
|
||||
RANDOM_CARD_FROM_ZONE = -2
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
Type = typeOther
|
||||
};
|
||||
int type() const override
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
QRectF boundingRect() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
|
||||
void playCard(CardItem *c, bool faceDown, bool tapped);
|
||||
void addCard(CardItem *c);
|
||||
void deleteCard(CardItem *c);
|
||||
void addZone(CardZone *z);
|
||||
|
||||
AbstractCounter *addCounter(const ServerInfo_Counter &counter);
|
||||
AbstractCounter *addCounter(int counterId, const QString &name, QColor color, int radius, int value);
|
||||
void delCounter(int counterId);
|
||||
void clearCounters();
|
||||
|
||||
ArrowItem *addArrow(const ServerInfo_Arrow &arrow);
|
||||
ArrowItem *addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color);
|
||||
void delArrow(int arrowId);
|
||||
void removeArrow(ArrowItem *arrow);
|
||||
void clearArrows();
|
||||
PlayerTarget *getPlayerTarget() const
|
||||
{
|
||||
return playerTarget;
|
||||
}
|
||||
|
||||
Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, TabGame *_parent);
|
||||
~Player() override;
|
||||
|
||||
void retranslateUi();
|
||||
void clear();
|
||||
TabGame *getGame() const
|
||||
{
|
||||
return game;
|
||||
}
|
||||
void setDeck(const DeckLoader &_deck);
|
||||
QMenu *getPlayerMenu() const
|
||||
{
|
||||
return playerMenu;
|
||||
}
|
||||
int getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
QString getName() const;
|
||||
ServerInfo_User *getUserInfo() const
|
||||
{
|
||||
return userInfo;
|
||||
}
|
||||
bool getLocal() const
|
||||
{
|
||||
return local;
|
||||
}
|
||||
bool getLocalOrJudge() const
|
||||
{
|
||||
return local || judge;
|
||||
}
|
||||
bool getJudge() const
|
||||
{
|
||||
return judge;
|
||||
}
|
||||
bool getMirrored() const
|
||||
{
|
||||
return mirrored;
|
||||
}
|
||||
int getZoneId() const
|
||||
{
|
||||
return zoneId;
|
||||
}
|
||||
void setZoneId(int _zoneId);
|
||||
const QMap<QString, CardZone *> &getZones() const
|
||||
{
|
||||
return zones;
|
||||
}
|
||||
const QMap<int, ArrowItem *> &getArrows() const
|
||||
{
|
||||
return arrows;
|
||||
}
|
||||
void setCardMenu(QMenu *menu);
|
||||
QMenu *getCardMenu() const;
|
||||
void updateCardMenu(const CardItem *card);
|
||||
bool getActive() const
|
||||
{
|
||||
return active;
|
||||
}
|
||||
void setActive(bool _active);
|
||||
void setShortcutsActive();
|
||||
void setShortcutsInactive();
|
||||
void updateZones();
|
||||
|
||||
void setConceded(bool _conceded);
|
||||
bool getConceded() const
|
||||
{
|
||||
return conceded;
|
||||
}
|
||||
|
||||
void setGameStarted();
|
||||
|
||||
qreal getMinimumWidth() const;
|
||||
void setMirrored(bool _mirrored);
|
||||
void processSceneSizeChange(int newPlayerWidth);
|
||||
|
||||
void processPlayerInfo(const ServerInfo_Player &info);
|
||||
void processCardAttachment(const ServerInfo_Player &info);
|
||||
|
||||
void processGameEvent(GameEvent::GameEventType type, const GameEvent &event, const GameEventContext &context);
|
||||
|
||||
PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
|
||||
PendingCommand *prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList);
|
||||
void sendGameCommand(PendingCommand *pend);
|
||||
void sendGameCommand(const google::protobuf::Message &command);
|
||||
|
||||
void setLastToken(CardInfoPtr cardInfo);
|
||||
};
|
||||
|
||||
class AnnotationDialog : public QInputDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
public:
|
||||
AnnotationDialog(QWidget *parent) : QInputDialog(parent)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
227
cockatrice/src/game/player/player_list_widget.cpp
Normal file
227
cockatrice/src/game/player/player_list_widget.cpp
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
#include "player_list_widget.h"
|
||||
|
||||
#include "../../client/game_logic/abstract_client.h"
|
||||
#include "../../client/tabs/tab_account.h"
|
||||
#include "../../client/tabs/tab_game.h"
|
||||
#include "../../client/tabs/tab_supervisor.h"
|
||||
#include "../../client/ui/pixel_map_generator.h"
|
||||
#include "../../server/user/user_context_menu.h"
|
||||
#include "../../server/user/user_list.h"
|
||||
#include "pb/command_kick_from_game.pb.h"
|
||||
#include "pb/serverinfo_playerproperties.pb.h"
|
||||
#include "pb/session_commands.pb.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QMouseEvent>
|
||||
|
||||
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()) {
|
||||
QMouseEvent *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,
|
||||
TabGame *_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, SIGNAL(openMessageDialog(QString, bool)), this,
|
||||
SIGNAL(openMessageDialog(QString, bool)));
|
||||
} 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, QIcon(UserLevelPixmapGenerator::generatePixmap(12, UserLevelFlags(prop.user_info().user_level()), 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);
|
||||
}
|
||||
56
cockatrice/src/game/player/player_list_widget.h
Normal file
56
cockatrice/src/game/player/player_list_widget.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#ifndef PLAYERLISTWIDGET_H
|
||||
#define PLAYERLISTWIDGET_H
|
||||
|
||||
#include <QIcon>
|
||||
#include <QMap>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QTreeWidget>
|
||||
|
||||
class ServerInfo_PlayerProperties;
|
||||
class TabSupervisor;
|
||||
class AbstractClient;
|
||||
class TabGame;
|
||||
class UserContextMenu;
|
||||
|
||||
class PlayerListItemDelegate : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
PlayerListItemDelegate(QObject *const parent);
|
||||
bool
|
||||
editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index);
|
||||
};
|
||||
|
||||
class PlayerListTWI : public QTreeWidgetItem
|
||||
{
|
||||
public:
|
||||
PlayerListTWI();
|
||||
bool operator<(const QTreeWidgetItem &other) const;
|
||||
};
|
||||
|
||||
class PlayerListWidget : public QTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
PlayerListItemDelegate *itemDelegate;
|
||||
QMap<int, QTreeWidgetItem *> players;
|
||||
TabSupervisor *tabSupervisor;
|
||||
AbstractClient *client;
|
||||
TabGame *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, TabGame *_game, QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
void addPlayer(const ServerInfo_PlayerProperties &player);
|
||||
void removePlayer(int playerId);
|
||||
void setActivePlayer(int playerId);
|
||||
void updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId = -1);
|
||||
void setGameStarted(bool _gameStarted, bool resuming);
|
||||
void showContextMenu(const QPoint &pos, const QModelIndex &index);
|
||||
};
|
||||
|
||||
#endif
|
||||
169
cockatrice/src/game/player/player_target.cpp
Normal file
169
cockatrice/src/game/player/player_target.cpp
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
#include "player_target.h"
|
||||
|
||||
#include "../../client/ui/pixel_map_generator.h"
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
#include "player.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <QPixmapCache>
|
||||
#include <QtMath>
|
||||
|
||||
PlayerCounter::PlayerCounter(Player *_player,
|
||||
int _id,
|
||||
const QString &_name,
|
||||
int _value,
|
||||
QGraphicsItem *parent,
|
||||
QWidget *game)
|
||||
: AbstractCounter(_player, _id, _name, false, _value, false, parent, game)
|
||||
{
|
||||
}
|
||||
|
||||
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(Player *_owner, QGraphicsItem *parentItem, QWidget *_game)
|
||||
: ArrowTarget(_owner, parentItem), playerCounter(nullptr), game(_game)
|
||||
{
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
|
||||
const std::string &bmp = _owner->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->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);
|
||||
|
||||
QPixmap tempPixmap;
|
||||
if (fullPixmap.isNull())
|
||||
tempPixmap =
|
||||
UserLevelPixmapGenerator::generatePixmap(translatedSize.height(), UserLevelFlags(info->user_level()),
|
||||
false, QString::fromStdString(info->privlevel()));
|
||||
else
|
||||
tempPixmap = fullPixmap.scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
tempPainter.drawPixmap((translatedSize.width() - tempPixmap.width()) / 2,
|
||||
(translatedSize.height() - tempPixmap.height()) / 2, 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(int _counterId, const QString &_name, int _value)
|
||||
{
|
||||
if (playerCounter) {
|
||||
disconnect(playerCounter, nullptr, this, nullptr);
|
||||
playerCounter->delCounter();
|
||||
}
|
||||
|
||||
playerCounter = new PlayerCounter(owner, _counterId, _name, _value, this, game);
|
||||
playerCounter->setPos(boundingRect().width() - playerCounter->boundingRect().width(),
|
||||
boundingRect().height() - playerCounter->boundingRect().height());
|
||||
connect(playerCounter, SIGNAL(destroyed()), this, SLOT(counterDeleted()));
|
||||
|
||||
return playerCounter;
|
||||
}
|
||||
|
||||
void PlayerTarget::counterDeleted()
|
||||
{
|
||||
playerCounter = nullptr;
|
||||
}
|
||||
54
cockatrice/src/game/player/player_target.h
Normal file
54
cockatrice/src/game/player/player_target.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef PLAYERTARGET_H
|
||||
#define PLAYERTARGET_H
|
||||
|
||||
#include "../board/abstract_counter.h"
|
||||
#include "../board/arrow_target.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QPixmap>
|
||||
|
||||
class Player;
|
||||
|
||||
class PlayerCounter : public AbstractCounter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PlayerCounter(Player *_player,
|
||||
int _id,
|
||||
const QString &_name,
|
||||
int _value,
|
||||
QGraphicsItem *parent = nullptr,
|
||||
QWidget *game = nullptr);
|
||||
QRectF boundingRect() const;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
};
|
||||
|
||||
class PlayerTarget : public ArrowTarget
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QPixmap fullPixmap;
|
||||
PlayerCounter *playerCounter;
|
||||
QWidget *game;
|
||||
public slots:
|
||||
void counterDeleted();
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
Type = typePlayerTarget
|
||||
};
|
||||
int type() const
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
|
||||
PlayerTarget(Player *_player = nullptr, QGraphicsItem *parentItem = nullptr, QWidget *_game = nullptr);
|
||||
~PlayerTarget();
|
||||
QRectF boundingRect() const;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
|
||||
AbstractCounter *addCounter(int _counterId, const QString &_name, int _value);
|
||||
};
|
||||
|
||||
#endif
|
||||
217
cockatrice/src/game/zones/card_zone.cpp
Normal file
217
cockatrice/src/game/zones/card_zone.cpp
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
#include "card_zone.h"
|
||||
|
||||
#include "../cards/card_item.h"
|
||||
#include "../player/player.h"
|
||||
#include "pb/command_move_card.pb.h"
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
#include "view_zone.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QMenu>
|
||||
|
||||
CardZone::CardZone(Player *_p,
|
||||
const QString &_name,
|
||||
bool _hasCardAttr,
|
||||
bool _isShufflable,
|
||||
bool _contentsKnown,
|
||||
QGraphicsItem *parent,
|
||||
bool _isView)
|
||||
: AbstractGraphicsItem(parent), player(_p), name(_name), cards(_contentsKnown), views{}, menu(nullptr),
|
||||
doubleClickAction(0), hasCardAttr(_hasCardAttr), isShufflable(_isShufflable), isView(_isView)
|
||||
{
|
||||
if (!isView)
|
||||
player->addZone(this);
|
||||
}
|
||||
|
||||
CardZone::~CardZone()
|
||||
{
|
||||
qDebug() << "CardZone destructor: " << name;
|
||||
for (auto *view : views) {
|
||||
if (view != nullptr) {
|
||||
view->deleteLater();
|
||||
}
|
||||
}
|
||||
clearContents();
|
||||
}
|
||||
|
||||
void CardZone::retranslateUi()
|
||||
{
|
||||
for (int i = 0; i < cards.size(); ++i)
|
||||
cards[i]->retranslateUi();
|
||||
}
|
||||
|
||||
void CardZone::clearContents()
|
||||
{
|
||||
for (int i = 0; i < cards.size(); i++) {
|
||||
// If an incorrectly implemented server doesn't return attached cards to whom they belong before dropping a
|
||||
// player, we have to return them to avoid a crash.
|
||||
const QList<CardItem *> &attachedCards = cards[i]->getAttachedCards();
|
||||
for (auto attachedCard : attachedCards)
|
||||
attachedCard->setParentItem(attachedCard->getZone());
|
||||
|
||||
player->deleteCard(cards.at(i));
|
||||
}
|
||||
cards.clear();
|
||||
emit cardCountChanged();
|
||||
}
|
||||
|
||||
QString CardZone::getTranslatedName(bool theirOwn, GrammaticalCase gc) const
|
||||
{
|
||||
QString ownerName = player->getName();
|
||||
if (name == "hand")
|
||||
return (theirOwn ? tr("their hand", "nominative") : tr("%1's hand", "nominative").arg(ownerName));
|
||||
else if (name == "deck")
|
||||
switch (gc) {
|
||||
case CaseLookAtZone:
|
||||
return (theirOwn ? tr("their library", "look at zone")
|
||||
: tr("%1's library", "look at zone").arg(ownerName));
|
||||
case CaseTopCardsOfZone:
|
||||
return (theirOwn ? tr("of their library", "top cards of zone,")
|
||||
: tr("of %1's library", "top cards of zone").arg(ownerName));
|
||||
case CaseRevealZone:
|
||||
return (theirOwn ? tr("their library", "reveal zone")
|
||||
: tr("%1's library", "reveal zone").arg(ownerName));
|
||||
case CaseShuffleZone:
|
||||
return (theirOwn ? tr("their library", "shuffle") : tr("%1's library", "shuffle").arg(ownerName));
|
||||
default:
|
||||
return (theirOwn ? tr("their library", "nominative") : tr("%1's library", "nominative").arg(ownerName));
|
||||
}
|
||||
else if (name == "grave")
|
||||
return (theirOwn ? tr("their graveyard", "nominative") : tr("%1's graveyard", "nominative").arg(ownerName));
|
||||
else if (name == "rfg")
|
||||
return (theirOwn ? tr("their exile", "nominative") : tr("%1's exile", "nominative").arg(ownerName));
|
||||
else if (name == "sb")
|
||||
switch (gc) {
|
||||
case CaseLookAtZone:
|
||||
return (theirOwn ? tr("their sideboard", "look at zone")
|
||||
: tr("%1's sideboard", "look at zone").arg(ownerName));
|
||||
case CaseNominative:
|
||||
return (theirOwn ? tr("their sideboard", "nominative")
|
||||
: tr("%1's sideboard", "nominative").arg(ownerName));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
void CardZone::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * /*event*/)
|
||||
{
|
||||
if (doubleClickAction)
|
||||
doubleClickAction->trigger();
|
||||
}
|
||||
|
||||
bool CardZone::showContextMenu(const QPoint &screenPos)
|
||||
{
|
||||
if (menu) {
|
||||
menu->exec(screenPos);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CardZone::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::RightButton) {
|
||||
if (showContextMenu(event->screenPos()))
|
||||
event->accept();
|
||||
else
|
||||
event->ignore();
|
||||
} else
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void CardZone::addCard(CardItem *card, bool reorganize, int x, int y)
|
||||
{
|
||||
for (auto *view : views) {
|
||||
if ((x <= view->getCards().size()) || (view->getNumberCards() == -1)) {
|
||||
view->addCard(new CardItem(player, card->getName(), card->getId()), reorganize, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
card->setZone(this);
|
||||
addCardImpl(card, x, y);
|
||||
|
||||
if (reorganize)
|
||||
reorganizeCards();
|
||||
|
||||
emit cardCountChanged();
|
||||
}
|
||||
|
||||
CardItem *CardZone::getCard(int cardId, const QString &cardName)
|
||||
{
|
||||
CardItem *c = cards.findCard(cardId, false);
|
||||
if (!c) {
|
||||
qDebug() << "CardZone::getCard: card id=" << cardId << "not found";
|
||||
return 0;
|
||||
}
|
||||
// If the card's id is -1, this zone is invisible,
|
||||
// so we need to give the card an id and a name as it comes out.
|
||||
// It can be assumed that in an invisible zone, all cards are equal.
|
||||
if ((c->getId() == -1) || (c->getName().isEmpty())) {
|
||||
c->setId(cardId);
|
||||
c->setName(cardName);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/)
|
||||
{
|
||||
if (position == -1) {
|
||||
// position == -1 means either that the zone is indexed by card id
|
||||
// or that it doesn't matter which card you take.
|
||||
for (int i = 0; i < cards.size(); ++i)
|
||||
if (cards[i]->getId() == cardId) {
|
||||
position = i;
|
||||
break;
|
||||
}
|
||||
if (position == -1)
|
||||
position = 0;
|
||||
}
|
||||
if (position >= cards.size())
|
||||
return 0;
|
||||
|
||||
CardItem *c = cards.takeAt(position);
|
||||
|
||||
for (auto *view : views) {
|
||||
view->removeCard(position);
|
||||
}
|
||||
|
||||
c->setId(cardId);
|
||||
|
||||
reorganizeCards();
|
||||
emit cardCountChanged();
|
||||
return c;
|
||||
}
|
||||
|
||||
void CardZone::removeCard(CardItem *card)
|
||||
{
|
||||
cards.removeOne(card);
|
||||
reorganizeCards();
|
||||
emit cardCountChanged();
|
||||
player->deleteCard(card);
|
||||
}
|
||||
|
||||
void CardZone::moveAllToZone()
|
||||
{
|
||||
QList<QVariant> data = static_cast<QAction *>(sender())->data().toList();
|
||||
QString targetZone = data[0].toString();
|
||||
int targetX = data[1].toInt();
|
||||
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_zone(getName().toStdString());
|
||||
cmd.set_target_player_id(player->getId());
|
||||
cmd.set_target_zone(targetZone.toStdString());
|
||||
cmd.set_x(targetX);
|
||||
|
||||
for (int i = 0; i < cards.size(); ++i)
|
||||
cmd.mutable_cards_to_move()->add_card()->set_card_id(cards[i]->getId());
|
||||
|
||||
player->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
QPointF CardZone::closestGridPoint(const QPointF &point)
|
||||
{
|
||||
return point;
|
||||
}
|
||||
121
cockatrice/src/game/zones/card_zone.h
Normal file
121
cockatrice/src/game/zones/card_zone.h
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#ifndef CARDZONE_H
|
||||
#define CARDZONE_H
|
||||
|
||||
#include "../../client/translation.h"
|
||||
#include "../board/abstract_graphics_item.h"
|
||||
#include "../cards/card_list.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
class Player;
|
||||
class ZoneViewZone;
|
||||
class QMenu;
|
||||
class QAction;
|
||||
class QPainter;
|
||||
class CardDragItem;
|
||||
|
||||
class CardZone : public AbstractGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
Player *player;
|
||||
QString name;
|
||||
CardList cards;
|
||||
QList<ZoneViewZone *> views;
|
||||
QMenu *menu;
|
||||
QAction *doubleClickAction;
|
||||
bool hasCardAttr;
|
||||
bool isShufflable;
|
||||
bool isView;
|
||||
bool alwaysRevealTopCard;
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event);
|
||||
virtual void addCardImpl(CardItem *card, int x, int y) = 0;
|
||||
signals:
|
||||
void cardCountChanged();
|
||||
public slots:
|
||||
void moveAllToZone();
|
||||
bool showContextMenu(const QPoint &screenPos);
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
Type = typeZone
|
||||
};
|
||||
int type() const
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
virtual void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItem, CardZone *startZone, const QPoint &dropPoint) = 0;
|
||||
CardZone(Player *_player,
|
||||
const QString &_name,
|
||||
bool _hasCardAttr,
|
||||
bool _isShufflable,
|
||||
bool _contentsKnown,
|
||||
QGraphicsItem *parent = nullptr,
|
||||
bool _isView = false);
|
||||
~CardZone();
|
||||
void retranslateUi();
|
||||
void clearContents();
|
||||
bool getHasCardAttr() const
|
||||
{
|
||||
return hasCardAttr;
|
||||
}
|
||||
bool getIsShufflable() const
|
||||
{
|
||||
return isShufflable;
|
||||
}
|
||||
QMenu *getMenu() const
|
||||
{
|
||||
return menu;
|
||||
}
|
||||
void setMenu(QMenu *_menu, QAction *_doubleClickAction = 0)
|
||||
{
|
||||
menu = _menu;
|
||||
doubleClickAction = _doubleClickAction;
|
||||
}
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
QString getTranslatedName(bool theirOwn, GrammaticalCase gc) const;
|
||||
Player *getPlayer() const
|
||||
{
|
||||
return player;
|
||||
}
|
||||
bool contentsKnown() const
|
||||
{
|
||||
return cards.getContentsKnown();
|
||||
}
|
||||
const CardList &getCards() const
|
||||
{
|
||||
return cards;
|
||||
}
|
||||
void addCard(CardItem *card, bool reorganize, int x, int y = -1);
|
||||
// getCard() finds a card by id.
|
||||
CardItem *getCard(int cardId, const QString &cardName);
|
||||
// takeCard() finds a card by position and removes it from the zone and from all of its views.
|
||||
virtual CardItem *takeCard(int position, int cardId, bool canResize = true);
|
||||
void removeCard(CardItem *card);
|
||||
QList<ZoneViewZone *> &getViews()
|
||||
{
|
||||
return views;
|
||||
}
|
||||
virtual void reorganizeCards() = 0;
|
||||
virtual QPointF closestGridPoint(const QPointF &point);
|
||||
bool getIsView() const
|
||||
{
|
||||
return isView;
|
||||
}
|
||||
bool getAlwaysRevealTopCard() const
|
||||
{
|
||||
return alwaysRevealTopCard;
|
||||
}
|
||||
void setAlwaysRevealTopCard(bool _alwaysRevealTopCard)
|
||||
{
|
||||
alwaysRevealTopCard = _alwaysRevealTopCard;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
148
cockatrice/src/game/zones/hand_zone.cpp
Normal file
148
cockatrice/src/game/zones/hand_zone.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#include "hand_zone.h"
|
||||
|
||||
#include "../../client/ui/theme_manager.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../cards/card_drag_item.h"
|
||||
#include "../cards/card_item.h"
|
||||
#include "../player/player.h"
|
||||
#include "pb/command_move_card.pb.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
HandZone::HandZone(Player *_p, bool _contentsKnown, int _zoneHeight, QGraphicsItem *parent)
|
||||
: SelectZone(_p, "hand", false, false, _contentsKnown, parent), zoneHeight(_zoneHeight)
|
||||
{
|
||||
connect(themeManager, SIGNAL(themeChanged()), this, SLOT(updateBg()));
|
||||
updateBg();
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
}
|
||||
|
||||
void HandZone::updateBg()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void HandZone::addCardImpl(CardItem *card, int x, int /*y*/)
|
||||
{
|
||||
// if x is negative set it to add at end
|
||||
if (x < 0 || x >= cards.size()) {
|
||||
x = cards.size();
|
||||
}
|
||||
cards.insert(x, card);
|
||||
|
||||
if (!cards.getContentsKnown()) {
|
||||
card->setId(-1);
|
||||
card->setName();
|
||||
}
|
||||
card->setParentItem(this);
|
||||
card->resetState();
|
||||
card->setVisible(true);
|
||||
card->update();
|
||||
}
|
||||
|
||||
void HandZone::handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint)
|
||||
{
|
||||
QPoint point = dropPoint + scenePos().toPoint();
|
||||
int x = -1;
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
for (x = 0; x < cards.size(); x++)
|
||||
if (point.x() < static_cast<CardItem *>(cards.at(x))->scenePos().x())
|
||||
break;
|
||||
} else {
|
||||
for (x = 0; x < cards.size(); x++)
|
||||
if (point.y() < static_cast<CardItem *>(cards.at(x))->scenePos().y())
|
||||
break;
|
||||
}
|
||||
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(player->getId());
|
||||
cmd.set_target_zone(getName().toStdString());
|
||||
cmd.set_x(x);
|
||||
cmd.set_y(-1);
|
||||
|
||||
for (int i = 0; i < dragItems.size(); ++i)
|
||||
cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId());
|
||||
|
||||
player->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
QRectF HandZone::boundingRect() const
|
||||
{
|
||||
if (SettingsCache::instance().getHorizontalHand())
|
||||
return QRectF(0, 0, width, CARD_HEIGHT + 10);
|
||||
else
|
||||
return QRectF(0, 0, 100, zoneHeight);
|
||||
}
|
||||
|
||||
void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush brush = themeManager->getHandBgBrush();
|
||||
|
||||
if (player->getZoneId() > 0) {
|
||||
// If the extra image is not found, load the default one
|
||||
brush = themeManager->getExtraHandBgBrush(QString::number(player->getZoneId()), brush);
|
||||
}
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
}
|
||||
|
||||
void HandZone::reorganizeCards()
|
||||
{
|
||||
if (!cards.isEmpty()) {
|
||||
const int cardCount = cards.size();
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
bool leftJustified = SettingsCache::instance().getLeftJustified();
|
||||
qreal cardWidth = cards.at(0)->boundingRect().width();
|
||||
const int xPadding = leftJustified ? cardWidth * 1.4 : 5;
|
||||
qreal totalWidth =
|
||||
leftJustified ? boundingRect().width() - (1 * xPadding) - 5 : boundingRect().width() - 2 * xPadding;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *c = cards.at(i);
|
||||
// If the total width of the cards is smaller than the available width,
|
||||
// the cards do not need to overlap and are displayed in the center of the area.
|
||||
if (cardWidth * cardCount > totalWidth)
|
||||
c->setPos(xPadding + ((qreal)i) * (totalWidth - cardWidth) / (cardCount - 1), 5);
|
||||
else {
|
||||
qreal xPosition =
|
||||
leftJustified ? xPadding + ((qreal)i) * cardWidth
|
||||
: xPadding + ((qreal)i) * cardWidth + (totalWidth - cardCount * cardWidth) / 2;
|
||||
c->setPos(xPosition, 5);
|
||||
}
|
||||
c->setRealZValue(i);
|
||||
}
|
||||
} else {
|
||||
qreal totalWidth = boundingRect().width();
|
||||
qreal cardWidth = cards.at(0)->boundingRect().width();
|
||||
qreal xspace = 5;
|
||||
qreal x1 = xspace;
|
||||
qreal x2 = totalWidth - xspace - cardWidth;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *card = cards.at(i);
|
||||
qreal x = (i % 2) ? x2 : x1;
|
||||
qreal y =
|
||||
divideCardSpaceInZone(i, cardCount, boundingRect().height(), cards.at(0)->boundingRect().height());
|
||||
card->setPos(x, y);
|
||||
card->setRealZValue(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void HandZone::setWidth(qreal _width)
|
||||
{
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
prepareGeometryChange();
|
||||
width = _width;
|
||||
reorganizeCards();
|
||||
}
|
||||
}
|
||||
|
||||
void HandZone::updateOrientation()
|
||||
{
|
||||
prepareGeometryChange();
|
||||
reorganizeCards();
|
||||
}
|
||||
28
cockatrice/src/game/zones/hand_zone.h
Normal file
28
cockatrice/src/game/zones/hand_zone.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef HANDZONE_H
|
||||
#define HANDZONE_H
|
||||
|
||||
#include "select_zone.h"
|
||||
|
||||
class HandZone : public SelectZone
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
qreal width, zoneHeight;
|
||||
private slots:
|
||||
void updateBg();
|
||||
public slots:
|
||||
void updateOrientation();
|
||||
|
||||
public:
|
||||
HandZone(Player *_p, bool _contentsKnown, int _zoneHeight, QGraphicsItem *parent = nullptr);
|
||||
void handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint);
|
||||
QRectF boundingRect() const;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
void reorganizeCards();
|
||||
void setWidth(qreal _width);
|
||||
|
||||
protected:
|
||||
void addCardImpl(CardItem *card, int x, int y);
|
||||
};
|
||||
|
||||
#endif
|
||||
136
cockatrice/src/game/zones/pile_zone.cpp
Normal file
136
cockatrice/src/game/zones/pile_zone.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#include "pile_zone.h"
|
||||
|
||||
#include "../cards/card_drag_item.h"
|
||||
#include "../cards/card_item.h"
|
||||
#include "../player/player.h"
|
||||
#include "pb/command_move_card.pb.h"
|
||||
#include "view_zone.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
PileZone::PileZone(Player *_p, const QString &_name, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent)
|
||||
: CardZone(_p, _name, false, _isShufflable, _contentsKnown, parent)
|
||||
{
|
||||
setCacheMode(DeviceCoordinateCache); // Do not move this line to the parent constructor!
|
||||
setAcceptHoverEvents(true);
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
|
||||
setTransform(QTransform()
|
||||
.translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2)
|
||||
.rotate(90)
|
||||
.translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2));
|
||||
}
|
||||
|
||||
QRectF PileZone::boundingRect() const
|
||||
{
|
||||
return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
|
||||
}
|
||||
|
||||
QPainterPath PileZone::shape() const
|
||||
{
|
||||
QPainterPath shape;
|
||||
shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH);
|
||||
return shape;
|
||||
}
|
||||
|
||||
void PileZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
painter->drawPath(shape());
|
||||
|
||||
if (!cards.isEmpty())
|
||||
cards.at(0)->paintPicture(painter, cards.at(0)->getTranslatedSize(painter), 90);
|
||||
|
||||
painter->translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2);
|
||||
painter->rotate(-90);
|
||||
painter->translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2);
|
||||
paintNumberEllipse(cards.size(), 28, Qt::white, -1, -1, painter);
|
||||
}
|
||||
|
||||
void PileZone::addCardImpl(CardItem *card, int x, int /*y*/)
|
||||
{
|
||||
connect(card, SIGNAL(sigPixmapUpdated()), this, SLOT(callUpdate()));
|
||||
// if x is negative set it to add at end
|
||||
if (x < 0 || x >= cards.size()) {
|
||||
x = cards.size();
|
||||
}
|
||||
cards.insert(x, card);
|
||||
card->setPos(0, 0);
|
||||
if (!contentsKnown()) {
|
||||
card->setName(QString());
|
||||
card->setId(-1);
|
||||
// If we obscure a previously revealed card, its name has to be forgotten
|
||||
if (cards.size() > x + 1)
|
||||
cards.at(x + 1)->setName(QString());
|
||||
}
|
||||
card->setVisible(false);
|
||||
card->resetState();
|
||||
card->setParentItem(this);
|
||||
}
|
||||
|
||||
void PileZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||
CardZone *startZone,
|
||||
const QPoint & /*dropPoint*/)
|
||||
{
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(player->getId());
|
||||
cmd.set_target_zone(getName().toStdString());
|
||||
cmd.set_x(0);
|
||||
cmd.set_y(0);
|
||||
|
||||
for (int i = 0; i < dragItems.size(); ++i)
|
||||
cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId());
|
||||
|
||||
player->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void PileZone::reorganizeCards()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void PileZone::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
CardZone::mousePressEvent(event);
|
||||
if (event->isAccepted())
|
||||
return;
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
event->accept();
|
||||
} else
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void PileZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
|
||||
QApplication::startDragDistance())
|
||||
return;
|
||||
|
||||
if (cards.isEmpty())
|
||||
return;
|
||||
|
||||
bool faceDown = event->modifiers().testFlag(Qt::ShiftModifier);
|
||||
bool bottomCard = event->modifiers().testFlag(Qt::ControlModifier);
|
||||
CardItem *card = bottomCard ? cards.last() : cards.first();
|
||||
const int cardid = contentsKnown() ? card->getId() : (bottomCard ? cards.size() - 1 : 0);
|
||||
CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), faceDown);
|
||||
drag->grabMouse();
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
}
|
||||
|
||||
void PileZone::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/)
|
||||
{
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
}
|
||||
|
||||
void PileZone::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
|
||||
{
|
||||
if (!cards.isEmpty())
|
||||
cards[0]->processHoverEvent();
|
||||
QGraphicsItem::hoverEnterEvent(event);
|
||||
}
|
||||
35
cockatrice/src/game/zones/pile_zone.h
Normal file
35
cockatrice/src/game/zones/pile_zone.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef PILEZONE_H
|
||||
#define PILEZONE_H
|
||||
|
||||
#include "card_zone.h"
|
||||
|
||||
class PileZone : public CardZone
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void callUpdate()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
public:
|
||||
PileZone(Player *_p,
|
||||
const QString &_name,
|
||||
bool _isShufflable,
|
||||
bool _contentsKnown,
|
||||
QGraphicsItem *parent = nullptr);
|
||||
QRectF boundingRect() const override;
|
||||
QPainterPath shape() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
void reorganizeCards() override;
|
||||
void handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint) override;
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
|
||||
void addCardImpl(CardItem *card, int x, int y) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
90
cockatrice/src/game/zones/select_zone.cpp
Normal file
90
cockatrice/src/game/zones/select_zone.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#include "select_zone.h"
|
||||
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../cards/card_item.h"
|
||||
#include "../game_scene.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
|
||||
qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse)
|
||||
{
|
||||
qreal cardMinOverlap = cardHeight * SettingsCache::instance().getStackCardOverlapPercent() / 100;
|
||||
qreal desiredHeight = cardHeight * cardCount - cardMinOverlap * (cardCount - 1);
|
||||
qreal y;
|
||||
if (desiredHeight > totalHeight) {
|
||||
if (reverse) {
|
||||
y = index / ((totalHeight - cardHeight) / (cardCount - 1));
|
||||
} else {
|
||||
y = index * (totalHeight - cardHeight) / (cardCount - 1);
|
||||
}
|
||||
} else {
|
||||
qreal start = (totalHeight - desiredHeight) / 2;
|
||||
if (reverse) {
|
||||
if (index <= start) {
|
||||
return 0;
|
||||
}
|
||||
y = (index - start) / (cardHeight - cardMinOverlap);
|
||||
} else {
|
||||
y = index * (cardHeight - cardMinOverlap) + start;
|
||||
}
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
SelectZone::SelectZone(Player *_player,
|
||||
const QString &_name,
|
||||
bool _hasCardAttr,
|
||||
bool _isShufflable,
|
||||
bool _contentsKnown,
|
||||
QGraphicsItem *parent,
|
||||
bool isView)
|
||||
: CardZone(_player, _name, _hasCardAttr, _isShufflable, _contentsKnown, parent, isView)
|
||||
{
|
||||
}
|
||||
|
||||
void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->buttons().testFlag(Qt::LeftButton)) {
|
||||
QPointF pos = event->pos();
|
||||
if (pos.x() < 0)
|
||||
pos.setX(0);
|
||||
QRectF br = boundingRect();
|
||||
if (pos.x() > br.width())
|
||||
pos.setX(br.width());
|
||||
if (pos.y() < 0)
|
||||
pos.setY(0);
|
||||
if (pos.y() > br.height())
|
||||
pos.setY(br.height());
|
||||
|
||||
QRectF selectionRect = QRectF(selectionOrigin, pos).normalized();
|
||||
for (int i = 0; i < cards.size(); ++i) {
|
||||
if (cards[i]->getAttachedTo())
|
||||
if (cards[i]->getAttachedTo()->getZone() != this)
|
||||
continue;
|
||||
cards[i]->setSelected(selectionRect.intersects(cards[i]->mapRectToParent(cards[i]->boundingRect())));
|
||||
}
|
||||
static_cast<GameScene *>(scene())->resizeRubberBand(
|
||||
deviceTransform(static_cast<GameScene *>(scene())->getViewportTransform()).map(pos));
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
scene()->clearSelection();
|
||||
|
||||
selectionOrigin = event->pos();
|
||||
static_cast<GameScene *>(scene())->startRubberBand(event->scenePos());
|
||||
event->accept();
|
||||
} else
|
||||
CardZone::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void SelectZone::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
selectionOrigin = QPoint();
|
||||
static_cast<GameScene *>(scene())->stopRubberBand();
|
||||
event->accept();
|
||||
}
|
||||
29
cockatrice/src/game/zones/select_zone.h
Normal file
29
cockatrice/src/game/zones/select_zone.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef SELECTZONE_H
|
||||
#define SELECTZONE_H
|
||||
|
||||
#include "card_zone.h"
|
||||
|
||||
class SelectZone : public CardZone
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QPointF selectionOrigin;
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
|
||||
|
||||
public:
|
||||
SelectZone(Player *_player,
|
||||
const QString &_name,
|
||||
bool _hasCardAttr,
|
||||
bool _isShufflable,
|
||||
bool _contentsKnown,
|
||||
QGraphicsItem *parent = nullptr,
|
||||
bool isView = false);
|
||||
};
|
||||
|
||||
qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse = false);
|
||||
|
||||
#endif
|
||||
123
cockatrice/src/game/zones/stack_zone.cpp
Normal file
123
cockatrice/src/game/zones/stack_zone.cpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
#include "stack_zone.h"
|
||||
|
||||
#include "../../client/ui/theme_manager.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../board/arrow_item.h"
|
||||
#include "../cards/card_drag_item.h"
|
||||
#include "../cards/card_item.h"
|
||||
#include "../player/player.h"
|
||||
#include "pb/command_move_card.pb.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QSet>
|
||||
|
||||
StackZone::StackZone(Player *_p, int _zoneHeight, QGraphicsItem *parent)
|
||||
: SelectZone(_p, "stack", false, false, true, parent), zoneHeight(_zoneHeight)
|
||||
{
|
||||
connect(themeManager, SIGNAL(themeChanged()), this, SLOT(updateBg()));
|
||||
updateBg();
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
}
|
||||
|
||||
void StackZone::updateBg()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void StackZone::addCardImpl(CardItem *card, int x, int /*y*/)
|
||||
{
|
||||
// if x is negative set it to add at end
|
||||
if (x < 0 || x >= cards.size()) {
|
||||
x = cards.size();
|
||||
}
|
||||
cards.insert(x, card);
|
||||
|
||||
if (!cards.getContentsKnown()) {
|
||||
card->setId(-1);
|
||||
card->setName();
|
||||
}
|
||||
card->setParentItem(this);
|
||||
card->resetState();
|
||||
card->setVisible(true);
|
||||
card->update();
|
||||
}
|
||||
|
||||
QRectF StackZone::boundingRect() const
|
||||
{
|
||||
return QRectF(0, 0, 100, zoneHeight);
|
||||
}
|
||||
|
||||
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush brush = themeManager->getStackBgBrush();
|
||||
|
||||
if (player->getZoneId() > 0) {
|
||||
// If the extra image is not found, load the default one
|
||||
brush = themeManager->getExtraStackBgBrush(QString::number(player->getZoneId()), brush);
|
||||
}
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
}
|
||||
|
||||
void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint)
|
||||
{
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(player->getId());
|
||||
cmd.set_target_zone(getName().toStdString());
|
||||
int index;
|
||||
if (cards.isEmpty()) {
|
||||
index = 0;
|
||||
} else {
|
||||
const int cardCount = cards.size();
|
||||
index = qRound(divideCardSpaceInZone(dropPoint.y(), cardCount, boundingRect().height(),
|
||||
cards.at(0)->boundingRect().height(), true));
|
||||
}
|
||||
if (startZone == this) {
|
||||
if (cards.at(index)->getId() == dragItems.at(0)->getId()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
cmd.set_x(index);
|
||||
cmd.set_y(0);
|
||||
|
||||
for (CardDragItem *item : dragItems) {
|
||||
cmd.mutable_cards_to_move()->add_card()->set_card_id(item->getId());
|
||||
}
|
||||
|
||||
player->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void StackZone::reorganizeCards()
|
||||
{
|
||||
if (!cards.isEmpty()) {
|
||||
QSet<ArrowItem *> arrowsToUpdate;
|
||||
|
||||
const int cardCount = cards.size();
|
||||
qreal totalWidth = boundingRect().width();
|
||||
qreal cardWidth = cards.at(0)->boundingRect().width();
|
||||
qreal xspace = 5;
|
||||
qreal x1 = xspace;
|
||||
qreal x2 = totalWidth - xspace - cardWidth;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *card = cards.at(i);
|
||||
qreal x = (i % 2) ? x2 : x1;
|
||||
qreal y =
|
||||
divideCardSpaceInZone(i, cardCount, boundingRect().height(), cards.at(0)->boundingRect().height());
|
||||
card->setPos(x, y);
|
||||
card->setRealZValue(i);
|
||||
|
||||
for (ArrowItem *item : card->getArrowsFrom()) {
|
||||
arrowsToUpdate.insert(item);
|
||||
}
|
||||
for (ArrowItem *item : card->getArrowsTo()) {
|
||||
arrowsToUpdate.insert(item);
|
||||
}
|
||||
}
|
||||
for (ArrowItem *item : arrowsToUpdate) {
|
||||
item->updatePath();
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
25
cockatrice/src/game/zones/stack_zone.h
Normal file
25
cockatrice/src/game/zones/stack_zone.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef STACKZONE_H
|
||||
#define STACKZONE_H
|
||||
|
||||
#include "select_zone.h"
|
||||
|
||||
class StackZone : public SelectZone
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
qreal zoneHeight;
|
||||
private slots:
|
||||
void updateBg();
|
||||
|
||||
public:
|
||||
StackZone(Player *_p, int _zoneHeight, QGraphicsItem *parent = nullptr);
|
||||
void handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint);
|
||||
QRectF boundingRect() const;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
void reorganizeCards();
|
||||
|
||||
protected:
|
||||
void addCardImpl(CardItem *card, int x, int y);
|
||||
};
|
||||
|
||||
#endif
|
||||
404
cockatrice/src/game/zones/table_zone.cpp
Normal file
404
cockatrice/src/game/zones/table_zone.cpp
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
#include "table_zone.h"
|
||||
|
||||
#include "../../client/ui/theme_manager.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../board/arrow_item.h"
|
||||
#include "../cards/card_database.h"
|
||||
#include "../cards/card_drag_item.h"
|
||||
#include "../cards/card_item.h"
|
||||
#include "../player/player.h"
|
||||
#include "pb/command_move_card.pb.h"
|
||||
#include "pb/command_set_card_attr.pb.h"
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QPainter>
|
||||
#include <QSet>
|
||||
|
||||
const QColor TableZone::BACKGROUND_COLOR = QColor(100, 100, 100);
|
||||
const QColor TableZone::FADE_MASK = QColor(0, 0, 0, 80);
|
||||
const QColor TableZone::GRADIENT_COLOR = QColor(255, 255, 255, 150);
|
||||
const QColor TableZone::GRADIENT_COLORLESS = QColor(255, 255, 255, 0);
|
||||
|
||||
TableZone::TableZone(Player *_p, QGraphicsItem *parent)
|
||||
: SelectZone(_p, "table", true, false, true, parent), active(false)
|
||||
{
|
||||
connect(themeManager, SIGNAL(themeChanged()), this, SLOT(updateBg()));
|
||||
connect(&SettingsCache::instance(), SIGNAL(invertVerticalCoordinateChanged()), this, SLOT(reorganizeCards()));
|
||||
|
||||
updateBg();
|
||||
|
||||
height = MARGIN_TOP + MARGIN_BOTTOM + TABLEROWS * CARD_HEIGHT + (TABLEROWS - 1) * PADDING_Y;
|
||||
width = MIN_WIDTH;
|
||||
currentMinimumWidth = width;
|
||||
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
setAcceptHoverEvents(true);
|
||||
}
|
||||
|
||||
void TableZone::updateBg()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
QRectF TableZone::boundingRect() const
|
||||
{
|
||||
return QRectF(0, 0, width, height);
|
||||
}
|
||||
|
||||
bool TableZone::isInverted() const
|
||||
{
|
||||
return ((player->getMirrored() && !SettingsCache::instance().getInvertVerticalCoordinate()) ||
|
||||
(!player->getMirrored() && SettingsCache::instance().getInvertVerticalCoordinate()));
|
||||
}
|
||||
|
||||
void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush brush = themeManager->getTableBgBrush();
|
||||
|
||||
if (player->getZoneId() > 0) {
|
||||
// If the extra image is not found, load the default one
|
||||
brush = themeManager->getExtraTableBgBrush(QString::number(player->getZoneId()), brush);
|
||||
}
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
|
||||
if (active) {
|
||||
paintZoneOutline(painter);
|
||||
} else {
|
||||
// inactive player gets a darker table zone with a semi transparent black mask
|
||||
// this means if the user provides a custom background it will fade
|
||||
painter->fillRect(boundingRect(), FADE_MASK);
|
||||
}
|
||||
|
||||
paintLandDivider(painter);
|
||||
}
|
||||
|
||||
/**
|
||||
Render a soft outline around the edge of the TableZone.
|
||||
|
||||
@param painter QPainter object
|
||||
*/
|
||||
void TableZone::paintZoneOutline(QPainter *painter)
|
||||
{
|
||||
QLinearGradient grad1(0, 0, 0, 1);
|
||||
grad1.setCoordinateMode(QGradient::ObjectBoundingMode);
|
||||
grad1.setColorAt(0, GRADIENT_COLOR);
|
||||
grad1.setColorAt(1, GRADIENT_COLORLESS);
|
||||
painter->fillRect(QRectF(0, 0, width, BOX_LINE_WIDTH), QBrush(grad1));
|
||||
|
||||
grad1.setFinalStop(1, 0);
|
||||
painter->fillRect(QRectF(0, 0, BOX_LINE_WIDTH, height), QBrush(grad1));
|
||||
|
||||
grad1.setStart(0, 1);
|
||||
grad1.setFinalStop(0, 0);
|
||||
painter->fillRect(QRectF(0, height - BOX_LINE_WIDTH, width, BOX_LINE_WIDTH), QBrush(grad1));
|
||||
|
||||
grad1.setStart(1, 0);
|
||||
painter->fillRect(QRectF(width - BOX_LINE_WIDTH, 0, BOX_LINE_WIDTH, height), QBrush(grad1));
|
||||
}
|
||||
|
||||
/**
|
||||
Render a division line for land placement
|
||||
|
||||
@painter QPainter object
|
||||
*/
|
||||
void TableZone::paintLandDivider(QPainter *painter)
|
||||
{
|
||||
// Place the line 2 grid heights down then back it off just enough to allow
|
||||
// some space between a 3-card stack and the land area.
|
||||
qreal separatorY = MARGIN_TOP + 2 * (CARD_HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2;
|
||||
if (isInverted())
|
||||
separatorY = height - separatorY;
|
||||
painter->setPen(QColor(255, 255, 255, 40));
|
||||
painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
|
||||
}
|
||||
|
||||
void TableZone::addCardImpl(CardItem *card, int _x, int _y)
|
||||
{
|
||||
cards.append(card);
|
||||
card->setGridPoint(QPoint(_x, _y));
|
||||
|
||||
card->setParentItem(this);
|
||||
card->setVisible(true);
|
||||
card->update();
|
||||
}
|
||||
|
||||
void TableZone::handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint)
|
||||
{
|
||||
handleDropEventByGrid(dragItems, startZone, mapToGrid(dropPoint));
|
||||
}
|
||||
|
||||
void TableZone::handleDropEventByGrid(const QList<CardDragItem *> &dragItems,
|
||||
CardZone *startZone,
|
||||
const QPoint &gridPoint)
|
||||
{
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(player->getId());
|
||||
cmd.set_target_zone(getName().toStdString());
|
||||
cmd.set_x(gridPoint.x());
|
||||
cmd.set_y(gridPoint.y());
|
||||
|
||||
for (const auto &item : dragItems) {
|
||||
CardToMove *ctm = cmd.mutable_cards_to_move()->add_card();
|
||||
ctm->set_card_id(item->getId());
|
||||
ctm->set_face_down(item->getFaceDown());
|
||||
if (startZone->getName() != name && !item->getFaceDown()) {
|
||||
const auto &info = item->getItem()->getInfo();
|
||||
if (info) {
|
||||
ctm->set_pt(info->getPowTough().toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startZone->getPlayer()->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void TableZone::reorganizeCards()
|
||||
{
|
||||
QSet<ArrowItem *> arrowsToUpdate;
|
||||
|
||||
// Calculate card stack widths so mapping functions work properly
|
||||
computeCardStackWidths();
|
||||
|
||||
for (int i = 0; i < cards.size(); ++i) {
|
||||
QPoint gridPoint = cards[i]->getGridPos();
|
||||
if (gridPoint.x() == -1)
|
||||
continue;
|
||||
|
||||
QPointF mapPoint = mapFromGrid(gridPoint);
|
||||
qreal x = mapPoint.x();
|
||||
qreal y = mapPoint.y();
|
||||
|
||||
int numberAttachedCards = cards[i]->getAttachedCards().size();
|
||||
qreal actualX = x + numberAttachedCards * STACKED_CARD_OFFSET_X;
|
||||
qreal actualY = y;
|
||||
if (numberAttachedCards)
|
||||
actualY += 15;
|
||||
|
||||
cards[i]->setPos(actualX, actualY);
|
||||
cards[i]->setRealZValue((actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100);
|
||||
|
||||
QListIterator<CardItem *> attachedCardIterator(cards[i]->getAttachedCards());
|
||||
int j = 0;
|
||||
while (attachedCardIterator.hasNext()) {
|
||||
++j;
|
||||
CardItem *attachedCard = attachedCardIterator.next();
|
||||
qreal childX = actualX - j * STACKED_CARD_OFFSET_X;
|
||||
qreal childY = y + 5;
|
||||
attachedCard->setPos(childX, childY);
|
||||
attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100);
|
||||
for (ArrowItem *item : attachedCard->getArrowsFrom()) {
|
||||
arrowsToUpdate.insert(item);
|
||||
}
|
||||
for (ArrowItem *item : attachedCard->getArrowsTo()) {
|
||||
arrowsToUpdate.insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
for (ArrowItem *item : cards[i]->getArrowsFrom()) {
|
||||
arrowsToUpdate.insert(item);
|
||||
}
|
||||
for (ArrowItem *item : cards[i]->getArrowsTo()) {
|
||||
arrowsToUpdate.insert(item);
|
||||
}
|
||||
}
|
||||
for (ArrowItem *item : arrowsToUpdate) {
|
||||
item->updatePath();
|
||||
}
|
||||
|
||||
resizeToContents();
|
||||
update();
|
||||
}
|
||||
|
||||
void TableZone::toggleTapped()
|
||||
{
|
||||
QList<QGraphicsItem *> selectedItems = scene()->selectedItems();
|
||||
bool tapAll = false;
|
||||
for (int i = 0; i < selectedItems.size(); i++)
|
||||
if (!qgraphicsitem_cast<CardItem *>(selectedItems[i])->getTapped()) {
|
||||
tapAll = true;
|
||||
break;
|
||||
}
|
||||
QList<const ::google::protobuf::Message *> cmdList;
|
||||
for (int i = 0; i < selectedItems.size(); i++) {
|
||||
CardItem *temp = qgraphicsitem_cast<CardItem *>(selectedItems[i]);
|
||||
if (temp->getTapped() != tapAll) {
|
||||
Command_SetCardAttr *cmd = new Command_SetCardAttr;
|
||||
cmd->set_zone(name.toStdString());
|
||||
cmd->set_card_id(temp->getId());
|
||||
cmd->set_attribute(AttrTapped);
|
||||
cmd->set_attr_value(tapAll ? "1" : "0");
|
||||
cmdList.append(cmd);
|
||||
}
|
||||
}
|
||||
player->sendGameCommand(player->prepareGameCommand(cmdList));
|
||||
}
|
||||
|
||||
CardItem *TableZone::takeCard(int position, int cardId, bool canResize)
|
||||
{
|
||||
CardItem *result = CardZone::takeCard(position, cardId);
|
||||
if (canResize)
|
||||
resizeToContents();
|
||||
return result;
|
||||
}
|
||||
|
||||
void TableZone::resizeToContents()
|
||||
{
|
||||
int xMax = 0;
|
||||
|
||||
// Find rightmost card position, which includes the left margin amount.
|
||||
for (int i = 0; i < cards.size(); ++i)
|
||||
if (cards[i]->pos().x() > xMax)
|
||||
xMax = (int)cards[i]->pos().x();
|
||||
|
||||
// Minimum width is the rightmost card position plus enough room for
|
||||
// another card with padding, then margin.
|
||||
currentMinimumWidth = xMax + (2 * CARD_WIDTH) + PADDING_X + MARGIN_RIGHT;
|
||||
|
||||
if (currentMinimumWidth < MIN_WIDTH)
|
||||
currentMinimumWidth = MIN_WIDTH;
|
||||
|
||||
if (currentMinimumWidth != width) {
|
||||
prepareGeometryChange();
|
||||
width = currentMinimumWidth;
|
||||
emit sizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
CardItem *TableZone::getCardFromGrid(const QPoint &gridPoint) const
|
||||
{
|
||||
for (int i = 0; i < cards.size(); i++)
|
||||
if (cards.at(i)->getGridPoint() == gridPoint)
|
||||
return cards.at(i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CardItem *TableZone::getCardFromCoords(const QPointF &point) const
|
||||
{
|
||||
QPoint gridPoint = mapToGrid(point);
|
||||
return getCardFromGrid(gridPoint);
|
||||
}
|
||||
|
||||
void TableZone::computeCardStackWidths()
|
||||
{
|
||||
// Each card stack is three grid points worth of card locations.
|
||||
// First pass: compute the number of cards at each card stack.
|
||||
QMap<int, int> cardStackCount;
|
||||
for (int i = 0; i < cards.size(); ++i) {
|
||||
const QPoint &gridPoint = cards[i]->getGridPos();
|
||||
if (gridPoint.x() == -1)
|
||||
continue;
|
||||
|
||||
const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y());
|
||||
cardStackCount.insert(key, cardStackCount.value(key, 0) + 1);
|
||||
}
|
||||
|
||||
// Second pass: compute the width at each card stack.
|
||||
cardStackWidth.clear();
|
||||
for (int i = 0; i < cards.size(); ++i) {
|
||||
const QPoint &gridPoint = cards[i]->getGridPos();
|
||||
if (gridPoint.x() == -1)
|
||||
continue;
|
||||
|
||||
const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y());
|
||||
const int stackCount = cardStackCount.value(key, 0);
|
||||
if (stackCount == 1)
|
||||
cardStackWidth.insert(key, CARD_WIDTH + cards[i]->getAttachedCards().size() * STACKED_CARD_OFFSET_X);
|
||||
else
|
||||
cardStackWidth.insert(key, CARD_WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X);
|
||||
}
|
||||
}
|
||||
|
||||
QPointF TableZone::mapFromGrid(QPoint gridPoint) const
|
||||
{
|
||||
qreal x, y;
|
||||
|
||||
// Start with margin plus stacked card offset
|
||||
x = MARGIN_LEFT + (gridPoint.x() % 3) * STACKED_CARD_OFFSET_X;
|
||||
|
||||
// Add in width of card stack plus padding for each column
|
||||
for (int i = 0; i < gridPoint.x() / 3; ++i) {
|
||||
const int key = getCardStackMapKey(i, gridPoint.y());
|
||||
x += cardStackWidth.value(key, CARD_WIDTH) + PADDING_X;
|
||||
}
|
||||
|
||||
if (isInverted())
|
||||
gridPoint.setY(TABLEROWS - 1 - gridPoint.y());
|
||||
|
||||
// Start with margin plus stacked card offset
|
||||
y = MARGIN_TOP + (gridPoint.x() % 3) * STACKED_CARD_OFFSET_Y;
|
||||
|
||||
// Add in card size and padding for each row
|
||||
for (int i = 0; i < gridPoint.y(); ++i)
|
||||
y += CARD_HEIGHT + PADDING_Y;
|
||||
|
||||
return QPointF(x, y);
|
||||
}
|
||||
|
||||
QPoint TableZone::mapToGrid(const QPointF &mapPoint) const
|
||||
{
|
||||
// Begin by calculating the y-coordinate of the grid space, which will be
|
||||
// used for the x-coordinate.
|
||||
|
||||
// Offset point by the margin amount to reference point within grid area.
|
||||
int y = mapPoint.y() - MARGIN_TOP;
|
||||
|
||||
// Below calculation effectively rounds to the nearest grid point.
|
||||
const int gridPointHeight = CARD_HEIGHT + PADDING_Y;
|
||||
int gridPointY = (y + PADDING_Y / 2) / gridPointHeight;
|
||||
|
||||
gridPointY = clampValidTableRow(gridPointY);
|
||||
|
||||
if (isInverted())
|
||||
gridPointY = TABLEROWS - 1 - gridPointY;
|
||||
|
||||
// Calculating the x-coordinate of the grid space requires adding up the
|
||||
// widths of each card stack along the row.
|
||||
|
||||
// Offset point by the margin amount to reference point within grid area.
|
||||
int x = mapPoint.x() - MARGIN_LEFT + PADDING_X / 2;
|
||||
|
||||
// Maximum value is a card width from the right margin, referenced to the
|
||||
// grid area.
|
||||
const int xMax = width - MARGIN_LEFT - MARGIN_RIGHT - CARD_WIDTH;
|
||||
|
||||
int xStack = 0;
|
||||
int xNextStack = 0;
|
||||
int nextStackCol = 0;
|
||||
while ((xNextStack <= x) && (xNextStack <= xMax)) {
|
||||
xStack = xNextStack;
|
||||
const int key = getCardStackMapKey(nextStackCol, gridPointY);
|
||||
xNextStack += cardStackWidth.value(key, CARD_WIDTH) + PADDING_X;
|
||||
nextStackCol++;
|
||||
}
|
||||
int stackCol = qMax(nextStackCol - 1, 0);
|
||||
|
||||
// Have the stack column, need to refine to the grid column. Take the
|
||||
// difference between the point and the stack point and divide by stacked
|
||||
// card offsets.
|
||||
int xDiff = x - xStack;
|
||||
int gridPointX = stackCol * 3 + qMin(xDiff / STACKED_CARD_OFFSET_X, 2);
|
||||
|
||||
return QPoint(gridPointX, gridPointY);
|
||||
}
|
||||
|
||||
QPointF TableZone::closestGridPoint(const QPointF &point)
|
||||
{
|
||||
QPoint gridPoint = mapToGrid(point);
|
||||
gridPoint.setX((gridPoint.x() / 3) * 3);
|
||||
if (getCardFromGrid(gridPoint))
|
||||
gridPoint.setX(gridPoint.x() + 1);
|
||||
if (getCardFromGrid(gridPoint))
|
||||
gridPoint.setX(gridPoint.x() + 1);
|
||||
return mapFromGrid(gridPoint);
|
||||
}
|
||||
|
||||
int TableZone::clampValidTableRow(const int row)
|
||||
{
|
||||
if (row < 0)
|
||||
return 0;
|
||||
if (row >= TABLEROWS)
|
||||
return TABLEROWS - 1;
|
||||
return row;
|
||||
}
|
||||
207
cockatrice/src/game/zones/table_zone.h
Normal file
207
cockatrice/src/game/zones/table_zone.h
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
#ifndef TABLEZONE_H
|
||||
#define TABLEZONE_H
|
||||
|
||||
#include "../cards/abstract_card_item.h"
|
||||
#include "select_zone.h"
|
||||
|
||||
/*
|
||||
* TableZone is the grid based rect where CardItems may be placed.
|
||||
* It is the main play zone and can be customized with background images.
|
||||
*
|
||||
* TODO: Refactor methods to make more readable, extract some logic to
|
||||
* private methods (Im looking at you TableZone::reorganizeCards())
|
||||
*/
|
||||
class TableZone : public SelectZone
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void sizeChanged();
|
||||
|
||||
private:
|
||||
static const int TABLEROWS = 3;
|
||||
|
||||
/*
|
||||
Margins between table edges and cards, paddings between cards
|
||||
*/
|
||||
static const int MARGIN_LEFT = 20;
|
||||
static const int MARGIN_RIGHT = 15;
|
||||
static const int MARGIN_TOP = 10;
|
||||
static const int MARGIN_BOTTOM = 30;
|
||||
static const int PADDING_X = 35;
|
||||
static const int PADDING_Y = 30;
|
||||
|
||||
/*
|
||||
Minimum width of the table zone including margins.
|
||||
*/
|
||||
static const int MIN_WIDTH = MARGIN_LEFT + (5 * CARD_WIDTH) + MARGIN_RIGHT;
|
||||
|
||||
/*
|
||||
Offset sizes when cards are stacked on each other in the grid
|
||||
*/
|
||||
static const int STACKED_CARD_OFFSET_X = CARD_WIDTH / 3;
|
||||
static const int STACKED_CARD_OFFSET_Y = PADDING_Y / 3;
|
||||
|
||||
/*
|
||||
Width of the box line drawn in the margin around the active player's area
|
||||
*/
|
||||
static const int BOX_LINE_WIDTH = 10;
|
||||
|
||||
/*
|
||||
Default inactive mask and border gradient
|
||||
*/
|
||||
static const QColor BACKGROUND_COLOR;
|
||||
static const QColor FADE_MASK;
|
||||
static const QColor GRADIENT_COLOR;
|
||||
static const QColor GRADIENT_COLORLESS;
|
||||
|
||||
/*
|
||||
Size and shape variables
|
||||
*/
|
||||
int width;
|
||||
int height;
|
||||
int currentMinimumWidth;
|
||||
|
||||
/*
|
||||
Internal cache for widths of stacks of cards by row and column.
|
||||
*/
|
||||
QMap<int, int> cardStackWidth;
|
||||
|
||||
/*
|
||||
Holds any custom background image for the TableZone
|
||||
*/
|
||||
QPixmap backgroundPixelMap;
|
||||
|
||||
/*
|
||||
If this TableZone is currently active
|
||||
*/
|
||||
bool active;
|
||||
|
||||
bool isInverted() const;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
Loads in any found custom background and updates
|
||||
*/
|
||||
void updateBg();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
Reorganizes CardItems in the TableZone
|
||||
*/
|
||||
void reorganizeCards();
|
||||
|
||||
public:
|
||||
/**
|
||||
Constructs TableZone.
|
||||
|
||||
@param _p the Player
|
||||
@param parent defaults to null
|
||||
*/
|
||||
TableZone(Player *_p, QGraphicsItem *parent = nullptr);
|
||||
|
||||
/**
|
||||
@return a QRectF of the TableZone bounding box.
|
||||
*/
|
||||
QRectF boundingRect() const;
|
||||
|
||||
/**
|
||||
Render the TableZone
|
||||
|
||||
@param painter
|
||||
@param option
|
||||
*/
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
|
||||
/**
|
||||
Toggles the selected items as tapped.
|
||||
*/
|
||||
void toggleTapped();
|
||||
|
||||
/**
|
||||
See HandleDropEventByGrid
|
||||
*/
|
||||
void handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint);
|
||||
|
||||
/**
|
||||
Handles the placement of cards
|
||||
*/
|
||||
void handleDropEventByGrid(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &gridPoint);
|
||||
|
||||
/**
|
||||
@return CardItem from grid location
|
||||
*/
|
||||
CardItem *getCardFromGrid(const QPoint &gridPoint) const;
|
||||
|
||||
/**
|
||||
@return CardItem from coordinate location
|
||||
*/
|
||||
CardItem *getCardFromCoords(const QPointF &point) const;
|
||||
|
||||
QPointF closestGridPoint(const QPointF &point);
|
||||
|
||||
static int clampValidTableRow(const int row);
|
||||
|
||||
/**
|
||||
Removes a card from view.
|
||||
|
||||
@param position card position
|
||||
@param cardId id of card to take
|
||||
@param canResize defaults to true
|
||||
@return CardItem that has been removed
|
||||
*/
|
||||
CardItem *takeCard(int position, int cardId, bool canResize = true);
|
||||
|
||||
/**
|
||||
Resizes the TableZone in case CardItems are within or
|
||||
outside of the TableZone constraints.
|
||||
*/
|
||||
void resizeToContents();
|
||||
|
||||
int getMinimumWidth() const
|
||||
{
|
||||
return currentMinimumWidth;
|
||||
}
|
||||
void setWidth(qreal _width)
|
||||
{
|
||||
prepareGeometryChange();
|
||||
width = _width;
|
||||
}
|
||||
qreal getWidth() const
|
||||
{
|
||||
return width;
|
||||
}
|
||||
void setActive(bool _active)
|
||||
{
|
||||
active = _active;
|
||||
update();
|
||||
}
|
||||
|
||||
protected:
|
||||
void addCardImpl(CardItem *card, int x, int y);
|
||||
|
||||
private:
|
||||
void paintZoneOutline(QPainter *painter);
|
||||
void paintLandDivider(QPainter *painter);
|
||||
|
||||
/*
|
||||
Calculates card stack widths so mapping functions work properly
|
||||
*/
|
||||
void computeCardStackWidths();
|
||||
|
||||
/*
|
||||
Mapping functions for points to/from gridpoints.
|
||||
*/
|
||||
QPointF mapFromGrid(QPoint gridPoint) const;
|
||||
QPoint mapToGrid(const QPointF &mapPoint) const;
|
||||
|
||||
/*
|
||||
Helper function to create a single key from a card stack location.
|
||||
*/
|
||||
int getCardStackMapKey(int x, int y) const
|
||||
{
|
||||
return x + (y * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
259
cockatrice/src/game/zones/view_zone.cpp
Normal file
259
cockatrice/src/game/zones/view_zone.cpp
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
#include "view_zone.h"
|
||||
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../cards/card_database.h"
|
||||
#include "../cards/card_drag_item.h"
|
||||
#include "../cards/card_item.h"
|
||||
#include "../player/player.h"
|
||||
#include "pb/command_dump_zone.pb.h"
|
||||
#include "pb/command_move_card.pb.h"
|
||||
#include "pb/response_dump_zone.pb.h"
|
||||
#include "pb/serverinfo_card.pb.h"
|
||||
|
||||
#include <QBrush>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsSceneWheelEvent>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
|
||||
ZoneViewZone::ZoneViewZone(Player *_p,
|
||||
CardZone *_origZone,
|
||||
int _numberCards,
|
||||
bool _revealZone,
|
||||
bool _writeableRevealZone,
|
||||
QGraphicsItem *parent)
|
||||
: SelectZone(_p, _origZone->getName(), false, false, true, parent, true), bRect(QRectF()), minRows(0),
|
||||
numberCards(_numberCards), origZone(_origZone), revealZone(_revealZone),
|
||||
writeableRevealZone(_writeableRevealZone), sortByName(false), sortByType(false)
|
||||
{
|
||||
if (!(revealZone && !writeableRevealZone)) {
|
||||
origZone->getViews().append(this);
|
||||
}
|
||||
}
|
||||
|
||||
ZoneViewZone::~ZoneViewZone()
|
||||
{
|
||||
emit beingDeleted();
|
||||
qDebug("ZoneViewZone destructor");
|
||||
if (!(revealZone && !writeableRevealZone)) {
|
||||
origZone->getViews().removeOne(this);
|
||||
}
|
||||
}
|
||||
|
||||
QRectF ZoneViewZone::boundingRect() const
|
||||
{
|
||||
return bRect;
|
||||
}
|
||||
|
||||
void ZoneViewZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush windowBrush(QColor(240, 240, 240));
|
||||
windowBrush.setColor(windowBrush.color().darker(150));
|
||||
painter->fillRect(boundingRect(), windowBrush);
|
||||
}
|
||||
|
||||
void ZoneViewZone::initializeCards(const QList<const ServerInfo_Card *> &cardList)
|
||||
{
|
||||
if (!cardList.isEmpty()) {
|
||||
for (int i = 0; i < cardList.size(); ++i)
|
||||
addCard(
|
||||
new CardItem(player, QString::fromStdString(cardList[i]->name()), cardList[i]->id(), revealZone, this),
|
||||
false, i);
|
||||
reorganizeCards();
|
||||
} else if (!origZone->contentsKnown()) {
|
||||
Command_DumpZone cmd;
|
||||
cmd.set_player_id(player->getId());
|
||||
cmd.set_zone_name(name.toStdString());
|
||||
cmd.set_number_cards(numberCards);
|
||||
|
||||
PendingCommand *pend = player->prepareGameCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(zoneDumpReceived(const Response &)));
|
||||
player->sendGameCommand(pend);
|
||||
} else {
|
||||
const CardList &c = origZone->getCards();
|
||||
int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size());
|
||||
for (int i = 0; i < number; i++) {
|
||||
CardItem *card = c.at(i);
|
||||
addCard(new CardItem(player, card->getName(), card->getId(), revealZone, this), false, i);
|
||||
}
|
||||
reorganizeCards();
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneViewZone::zoneDumpReceived(const Response &r)
|
||||
{
|
||||
const Response_DumpZone &resp = r.GetExtension(Response_DumpZone::ext);
|
||||
const int respCardListSize = resp.zone_info().card_list_size();
|
||||
for (int i = 0; i < respCardListSize; ++i) {
|
||||
const ServerInfo_Card &cardInfo = resp.zone_info().card_list(i);
|
||||
auto cardName = QString::fromStdString(cardInfo.name());
|
||||
auto *card = new CardItem(player, cardName, cardInfo.id(), revealZone, this, this);
|
||||
cards.insert(i, card);
|
||||
}
|
||||
reorganizeCards();
|
||||
emit cardCountChanged();
|
||||
}
|
||||
|
||||
// Because of boundingRect(), this function must not be called before the zone was added to a scene.
|
||||
void ZoneViewZone::reorganizeCards()
|
||||
{
|
||||
int cardCount = cards.size();
|
||||
if (!origZone->contentsKnown())
|
||||
for (int i = 0; i < cardCount; ++i)
|
||||
cards[i]->setId(i);
|
||||
|
||||
int cols = qFloor(qSqrt((double)cardCount / 2));
|
||||
if (cols > 7)
|
||||
cols = 7;
|
||||
int rows = qCeil((double)cardCount / cols);
|
||||
if (rows < 1)
|
||||
rows = 1;
|
||||
if (minRows == 0)
|
||||
minRows = rows;
|
||||
else if (rows < minRows) {
|
||||
rows = minRows;
|
||||
cols = qCeil((double)cardCount / minRows);
|
||||
}
|
||||
if (cols < 2)
|
||||
cols = 2;
|
||||
|
||||
qDebug() << "reorganizeCards: rows=" << rows << "cols=" << cols;
|
||||
|
||||
CardList cardsToDisplay(cards);
|
||||
if (sortByName || sortByType)
|
||||
cardsToDisplay.sort((sortByName ? CardList::SortByName : 0) | (sortByType ? CardList::SortByType : 0));
|
||||
|
||||
int typeColumn = 0;
|
||||
int longestRow = 0;
|
||||
if (pileView && sortByType) { // we need sort by type enabled for the feature to work
|
||||
int typeRow = 0;
|
||||
QString lastCardType;
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *c = cardsToDisplay.at(i);
|
||||
QString cardType = c->getInfo() ? c->getInfo()->getMainCardType() : "";
|
||||
|
||||
if (i) { // if not the first card
|
||||
if (cardType == lastCardType)
|
||||
typeRow++; // add below current card
|
||||
else { // if no match then move card to next column
|
||||
typeColumn++;
|
||||
typeRow = 0;
|
||||
}
|
||||
}
|
||||
|
||||
lastCardType = cardType;
|
||||
qreal x = 7 + (typeColumn * CARD_WIDTH);
|
||||
qreal y = typeRow * CARD_HEIGHT / 3;
|
||||
c->setPos(x + 5, y + 5);
|
||||
c->setRealZValue(i);
|
||||
longestRow = qMax(typeRow, longestRow);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *c = cardsToDisplay.at(i);
|
||||
qreal x = 7 + ((i / rows) * CARD_WIDTH);
|
||||
qreal y = (i % rows) * CARD_HEIGHT / 3;
|
||||
c->setPos(x + 5, y + 5);
|
||||
c->setRealZValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
qreal aleft = 0;
|
||||
qreal atop = 0;
|
||||
qreal awidth = (pileView && sortByType) ? qMax(typeColumn + 1, 3) * CARD_WIDTH + (CARD_WIDTH / 2)
|
||||
: qMax(cols, 1) * CARD_WIDTH + (CARD_WIDTH / 2);
|
||||
qreal aheight = (pileView && sortByType) ? (longestRow * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3
|
||||
: (rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3;
|
||||
optimumRect = QRectF(aleft, atop, awidth, aheight);
|
||||
|
||||
updateGeometry();
|
||||
emit optimumRectChanged();
|
||||
}
|
||||
|
||||
void ZoneViewZone::setSortByName(int _sortByName)
|
||||
{
|
||||
sortByName = _sortByName;
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void ZoneViewZone::setSortByType(int _sortByType)
|
||||
{
|
||||
sortByType = _sortByType;
|
||||
if (!sortByType)
|
||||
pileView = false;
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void ZoneViewZone::setPileView(int _pileView)
|
||||
{
|
||||
pileView = _pileView;
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void ZoneViewZone::addCardImpl(CardItem *card, int x, int /*y*/)
|
||||
{
|
||||
// if x is negative set it to add at end
|
||||
if (x < 0 || x >= cards.size()) {
|
||||
x = cards.size();
|
||||
}
|
||||
cards.insert(x, card);
|
||||
card->setParentItem(this);
|
||||
card->update();
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void ZoneViewZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||
CardZone *startZone,
|
||||
const QPoint & /*dropPoint*/)
|
||||
{
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(player->getId());
|
||||
cmd.set_target_zone(getName().toStdString());
|
||||
cmd.set_x(0);
|
||||
cmd.set_y(0);
|
||||
|
||||
for (int i = 0; i < dragItems.size(); ++i)
|
||||
cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId());
|
||||
|
||||
player->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void ZoneViewZone::removeCard(int position)
|
||||
{
|
||||
if (position >= cards.size())
|
||||
return;
|
||||
|
||||
CardItem *card = cards.takeAt(position);
|
||||
card->deleteLater();
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void ZoneViewZone::setGeometry(const QRectF &rect)
|
||||
{
|
||||
prepareGeometryChange();
|
||||
setPos(rect.x(), rect.y());
|
||||
bRect = QRectF(0, 0, rect.width(), rect.height());
|
||||
}
|
||||
|
||||
QSizeF ZoneViewZone::sizeHint(Qt::SizeHint /*which*/, const QSizeF & /*constraint*/) const
|
||||
{
|
||||
return optimumRect.size();
|
||||
}
|
||||
|
||||
void ZoneViewZone::setWriteableRevealZone(bool _writeableRevealZone)
|
||||
{
|
||||
if (writeableRevealZone && !_writeableRevealZone) {
|
||||
origZone->getViews().append(this);
|
||||
} else if (!writeableRevealZone && _writeableRevealZone) {
|
||||
origZone->getViews().removeOne(this);
|
||||
}
|
||||
writeableRevealZone = _writeableRevealZone;
|
||||
}
|
||||
|
||||
void ZoneViewZone::wheelEvent(QGraphicsSceneWheelEvent *event)
|
||||
{
|
||||
emit wheelEventReceived(event);
|
||||
}
|
||||
74
cockatrice/src/game/zones/view_zone.h
Normal file
74
cockatrice/src/game/zones/view_zone.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#ifndef ZONEVIEWERZONE_H
|
||||
#define ZONEVIEWERZONE_H
|
||||
|
||||
#include "select_zone.h"
|
||||
|
||||
#include <QGraphicsLayoutItem>
|
||||
|
||||
class ZoneViewWidget;
|
||||
class Response;
|
||||
class ServerInfo_Card;
|
||||
class QGraphicsSceneWheelEvent;
|
||||
|
||||
class ZoneViewZone : public SelectZone, public QGraphicsLayoutItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsLayoutItem)
|
||||
private:
|
||||
QRectF bRect, optimumRect;
|
||||
int minRows, numberCards;
|
||||
void handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint);
|
||||
CardZone *origZone;
|
||||
bool revealZone, writeableRevealZone;
|
||||
bool sortByName, sortByType;
|
||||
bool pileView;
|
||||
|
||||
public:
|
||||
ZoneViewZone(Player *_p,
|
||||
CardZone *_origZone,
|
||||
int _numberCards = -1,
|
||||
bool _revealZone = false,
|
||||
bool _writeableRevealZone = false,
|
||||
QGraphicsItem *parent = nullptr);
|
||||
~ZoneViewZone();
|
||||
QRectF boundingRect() const;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
|
||||
void reorganizeCards();
|
||||
void initializeCards(const QList<const ServerInfo_Card *> &cardList = QList<const ServerInfo_Card *>());
|
||||
void removeCard(int position);
|
||||
int getNumberCards() const
|
||||
{
|
||||
return numberCards;
|
||||
}
|
||||
void setGeometry(const QRectF &rect);
|
||||
QRectF getOptimumRect() const
|
||||
{
|
||||
return optimumRect;
|
||||
}
|
||||
bool getRevealZone() const
|
||||
{
|
||||
return revealZone;
|
||||
}
|
||||
bool getWriteableRevealZone() const
|
||||
{
|
||||
return writeableRevealZone;
|
||||
}
|
||||
void setWriteableRevealZone(bool _writeableRevealZone);
|
||||
public slots:
|
||||
void setSortByName(int _sortByName);
|
||||
void setSortByType(int _sortByType);
|
||||
void setPileView(int _pileView);
|
||||
private slots:
|
||||
void zoneDumpReceived(const Response &r);
|
||||
signals:
|
||||
void beingDeleted();
|
||||
void optimumRectChanged();
|
||||
void wheelEventReceived(QGraphicsSceneWheelEvent *event);
|
||||
|
||||
protected:
|
||||
void addCardImpl(CardItem *card, int x, int y);
|
||||
QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const;
|
||||
void wheelEvent(QGraphicsSceneWheelEvent *event);
|
||||
};
|
||||
|
||||
#endif
|
||||
220
cockatrice/src/game/zones/view_zone_widget.cpp
Normal file
220
cockatrice/src/game/zones/view_zone_widget.cpp
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
#include "view_zone_widget.h"
|
||||
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../cards/card_item.h"
|
||||
#include "../game_scene.h"
|
||||
#include "../player/player.h"
|
||||
#include "pb/command_shuffle.pb.h"
|
||||
#include "view_zone.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGraphicsLinearLayout>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QStyleOption>
|
||||
#include <QStyleOptionTitleBar>
|
||||
|
||||
ZoneViewWidget::ZoneViewWidget(Player *_player,
|
||||
CardZone *_origZone,
|
||||
int numberCards,
|
||||
bool _revealZone,
|
||||
bool _writeableRevealZone,
|
||||
const QList<const ServerInfo_Card *> &cardList)
|
||||
: QGraphicsWidget(0, Qt::Window), canBeShuffled(_origZone->getIsShufflable()), player(_player)
|
||||
{
|
||||
setAcceptHoverEvents(true);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setZValue(2000000006);
|
||||
setFlag(ItemIgnoresTransformations);
|
||||
|
||||
QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical);
|
||||
QGraphicsLinearLayout *hPilebox = 0;
|
||||
|
||||
if (numberCards < 0) {
|
||||
hPilebox = new QGraphicsLinearLayout(Qt::Horizontal);
|
||||
QGraphicsLinearLayout *hFilterbox = new QGraphicsLinearLayout(Qt::Horizontal);
|
||||
|
||||
QGraphicsProxyWidget *sortByNameProxy = new QGraphicsProxyWidget;
|
||||
sortByNameProxy->setWidget(&sortByNameCheckBox);
|
||||
hFilterbox->addItem(sortByNameProxy);
|
||||
|
||||
QGraphicsProxyWidget *sortByTypeProxy = new QGraphicsProxyWidget;
|
||||
sortByTypeProxy->setWidget(&sortByTypeCheckBox);
|
||||
hFilterbox->addItem(sortByTypeProxy);
|
||||
|
||||
vbox->addItem(hFilterbox);
|
||||
|
||||
QGraphicsProxyWidget *lineProxy = new QGraphicsProxyWidget;
|
||||
QFrame *line = new QFrame;
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
lineProxy->setWidget(line);
|
||||
vbox->addItem(lineProxy);
|
||||
|
||||
QGraphicsProxyWidget *pileViewProxy = new QGraphicsProxyWidget;
|
||||
pileViewProxy->setWidget(&pileViewCheckBox);
|
||||
hPilebox->addItem(pileViewProxy);
|
||||
}
|
||||
|
||||
if (_origZone->getIsShufflable() && (numberCards == -1)) {
|
||||
shuffleCheckBox.setChecked(true);
|
||||
QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget;
|
||||
shuffleProxy->setWidget(&shuffleCheckBox);
|
||||
hPilebox->addItem(shuffleProxy);
|
||||
}
|
||||
|
||||
vbox->addItem(hPilebox);
|
||||
|
||||
extraHeight = vbox->sizeHint(Qt::PreferredSize).height();
|
||||
resize(150, 150);
|
||||
|
||||
QGraphicsLinearLayout *zoneHBox = new QGraphicsLinearLayout(Qt::Horizontal);
|
||||
|
||||
zoneContainer = new QGraphicsWidget(this);
|
||||
zoneContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
zoneContainer->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
|
||||
zoneHBox->addItem(zoneContainer);
|
||||
|
||||
scrollBar = new QScrollBar(Qt::Vertical);
|
||||
scrollBar->setMinimum(0);
|
||||
scrollBar->setSingleStep(20);
|
||||
scrollBar->setPageStep(200);
|
||||
connect(scrollBar, SIGNAL(valueChanged(int)), this, SLOT(handleScrollBarChange(int)));
|
||||
scrollBarProxy = new ScrollableGraphicsProxyWidget;
|
||||
scrollBarProxy->setWidget(scrollBar);
|
||||
zoneHBox->addItem(scrollBarProxy);
|
||||
|
||||
vbox->addItem(zoneHBox);
|
||||
|
||||
zone = new ZoneViewZone(player, _origZone, numberCards, _revealZone, _writeableRevealZone, zoneContainer);
|
||||
connect(zone, SIGNAL(wheelEventReceived(QGraphicsSceneWheelEvent *)), scrollBarProxy,
|
||||
SLOT(recieveWheelEvent(QGraphicsSceneWheelEvent *)));
|
||||
|
||||
// numberCard is the num of cards we want to reveal from an area. Ex: scry the top 3 cards.
|
||||
// If the number is < 0 then it means that we can make the area sorted and we dont care about the order.
|
||||
if (numberCards < 0) {
|
||||
connect(&sortByNameCheckBox, SIGNAL(stateChanged(int)), this, SLOT(processSortByName(int)));
|
||||
connect(&sortByTypeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(processSortByType(int)));
|
||||
connect(&pileViewCheckBox, SIGNAL(stateChanged(int)), this, SLOT(processSetPileView(int)));
|
||||
sortByNameCheckBox.setChecked(SettingsCache::instance().getZoneViewSortByName());
|
||||
sortByTypeCheckBox.setChecked(SettingsCache::instance().getZoneViewSortByType());
|
||||
pileViewCheckBox.setChecked(SettingsCache::instance().getZoneViewPileView());
|
||||
if (!SettingsCache::instance().getZoneViewSortByType())
|
||||
pileViewCheckBox.setEnabled(false);
|
||||
}
|
||||
|
||||
retranslateUi();
|
||||
setLayout(vbox);
|
||||
|
||||
connect(zone, SIGNAL(optimumRectChanged()), this, SLOT(resizeToZoneContents()));
|
||||
connect(zone, SIGNAL(beingDeleted()), this, SLOT(zoneDeleted()));
|
||||
zone->initializeCards(cardList);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::processSortByType(int value)
|
||||
{
|
||||
pileViewCheckBox.setEnabled(value);
|
||||
SettingsCache::instance().setZoneViewSortByType(value);
|
||||
zone->setPileView(pileViewCheckBox.isChecked());
|
||||
zone->setSortByType(value);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::processSortByName(int value)
|
||||
{
|
||||
SettingsCache::instance().setZoneViewSortByName(value);
|
||||
zone->setSortByName(value);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::processSetPileView(int value)
|
||||
{
|
||||
SettingsCache::instance().setZoneViewPileView(value);
|
||||
zone->setPileView(value);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(zone->getTranslatedName(false, CaseNominative));
|
||||
sortByNameCheckBox.setText(tr("sort by name"));
|
||||
sortByTypeCheckBox.setText(tr("sort by type"));
|
||||
shuffleCheckBox.setText(tr("shuffle when closing"));
|
||||
pileViewCheckBox.setText(tr("pile view"));
|
||||
}
|
||||
|
||||
void ZoneViewWidget::moveEvent(QGraphicsSceneMoveEvent * /* event */)
|
||||
{
|
||||
if (!scene())
|
||||
return;
|
||||
|
||||
int titleBarHeight = 24;
|
||||
|
||||
QPointF scenePos = pos();
|
||||
|
||||
if (scenePos.x() < 0) {
|
||||
scenePos.setX(0);
|
||||
} else {
|
||||
qreal maxw = scene()->sceneRect().width() - 100;
|
||||
if (scenePos.x() > maxw)
|
||||
scenePos.setX(maxw);
|
||||
}
|
||||
|
||||
if (scenePos.y() < titleBarHeight) {
|
||||
scenePos.setY(titleBarHeight);
|
||||
} else {
|
||||
qreal maxh = scene()->sceneRect().height() - titleBarHeight;
|
||||
if (scenePos.y() > maxh)
|
||||
scenePos.setY(maxh);
|
||||
}
|
||||
|
||||
if (scenePos != pos())
|
||||
setPos(scenePos);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::resizeToZoneContents()
|
||||
{
|
||||
QRectF zoneRect = zone->getOptimumRect();
|
||||
qreal totalZoneHeight = zoneRect.height();
|
||||
if (zoneRect.height() > 500)
|
||||
zoneRect.setHeight(500);
|
||||
QSizeF newSize(qMax(QGraphicsWidget::layout()->effectiveSizeHint(Qt::MinimumSize, QSizeF()).width(),
|
||||
zoneRect.width() + scrollBar->width() + 10),
|
||||
zoneRect.height() + extraHeight + 10);
|
||||
setMaximumSize(newSize);
|
||||
resize(newSize);
|
||||
|
||||
zone->setGeometry(QRectF(0, -scrollBar->value(), zoneContainer->size().width(), totalZoneHeight));
|
||||
scrollBar->setMaximum(totalZoneHeight - zoneRect.height());
|
||||
|
||||
if (layout())
|
||||
layout()->invalidate();
|
||||
}
|
||||
|
||||
void ZoneViewWidget::handleScrollBarChange(int value)
|
||||
{
|
||||
zone->setY(-value);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
disconnect(zone, SIGNAL(beingDeleted()), this, 0);
|
||||
if (shuffleCheckBox.isChecked())
|
||||
player->sendGameCommand(Command_Shuffle());
|
||||
emit closePressed(this);
|
||||
deleteLater();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void ZoneViewWidget::zoneDeleted()
|
||||
{
|
||||
emit closePressed(this);
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void ZoneViewWidget::initStyleOption(QStyleOption *option) const
|
||||
{
|
||||
QStyleOptionTitleBar *titleBar = qstyleoption_cast<QStyleOptionTitleBar *>(option);
|
||||
if (titleBar)
|
||||
titleBar->icon = QPixmap("theme:cockatrice");
|
||||
}
|
||||
83
cockatrice/src/game/zones/view_zone_widget.h
Normal file
83
cockatrice/src/game/zones/view_zone_widget.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#ifndef ZONEVIEWWIDGET_H
|
||||
#define ZONEVIEWWIDGET_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QGraphicsWidget>
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class CardZone;
|
||||
class ZoneViewZone;
|
||||
class Player;
|
||||
class CardDatabase;
|
||||
class QScrollBar;
|
||||
class QCheckBox;
|
||||
class GameScene;
|
||||
class ServerInfo_Card;
|
||||
class QGraphicsSceneMouseEvent;
|
||||
class QGraphicsSceneWheelEvent;
|
||||
class QStyleOption;
|
||||
|
||||
class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
void recieveWheelEvent(QGraphicsSceneWheelEvent *event)
|
||||
{
|
||||
wheelEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
class ZoneViewWidget : public QGraphicsWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
ZoneViewZone *zone;
|
||||
QGraphicsWidget *zoneContainer;
|
||||
|
||||
QPushButton *closeButton;
|
||||
QScrollBar *scrollBar;
|
||||
ScrollableGraphicsProxyWidget *scrollBarProxy;
|
||||
QCheckBox sortByNameCheckBox;
|
||||
QCheckBox sortByTypeCheckBox;
|
||||
QCheckBox shuffleCheckBox;
|
||||
QCheckBox pileViewCheckBox;
|
||||
|
||||
bool canBeShuffled;
|
||||
int extraHeight;
|
||||
Player *player;
|
||||
signals:
|
||||
void closePressed(ZoneViewWidget *zv);
|
||||
private slots:
|
||||
void processSortByType(int value);
|
||||
void processSortByName(int value);
|
||||
void processSetPileView(int value);
|
||||
void resizeToZoneContents();
|
||||
void handleScrollBarChange(int value);
|
||||
void zoneDeleted();
|
||||
void moveEvent(QGraphicsSceneMoveEvent * /* event */);
|
||||
|
||||
public:
|
||||
ZoneViewWidget(Player *_player,
|
||||
CardZone *_origZone,
|
||||
int numberCards = 0,
|
||||
bool _revealZone = false,
|
||||
bool _writeableRevealZone = false,
|
||||
const QList<const ServerInfo_Card *> &cardList = QList<const ServerInfo_Card *>());
|
||||
ZoneViewZone *getZone() const
|
||||
{
|
||||
return zone;
|
||||
}
|
||||
Player *getPlayer() const
|
||||
{
|
||||
return player;
|
||||
}
|
||||
void retranslateUi();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event);
|
||||
void initStyleOption(QStyleOption *option) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue