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:
LunaticCat 2024-10-20 16:11:35 +02:00 committed by GitHub
parent d1e0f9dfc5
commit fa999880ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
261 changed files with 735 additions and 729 deletions

View 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;
}

View 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

View 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);
}

View 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

View 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);
}

View 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

View 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);
}

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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();
}

View 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

View 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);
}

View 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

View 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);
}

View 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

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

View 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

View 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);
}

View 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

View 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);
}

View 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

View 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);
}

View 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