mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-14 22:42:14 -07:00
Move deck list to its own folder ahead of refactor.
Took 39 minutes
This commit is contained in:
parent
da52d677c7
commit
7f157496d3
26 changed files with 26 additions and 26 deletions
994
common/deck_list/deck_list.cpp
Normal file
994
common/deck_list/deck_list.cpp
Normal file
|
|
@ -0,0 +1,994 @@
|
|||
#include "deck_list.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
#include <QTextStream>
|
||||
#include <algorithm>
|
||||
|
||||
#if QT_VERSION < 0x050600
|
||||
// qHash on QRegularExpression was added in 5.6, FIX IT
|
||||
uint qHash(const QRegularExpression &key, uint seed) noexcept
|
||||
{
|
||||
return qHash(key.pattern(), seed); // call qHash on pattern QString instead
|
||||
}
|
||||
#endif
|
||||
|
||||
SideboardPlan::SideboardPlan(const QString &_name, const QList<MoveCard_ToZone> &_moveList)
|
||||
: name(_name), moveList(_moveList)
|
||||
{
|
||||
}
|
||||
|
||||
void SideboardPlan::setMoveList(const QList<MoveCard_ToZone> &_moveList)
|
||||
{
|
||||
moveList = _moveList;
|
||||
}
|
||||
|
||||
bool SideboardPlan::readElement(QXmlStreamReader *xml)
|
||||
{
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
const QString childName = xml->name().toString();
|
||||
if (xml->isStartElement()) {
|
||||
if (childName == "name")
|
||||
name = xml->readElementText();
|
||||
else if (childName == "move_card_to_zone") {
|
||||
MoveCard_ToZone m;
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
const QString childName2 = xml->name().toString();
|
||||
if (xml->isStartElement()) {
|
||||
if (childName2 == "card_name")
|
||||
m.set_card_name(xml->readElementText().toStdString());
|
||||
else if (childName2 == "start_zone")
|
||||
m.set_start_zone(xml->readElementText().toStdString());
|
||||
else if (childName2 == "target_zone")
|
||||
m.set_target_zone(xml->readElementText().toStdString());
|
||||
} else if (xml->isEndElement() && (childName2 == "move_card_to_zone")) {
|
||||
moveList.append(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (xml->isEndElement() && (childName == "sideboard_plan"))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SideboardPlan::write(QXmlStreamWriter *xml)
|
||||
{
|
||||
xml->writeStartElement("sideboard_plan");
|
||||
xml->writeTextElement("name", name);
|
||||
for (auto &i : moveList) {
|
||||
xml->writeStartElement("move_card_to_zone");
|
||||
xml->writeTextElement("card_name", QString::fromStdString(i.card_name()));
|
||||
xml->writeTextElement("start_zone", QString::fromStdString(i.start_zone()));
|
||||
xml->writeTextElement("target_zone", QString::fromStdString(i.target_zone()));
|
||||
xml->writeEndElement();
|
||||
}
|
||||
xml->writeEndElement();
|
||||
}
|
||||
|
||||
AbstractDecklistNode::AbstractDecklistNode(InnerDecklistNode *_parent, int position)
|
||||
: parent(_parent), sortMethod(Default)
|
||||
{
|
||||
if (parent) {
|
||||
if (position == -1) {
|
||||
parent->append(this);
|
||||
} else {
|
||||
parent->insert(position, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int AbstractDecklistNode::depth() const
|
||||
{
|
||||
if (parent) {
|
||||
return parent->depth() + 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
InnerDecklistNode::InnerDecklistNode(InnerDecklistNode *other, InnerDecklistNode *_parent)
|
||||
: AbstractDecklistNode(_parent), name(other->getName())
|
||||
{
|
||||
for (int i = 0; i < other->size(); ++i) {
|
||||
auto *inner = dynamic_cast<InnerDecklistNode *>(other->at(i));
|
||||
if (inner) {
|
||||
new InnerDecklistNode(inner, this);
|
||||
} else {
|
||||
new DecklistCardNode(dynamic_cast<DecklistCardNode *>(other->at(i)), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InnerDecklistNode::~InnerDecklistNode()
|
||||
{
|
||||
clearTree();
|
||||
}
|
||||
|
||||
QString InnerDecklistNode::visibleNameFromName(const QString &_name)
|
||||
{
|
||||
if (_name == DECK_ZONE_MAIN) {
|
||||
return QObject::tr("Maindeck");
|
||||
} else if (_name == DECK_ZONE_SIDE) {
|
||||
return QObject::tr("Sideboard");
|
||||
} else if (_name == DECK_ZONE_TOKENS) {
|
||||
return QObject::tr("Tokens");
|
||||
} else {
|
||||
return _name;
|
||||
}
|
||||
}
|
||||
|
||||
void InnerDecklistNode::setSortMethod(DeckSortMethod method)
|
||||
{
|
||||
sortMethod = method;
|
||||
for (int i = 0; i < size(); i++) {
|
||||
at(i)->setSortMethod(method);
|
||||
}
|
||||
}
|
||||
|
||||
QString InnerDecklistNode::getVisibleName() const
|
||||
{
|
||||
return visibleNameFromName(name);
|
||||
}
|
||||
|
||||
void InnerDecklistNode::clearTree()
|
||||
{
|
||||
for (int i = 0; i < size(); i++)
|
||||
delete at(i);
|
||||
clear();
|
||||
}
|
||||
|
||||
DecklistCardNode::DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent)
|
||||
: AbstractDecklistCardNode(_parent), name(other->getName()), number(other->getNumber()),
|
||||
cardSetShortName(other->getCardSetShortName()), cardSetNumber(other->getCardCollectorNumber()),
|
||||
cardProviderId(other->getCardProviderId())
|
||||
{
|
||||
}
|
||||
|
||||
AbstractDecklistNode *InnerDecklistNode::findChild(const QString &_name)
|
||||
{
|
||||
for (int i = 0; i < size(); i++) {
|
||||
if (at(i)->getName() == _name) {
|
||||
return at(i);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AbstractDecklistNode *InnerDecklistNode::findCardChildByNameProviderIdAndNumber(const QString &_name,
|
||||
const QString &_providerId,
|
||||
const QString &_cardNumber)
|
||||
{
|
||||
for (const auto &i : *this) {
|
||||
if (!i || i->getName() != _name) {
|
||||
continue;
|
||||
}
|
||||
if (_cardNumber != "" && i->getCardCollectorNumber() != _cardNumber) {
|
||||
continue;
|
||||
}
|
||||
if (_providerId != "" && i->getCardProviderId() != _providerId) {
|
||||
continue;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int InnerDecklistNode::height() const
|
||||
{
|
||||
return at(0)->height() + 1;
|
||||
}
|
||||
|
||||
int InnerDecklistNode::recursiveCount(bool countTotalCards) const
|
||||
{
|
||||
int result = 0;
|
||||
for (int i = 0; i < size(); i++) {
|
||||
auto *node = dynamic_cast<InnerDecklistNode *>(at(i));
|
||||
|
||||
if (node) {
|
||||
result += node->recursiveCount(countTotalCards);
|
||||
} else if (countTotalCards) {
|
||||
result += dynamic_cast<AbstractDecklistCardNode *>(at(i))->getNumber();
|
||||
} else {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool InnerDecklistNode::compare(AbstractDecklistNode *other) const
|
||||
{
|
||||
switch (sortMethod) {
|
||||
case ByNumber:
|
||||
return compareNumber(other);
|
||||
case ByName:
|
||||
return compareName(other);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool InnerDecklistNode::compareNumber(AbstractDecklistNode *other) const
|
||||
{
|
||||
auto *other2 = dynamic_cast<InnerDecklistNode *>(other);
|
||||
if (other2) {
|
||||
int n1 = recursiveCount(true);
|
||||
int n2 = other2->recursiveCount(true);
|
||||
return (n1 != n2) ? (n1 > n2) : compareName(other);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool InnerDecklistNode::compareName(AbstractDecklistNode *other) const
|
||||
{
|
||||
auto *other2 = dynamic_cast<InnerDecklistNode *>(other);
|
||||
if (other2) {
|
||||
return (getName() > other2->getName());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractDecklistCardNode::compare(AbstractDecklistNode *other) const
|
||||
{
|
||||
switch (sortMethod) {
|
||||
case ByNumber:
|
||||
return compareNumber(other);
|
||||
case ByName:
|
||||
return compareName(other);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractDecklistCardNode::compareNumber(AbstractDecklistNode *other) const
|
||||
{
|
||||
auto *other2 = dynamic_cast<AbstractDecklistCardNode *>(other);
|
||||
if (other2) {
|
||||
int n1 = getNumber();
|
||||
int n2 = other2->getNumber();
|
||||
return (n1 != n2) ? (n1 > n2) : compareName(other);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractDecklistCardNode::compareName(AbstractDecklistNode *other) const
|
||||
{
|
||||
auto *other2 = dynamic_cast<AbstractDecklistCardNode *>(other);
|
||||
if (other2) {
|
||||
return (getName() > other2->getName());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool InnerDecklistNode::readElement(QXmlStreamReader *xml)
|
||||
{
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
const QString childName = xml->name().toString();
|
||||
if (xml->isStartElement()) {
|
||||
if (childName == "zone") {
|
||||
InnerDecklistNode *newZone = new InnerDecklistNode(xml->attributes().value("name").toString(), this);
|
||||
newZone->readElement(xml);
|
||||
} else if (childName == "card") {
|
||||
DecklistCardNode *newCard = new DecklistCardNode(
|
||||
xml->attributes().value("name").toString(), xml->attributes().value("number").toString().toInt(),
|
||||
this, -1, xml->attributes().value("setShortName").toString(),
|
||||
xml->attributes().value("collectorNumber").toString(), xml->attributes().value("uuid").toString());
|
||||
newCard->readElement(xml);
|
||||
}
|
||||
} else if (xml->isEndElement() && (childName == "zone"))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InnerDecklistNode::writeElement(QXmlStreamWriter *xml)
|
||||
{
|
||||
xml->writeStartElement("zone");
|
||||
xml->writeAttribute("name", name);
|
||||
for (int i = 0; i < size(); i++)
|
||||
at(i)->writeElement(xml);
|
||||
xml->writeEndElement(); // zone
|
||||
}
|
||||
|
||||
bool AbstractDecklistCardNode::readElement(QXmlStreamReader *xml)
|
||||
{
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
if (xml->isEndElement() && xml->name().toString() == "card")
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AbstractDecklistCardNode::writeElement(QXmlStreamWriter *xml)
|
||||
{
|
||||
xml->writeEmptyElement("card");
|
||||
xml->writeAttribute("number", QString::number(getNumber()));
|
||||
xml->writeAttribute("name", getName());
|
||||
|
||||
if (!getCardSetShortName().isEmpty()) {
|
||||
xml->writeAttribute("setShortName", getCardSetShortName());
|
||||
}
|
||||
if (!getCardCollectorNumber().isEmpty()) {
|
||||
xml->writeAttribute("collectorNumber", getCardCollectorNumber());
|
||||
}
|
||||
if (!getCardProviderId().isEmpty()) {
|
||||
xml->writeAttribute("uuid", getCardProviderId());
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QPair<int, int>> InnerDecklistNode::sort(Qt::SortOrder order)
|
||||
{
|
||||
QVector<QPair<int, int>> result(size());
|
||||
|
||||
// Initialize temporary list with contents of current list
|
||||
QVector<QPair<int, AbstractDecklistNode *>> tempList(size());
|
||||
for (int i = size() - 1; i >= 0; --i) {
|
||||
tempList[i].first = i;
|
||||
tempList[i].second = at(i);
|
||||
}
|
||||
|
||||
// Sort temporary list
|
||||
auto cmp = [order](const auto &a, const auto &b) {
|
||||
return (order == Qt::AscendingOrder) ? (b.second->compare(a.second)) : (a.second->compare(b.second));
|
||||
};
|
||||
|
||||
std::sort(tempList.begin(), tempList.end(), cmp);
|
||||
|
||||
// Map old indexes to new indexes and
|
||||
// copy temporary list to the current one
|
||||
for (int i = size() - 1; i >= 0; --i) {
|
||||
result[i].first = tempList[i].first;
|
||||
result[i].second = i;
|
||||
replace(i, tempList[i].second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DeckList::DeckList()
|
||||
{
|
||||
root = new InnerDecklistNode;
|
||||
}
|
||||
|
||||
// TODO: https://qt-project.org/doc/qt-4.8/qobject.html#no-copy-constructor-or-assignment-operator
|
||||
DeckList::DeckList(const DeckList &other)
|
||||
: QObject(), name(other.name), comments(other.comments), bannerCard(other.bannerCard),
|
||||
lastLoadedTimestamp(other.lastLoadedTimestamp), tags(other.tags), cachedDeckHash(other.cachedDeckHash)
|
||||
{
|
||||
root = new InnerDecklistNode(other.getRoot());
|
||||
|
||||
QMapIterator<QString, SideboardPlan *> spIterator(other.getSideboardPlans());
|
||||
while (spIterator.hasNext()) {
|
||||
spIterator.next();
|
||||
sideboardPlans.insert(spIterator.key(), new SideboardPlan(spIterator.key(), spIterator.value()->getMoveList()));
|
||||
}
|
||||
}
|
||||
|
||||
DeckList::DeckList(const QString &nativeString)
|
||||
{
|
||||
root = new InnerDecklistNode;
|
||||
loadFromString_Native(nativeString);
|
||||
}
|
||||
|
||||
DeckList::~DeckList()
|
||||
{
|
||||
delete root;
|
||||
|
||||
QMapIterator<QString, SideboardPlan *> i(sideboardPlans);
|
||||
while (i.hasNext())
|
||||
delete i.next().value();
|
||||
}
|
||||
|
||||
QList<MoveCard_ToZone> DeckList::getCurrentSideboardPlan()
|
||||
{
|
||||
SideboardPlan *current = sideboardPlans.value(QString(), 0);
|
||||
if (!current)
|
||||
return QList<MoveCard_ToZone>();
|
||||
else
|
||||
return current->getMoveList();
|
||||
}
|
||||
|
||||
void DeckList::setCurrentSideboardPlan(const QList<MoveCard_ToZone> &plan)
|
||||
{
|
||||
SideboardPlan *current = sideboardPlans.value(QString(), 0);
|
||||
if (!current) {
|
||||
current = new SideboardPlan;
|
||||
sideboardPlans.insert(QString(), current);
|
||||
}
|
||||
|
||||
current->setMoveList(plan);
|
||||
}
|
||||
|
||||
bool DeckList::readElement(QXmlStreamReader *xml)
|
||||
{
|
||||
const QString childName = xml->name().toString();
|
||||
if (xml->isStartElement()) {
|
||||
if (childName == "lastLoadedTimestamp") {
|
||||
lastLoadedTimestamp = xml->readElementText();
|
||||
} else if (childName == "deckname") {
|
||||
name = xml->readElementText();
|
||||
} else if (childName == "comments") {
|
||||
comments = xml->readElementText();
|
||||
} else if (childName == "bannerCard") {
|
||||
QString providerId = xml->attributes().value("providerId").toString();
|
||||
QString cardName = xml->readElementText();
|
||||
bannerCard = {cardName, providerId};
|
||||
} else if (childName == "tags") {
|
||||
tags.clear(); // Clear existing tags
|
||||
while (xml->readNextStartElement()) {
|
||||
if (xml->name().toString() == "tag") {
|
||||
tags.append(xml->readElementText());
|
||||
}
|
||||
}
|
||||
} else if (childName == "zone") {
|
||||
InnerDecklistNode *newZone = getZoneObjFromName(xml->attributes().value("name").toString());
|
||||
newZone->readElement(xml);
|
||||
} else if (childName == "sideboard_plan") {
|
||||
SideboardPlan *newSideboardPlan = new SideboardPlan;
|
||||
if (newSideboardPlan->readElement(xml)) {
|
||||
sideboardPlans.insert(newSideboardPlan->getName(), newSideboardPlan);
|
||||
} else {
|
||||
delete newSideboardPlan;
|
||||
}
|
||||
}
|
||||
} else if (xml->isEndElement() && (childName == "cockatrice_deck")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeckList::write(QXmlStreamWriter *xml) const
|
||||
{
|
||||
xml->writeStartElement("cockatrice_deck");
|
||||
xml->writeAttribute("version", "1");
|
||||
xml->writeTextElement("lastLoadedTimestamp", lastLoadedTimestamp);
|
||||
xml->writeTextElement("deckname", name);
|
||||
xml->writeStartElement("bannerCard");
|
||||
xml->writeAttribute("providerId", bannerCard.providerId);
|
||||
xml->writeCharacters(bannerCard.name);
|
||||
xml->writeEndElement();
|
||||
xml->writeTextElement("comments", comments);
|
||||
|
||||
// Write tags
|
||||
xml->writeStartElement("tags");
|
||||
for (const QString &tag : tags) {
|
||||
xml->writeTextElement("tag", tag);
|
||||
}
|
||||
xml->writeEndElement();
|
||||
|
||||
// Write zones
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
root->at(i)->writeElement(xml);
|
||||
}
|
||||
|
||||
// Write sideboard plans
|
||||
QMapIterator<QString, SideboardPlan *> i(sideboardPlans);
|
||||
while (i.hasNext()) {
|
||||
i.next().value()->write(xml);
|
||||
}
|
||||
|
||||
xml->writeEndElement(); // Close "cockatrice_deck"
|
||||
}
|
||||
|
||||
bool DeckList::loadFromXml(QXmlStreamReader *xml)
|
||||
{
|
||||
if (xml->error()) {
|
||||
qDebug() << "Error loading deck from xml: " << xml->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanList();
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
if (xml->isStartElement()) {
|
||||
if (xml->name().toString() != "cockatrice_deck")
|
||||
return false;
|
||||
while (!xml->atEnd()) {
|
||||
xml->readNext();
|
||||
if (!readElement(xml))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshDeckHash();
|
||||
if (xml->error()) {
|
||||
qDebug() << "Error loading deck from xml: " << xml->errorString();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckList::loadFromString_Native(const QString &nativeString)
|
||||
{
|
||||
QXmlStreamReader xml(nativeString);
|
||||
return loadFromXml(&xml);
|
||||
}
|
||||
|
||||
QString DeckList::writeToString_Native() const
|
||||
{
|
||||
QString result;
|
||||
QXmlStreamWriter xml(&result);
|
||||
xml.writeStartDocument();
|
||||
write(&xml);
|
||||
xml.writeEndDocument();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DeckList::loadFromFile_Native(QIODevice *device)
|
||||
{
|
||||
QXmlStreamReader xml(device);
|
||||
return loadFromXml(&xml);
|
||||
}
|
||||
|
||||
bool DeckList::saveToFile_Native(QIODevice *device)
|
||||
{
|
||||
QXmlStreamWriter xml(device);
|
||||
xml.setAutoFormatting(true);
|
||||
xml.writeStartDocument();
|
||||
|
||||
write(&xml);
|
||||
|
||||
xml.writeEndDocument();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the decklist and loads in a new deck from text
|
||||
*
|
||||
* @param in The text to load
|
||||
* @param preserveMetadata If true, don't clear the existing metadata
|
||||
* @return False if the input was empty, true otherwise.
|
||||
*/
|
||||
bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata)
|
||||
{
|
||||
const QRegularExpression reCardLine(R"(^\s*[\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption);
|
||||
const QRegularExpression reEmpty("^\\s*$");
|
||||
const QRegularExpression reComment(R"([\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption);
|
||||
const QRegularExpression reSBMark("^\\s*sb:\\s*(.+)", QRegularExpression::CaseInsensitiveOption);
|
||||
const QRegularExpression reSBComment("^sideboard\\b.*$", QRegularExpression::CaseInsensitiveOption);
|
||||
const QRegularExpression reDeckComment("^((main)?deck(list)?|mainboard)\\b",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
// Regex for advanced card parsing
|
||||
const QRegularExpression reMultiplier(R"(^[xX\(\[]*(\d+)[xX\*\)\]]* ?(.+))");
|
||||
const QRegularExpression reSplitCard(R"( ?\/\/ ?)");
|
||||
const QRegularExpression reBrace(R"( ?[\[\{][^\]\}]*[\]\}] ?)"); // not nested
|
||||
const QRegularExpression reRoundBrace(R"(^\([^\)]*\) ?)"); // () are only matched at start of string
|
||||
const QRegularExpression reDigitBrace(R"( ?\(\d*\) ?)"); // () are matched if containing digits
|
||||
const QRegularExpression reBraceDigit(
|
||||
R"( ?\([\dA-Z]+\) *\d+$)"); // () are matched if containing setcode then a number
|
||||
const QRegularExpression reDoubleFacedMarker(R"( ?\(Transform\) ?)");
|
||||
|
||||
// Regex for extracting set code and collector number with attached symbols
|
||||
const QRegularExpression reHyphenFormat(R"(\((\w{3,})\)\s+(\w{3,})-(\d+[^\w\s]*))");
|
||||
const QRegularExpression reRegularFormat(R"(\((\w{3,})\)\s+(\d+[^\w\s]*))");
|
||||
|
||||
const QHash<QRegularExpression, QString> differences{{QRegularExpression("’"), QString("'")},
|
||||
{QRegularExpression("Æ"), QString("Ae")},
|
||||
{QRegularExpression("æ"), QString("ae")},
|
||||
{QRegularExpression(" ?[|/]+ ?"), QString(" // ")}};
|
||||
|
||||
cleanList(preserveMetadata);
|
||||
|
||||
auto inputs = in.readAll().trimmed().split('\n');
|
||||
auto max_line = inputs.size();
|
||||
|
||||
// Start at the first empty line before the first card line
|
||||
auto deckStart = inputs.indexOf(reCardLine);
|
||||
if (deckStart == -1) {
|
||||
if (inputs.indexOf(reComment) == -1) {
|
||||
return false; // Input is empty
|
||||
}
|
||||
deckStart = max_line;
|
||||
} else {
|
||||
deckStart = inputs.lastIndexOf(reEmpty, deckStart);
|
||||
if (deckStart == -1) {
|
||||
deckStart = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// find sideboard position, if marks are used this won't be needed
|
||||
int sBStart = -1;
|
||||
if (inputs.indexOf(reSBMark, deckStart) == -1) {
|
||||
sBStart = inputs.indexOf(reSBComment, deckStart);
|
||||
if (sBStart == -1) {
|
||||
sBStart = inputs.indexOf(reEmpty, deckStart + 1);
|
||||
if (sBStart == -1) {
|
||||
sBStart = max_line;
|
||||
}
|
||||
auto nextCard = inputs.indexOf(reCardLine, sBStart + 1);
|
||||
if (inputs.indexOf(reEmpty, nextCard + 1) != -1) {
|
||||
sBStart = max_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
// Parse name and comments
|
||||
while (index < deckStart) {
|
||||
const auto ¤t = inputs.at(index++);
|
||||
if (!current.contains(reEmpty)) {
|
||||
match = reComment.match(current);
|
||||
name = match.captured();
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (index < deckStart) {
|
||||
const auto ¤t = inputs.at(index++);
|
||||
if (!current.contains(reEmpty)) {
|
||||
match = reComment.match(current);
|
||||
comments += match.captured() + '\n';
|
||||
}
|
||||
}
|
||||
comments.chop(1);
|
||||
|
||||
// Discard empty lines
|
||||
while (index < max_line && inputs.at(index).contains(reEmpty)) {
|
||||
++index;
|
||||
}
|
||||
|
||||
// Discard line if it starts with deck or mainboard, all cards until the sideboard starts are in the mainboard
|
||||
if (inputs.at(index).contains(reDeckComment)) {
|
||||
++index;
|
||||
}
|
||||
|
||||
// Parse decklist
|
||||
for (; index < max_line; ++index) {
|
||||
// check if line is a card
|
||||
match = reCardLine.match(inputs.at(index));
|
||||
if (!match.hasMatch())
|
||||
continue;
|
||||
|
||||
QString cardName = match.captured().simplified();
|
||||
bool sideboard = false;
|
||||
|
||||
// Sideboard detection
|
||||
if (sBStart < 0) {
|
||||
match = reSBMark.match(cardName);
|
||||
if (match.hasMatch()) {
|
||||
sideboard = true;
|
||||
cardName = match.captured(1);
|
||||
}
|
||||
} else {
|
||||
if (index == sBStart)
|
||||
continue;
|
||||
sideboard = index > sBStart;
|
||||
}
|
||||
|
||||
// Extract set code, collector number, and foil
|
||||
QString setCode;
|
||||
QString collectorNumber;
|
||||
bool isFoil = false;
|
||||
|
||||
// Check for foil status at the end of the card name
|
||||
if (cardName.endsWith("*F*", Qt::CaseInsensitive)) {
|
||||
isFoil = true;
|
||||
cardName.chop(3); // Remove the "*F*" from the card name
|
||||
}
|
||||
Q_UNUSED(isFoil);
|
||||
|
||||
// Attempt to match the hyphen-separated format (PLST-2094)
|
||||
match = reHyphenFormat.match(cardName);
|
||||
if (match.hasMatch()) {
|
||||
setCode = match.captured(2).toUpper();
|
||||
collectorNumber = match.captured(3);
|
||||
cardName = cardName.left(match.capturedStart()).trimmed();
|
||||
} else {
|
||||
// Attempt to match the regular format (PLST) 2094
|
||||
match = reRegularFormat.match(cardName);
|
||||
if (match.hasMatch()) {
|
||||
setCode = match.captured(1).toUpper();
|
||||
collectorNumber = match.captured(2);
|
||||
cardName = cardName.left(match.capturedStart()).trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
// check if a specific amount is mentioned
|
||||
int amount = 1;
|
||||
match = reMultiplier.match(cardName);
|
||||
if (match.hasMatch()) {
|
||||
amount = match.captured(1).toInt();
|
||||
cardName = match.captured(2);
|
||||
}
|
||||
|
||||
// Handle advanced card types
|
||||
if (cardName.contains(reSplitCard)) {
|
||||
cardName = cardName.split(reSplitCard).join(" // ");
|
||||
}
|
||||
|
||||
if (cardName.contains(reDoubleFacedMarker)) {
|
||||
QStringList faces = cardName.split(reDoubleFacedMarker);
|
||||
cardName = faces.first().trimmed();
|
||||
}
|
||||
|
||||
// Remove unnecessary characters
|
||||
cardName.remove(reBrace);
|
||||
cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards
|
||||
cardName.remove(reDigitBrace); // from un-sets that have a word in between round braces at the end
|
||||
cardName.remove(reBraceDigit); // very specific format with the set code in () and collectors number after
|
||||
|
||||
// Normalize names
|
||||
for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) {
|
||||
cardName.replace(diff.key(), diff.value());
|
||||
}
|
||||
|
||||
// Resolve complete card name, this function does nothing if the name is not found
|
||||
cardName = getCompleteCardName(cardName);
|
||||
|
||||
// Determine the zone (mainboard/sideboard)
|
||||
QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN);
|
||||
|
||||
// make new entry in decklist
|
||||
new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), -1, setCode, collectorNumber);
|
||||
}
|
||||
|
||||
refreshDeckHash();
|
||||
return true;
|
||||
}
|
||||
|
||||
InnerDecklistNode *DeckList::getZoneObjFromName(const QString &zoneName)
|
||||
{
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
auto *node = dynamic_cast<InnerDecklistNode *>(root->at(i));
|
||||
if (node->getName() == zoneName) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return new InnerDecklistNode(zoneName, root);
|
||||
}
|
||||
|
||||
bool DeckList::loadFromFile_Plain(QIODevice *device)
|
||||
{
|
||||
QTextStream in(device);
|
||||
return loadFromStream_Plain(in, false);
|
||||
}
|
||||
|
||||
bool DeckList::saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards)
|
||||
{
|
||||
auto writeToStream = [&stream, prefixSideboardCards, slashTappedOutSplitCards](const auto node, const auto card) {
|
||||
if (prefixSideboardCards && node->getName() == DECK_ZONE_SIDE) {
|
||||
stream << "SB: ";
|
||||
}
|
||||
if (!slashTappedOutSplitCards) {
|
||||
stream << QString("%1 %2\n").arg(card->getNumber()).arg(card->getName());
|
||||
} else {
|
||||
stream << QString("%1 %2\n").arg(card->getNumber()).arg(card->getName().replace("//", "/"));
|
||||
}
|
||||
};
|
||||
|
||||
forEachCard(writeToStream);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckList::saveToFile_Plain(QIODevice *device, bool prefixSideboardCards, bool slashTappedOutSplitCards)
|
||||
{
|
||||
QTextStream out(device);
|
||||
return saveToStream_Plain(out, prefixSideboardCards, slashTappedOutSplitCards);
|
||||
}
|
||||
|
||||
QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappedOutSplitCards)
|
||||
{
|
||||
QString result;
|
||||
QTextStream out(&result);
|
||||
saveToStream_Plain(out, prefixSideboardCards, slashTappedOutSplitCards);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all cards and other data from the decklist
|
||||
*
|
||||
* @param preserveMetadata If true, only clear the cards
|
||||
*/
|
||||
void DeckList::cleanList(bool preserveMetadata)
|
||||
{
|
||||
root->clearTree();
|
||||
if (!preserveMetadata) {
|
||||
setName();
|
||||
setComments();
|
||||
setTags();
|
||||
}
|
||||
refreshDeckHash();
|
||||
}
|
||||
|
||||
void DeckList::getCardListHelper(InnerDecklistNode *item, QSet<QString> &result)
|
||||
{
|
||||
for (int i = 0; i < item->size(); ++i) {
|
||||
auto *node = dynamic_cast<DecklistCardNode *>(item->at(i));
|
||||
|
||||
if (node) {
|
||||
result.insert(node->getName());
|
||||
} else {
|
||||
getCardListHelper(dynamic_cast<InnerDecklistNode *>(item->at(i)), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeckList::getCardRefListHelper(InnerDecklistNode *item, QList<CardRef> &result)
|
||||
{
|
||||
for (int i = 0; i < item->size(); ++i) {
|
||||
auto *node = dynamic_cast<DecklistCardNode *>(item->at(i));
|
||||
|
||||
if (node) {
|
||||
result.append(node->toCardRef());
|
||||
} else {
|
||||
getCardRefListHelper(dynamic_cast<InnerDecklistNode *>(item->at(i)), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList DeckList::getCardList() const
|
||||
{
|
||||
QSet<QString> result;
|
||||
getCardListHelper(root, result);
|
||||
return result.values();
|
||||
}
|
||||
|
||||
QList<CardRef> DeckList::getCardRefList() const
|
||||
{
|
||||
QList<CardRef> result;
|
||||
getCardRefListHelper(root, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
int DeckList::getSideboardSize() const
|
||||
{
|
||||
int size = 0;
|
||||
for (int i = 0; i < root->size(); ++i) {
|
||||
auto *node = dynamic_cast<InnerDecklistNode *>(root->at(i));
|
||||
if (node->getName() != DECK_ZONE_SIDE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < node->size(); j++) {
|
||||
auto *card = dynamic_cast<DecklistCardNode *>(node->at(j));
|
||||
size += card->getNumber();
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
DecklistCardNode *DeckList::addCard(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const int position,
|
||||
const QString &cardSetName,
|
||||
const QString &cardSetCollectorNumber,
|
||||
const QString &cardProviderId)
|
||||
{
|
||||
auto *zoneNode = dynamic_cast<InnerDecklistNode *>(root->findChild(zoneName));
|
||||
if (zoneNode == nullptr) {
|
||||
zoneNode = new InnerDecklistNode(zoneName, root);
|
||||
}
|
||||
|
||||
auto *node =
|
||||
new DecklistCardNode(cardName, 1, zoneNode, position, cardSetName, cardSetCollectorNumber, cardProviderId);
|
||||
refreshDeckHash();
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode)
|
||||
{
|
||||
if (node == root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updateHash = false;
|
||||
if (rootNode == nullptr) {
|
||||
rootNode = root;
|
||||
updateHash = true;
|
||||
}
|
||||
|
||||
int index = rootNode->indexOf(node);
|
||||
if (index != -1) {
|
||||
delete rootNode->takeAt(index);
|
||||
|
||||
if (rootNode->empty()) {
|
||||
deleteNode(rootNode, rootNode->getParent());
|
||||
}
|
||||
|
||||
if (updateHash) {
|
||||
refreshDeckHash();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rootNode->size(); i++) {
|
||||
auto *inner = dynamic_cast<InnerDecklistNode *>(rootNode->at(i));
|
||||
if (inner) {
|
||||
if (deleteNode(node, inner)) {
|
||||
if (updateHash) {
|
||||
refreshDeckHash();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static QString computeDeckHash(const InnerDecklistNode *root)
|
||||
{
|
||||
QStringList cardList;
|
||||
QSet<QString> hashZones, optionalZones;
|
||||
|
||||
hashZones << DECK_ZONE_MAIN << DECK_ZONE_SIDE; // Zones in deck to be included in hashing process
|
||||
optionalZones << DECK_ZONE_TOKENS; // Optional zones in deck not included in hashing process
|
||||
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
auto *node = dynamic_cast<InnerDecklistNode *>(root->at(i));
|
||||
for (int j = 0; j < node->size(); j++) {
|
||||
if (hashZones.contains(node->getName())) // Mainboard or Sideboard
|
||||
{
|
||||
auto *card = dynamic_cast<DecklistCardNode *>(node->at(j));
|
||||
for (int k = 0; k < card->getNumber(); ++k) {
|
||||
cardList.append((node->getName() == DECK_ZONE_SIDE ? "SB:" : "") + card->getName().toLower());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cardList.sort();
|
||||
QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1);
|
||||
quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) +
|
||||
(((quint64)(unsigned char)deckHashArray[1]) << 24) +
|
||||
(((quint64)(unsigned char)deckHashArray[2] << 16)) +
|
||||
(((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4];
|
||||
return QString::number(number, 32).rightJustified(8, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the deck hash.
|
||||
* The hash is computed on the first call to this method, and is cached until the decklist is modified.
|
||||
*
|
||||
* @return The deck hash
|
||||
*/
|
||||
QString DeckList::getDeckHash() const
|
||||
{
|
||||
if (!cachedDeckHash.isEmpty()) {
|
||||
return cachedDeckHash;
|
||||
}
|
||||
|
||||
cachedDeckHash = computeDeckHash(root);
|
||||
return cachedDeckHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the cached deckHash and emits the deckHashChanged signal.
|
||||
*/
|
||||
void DeckList::refreshDeckHash()
|
||||
{
|
||||
cachedDeckHash = QString();
|
||||
emit deckHashChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a given function on each card in the deck.
|
||||
*/
|
||||
void DeckList::forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func)
|
||||
{
|
||||
// Support for this is only possible if the internal structure
|
||||
// doesn't get more complicated.
|
||||
for (int i = 0; i < root->size(); i++) {
|
||||
InnerDecklistNode *node = dynamic_cast<InnerDecklistNode *>(root->at(i));
|
||||
for (int j = 0; j < node->size(); j++) {
|
||||
DecklistCardNode *card = dynamic_cast<DecklistCardNode *>(node->at(j));
|
||||
func(node, card);
|
||||
}
|
||||
}
|
||||
}
|
||||
380
common/deck_list/deck_list.h
Normal file
380
common/deck_list/deck_list.h
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
#ifndef DECKLIST_H
|
||||
#define DECKLIST_H
|
||||
|
||||
#include "../card_ref.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QVector>
|
||||
|
||||
// Required on Mac. Forward declaration doesn't work. Don't ask why.
|
||||
#include <QtCore/QXmlStreamReader>
|
||||
#include <QtCore/QXmlStreamWriter>
|
||||
#include <common/pb/move_card_to_zone.pb.h>
|
||||
|
||||
class CardDatabase;
|
||||
class QIODevice;
|
||||
class QTextStream;
|
||||
|
||||
class InnerDecklistNode;
|
||||
|
||||
#define DECK_ZONE_MAIN "main"
|
||||
#define DECK_ZONE_SIDE "side"
|
||||
#define DECK_ZONE_TOKENS "tokens"
|
||||
|
||||
class SideboardPlan
|
||||
{
|
||||
private:
|
||||
QString name;
|
||||
QList<MoveCard_ToZone> moveList;
|
||||
|
||||
public:
|
||||
explicit SideboardPlan(const QString &_name = QString(),
|
||||
const QList<MoveCard_ToZone> &_moveList = QList<MoveCard_ToZone>());
|
||||
bool readElement(QXmlStreamReader *xml);
|
||||
void write(QXmlStreamWriter *xml);
|
||||
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
const QList<MoveCard_ToZone> &getMoveList() const
|
||||
{
|
||||
return moveList;
|
||||
}
|
||||
void setMoveList(const QList<MoveCard_ToZone> &_moveList);
|
||||
};
|
||||
|
||||
enum DeckSortMethod
|
||||
{
|
||||
ByNumber,
|
||||
ByName,
|
||||
Default
|
||||
};
|
||||
|
||||
class AbstractDecklistNode
|
||||
{
|
||||
protected:
|
||||
InnerDecklistNode *parent;
|
||||
DeckSortMethod sortMethod;
|
||||
|
||||
public:
|
||||
explicit AbstractDecklistNode(InnerDecklistNode *_parent = nullptr, int position = -1);
|
||||
virtual ~AbstractDecklistNode() = default;
|
||||
virtual void setSortMethod(DeckSortMethod method)
|
||||
{
|
||||
sortMethod = method;
|
||||
}
|
||||
virtual QString getName() const = 0;
|
||||
virtual QString getCardProviderId() const = 0;
|
||||
virtual QString getCardSetShortName() const = 0;
|
||||
virtual QString getCardCollectorNumber() const = 0;
|
||||
[[nodiscard]] virtual bool isDeckHeader() const = 0;
|
||||
InnerDecklistNode *getParent() const
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
int depth() const;
|
||||
virtual int height() const = 0;
|
||||
virtual bool compare(AbstractDecklistNode *other) const = 0;
|
||||
|
||||
virtual bool readElement(QXmlStreamReader *xml) = 0;
|
||||
virtual void writeElement(QXmlStreamWriter *xml) = 0;
|
||||
};
|
||||
|
||||
class InnerDecklistNode : public AbstractDecklistNode, public QList<AbstractDecklistNode *>
|
||||
{
|
||||
QString name;
|
||||
|
||||
public:
|
||||
explicit InnerDecklistNode(QString _name = QString(), InnerDecklistNode *_parent = nullptr, int position = -1)
|
||||
: AbstractDecklistNode(_parent, position), name(std::move(_name))
|
||||
{
|
||||
}
|
||||
explicit InnerDecklistNode(InnerDecklistNode *other, InnerDecklistNode *_parent = nullptr);
|
||||
~InnerDecklistNode() override;
|
||||
void setSortMethod(DeckSortMethod method) override;
|
||||
[[nodiscard]] QString getName() const override
|
||||
{
|
||||
return name;
|
||||
}
|
||||
void setName(const QString &_name)
|
||||
{
|
||||
name = _name;
|
||||
}
|
||||
static QString visibleNameFromName(const QString &_name);
|
||||
[[nodiscard]] virtual QString getVisibleName() const;
|
||||
[[nodiscard]] QString getCardProviderId() const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
[[nodiscard]] QString getCardSetShortName() const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
[[nodiscard]] QString getCardCollectorNumber() const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
[[nodiscard]] bool isDeckHeader() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void clearTree();
|
||||
AbstractDecklistNode *findChild(const QString &_name);
|
||||
AbstractDecklistNode *findCardChildByNameProviderIdAndNumber(const QString &_name,
|
||||
const QString &_providerId = "",
|
||||
const QString &_cardNumber = "");
|
||||
int height() const override;
|
||||
int recursiveCount(bool countTotalCards = false) const;
|
||||
bool compare(AbstractDecklistNode *other) const override;
|
||||
bool compareNumber(AbstractDecklistNode *other) const;
|
||||
bool compareName(AbstractDecklistNode *other) const;
|
||||
QVector<QPair<int, int>> sort(Qt::SortOrder order = Qt::AscendingOrder);
|
||||
|
||||
bool readElement(QXmlStreamReader *xml) override;
|
||||
void writeElement(QXmlStreamWriter *xml) override;
|
||||
};
|
||||
|
||||
class AbstractDecklistCardNode : public AbstractDecklistNode
|
||||
{
|
||||
public:
|
||||
explicit AbstractDecklistCardNode(InnerDecklistNode *_parent = nullptr, int position = -1)
|
||||
: AbstractDecklistNode(_parent, position)
|
||||
{
|
||||
}
|
||||
virtual int getNumber() const = 0;
|
||||
virtual void setNumber(int _number) = 0;
|
||||
QString getName() const override = 0;
|
||||
virtual void setName(const QString &_name) = 0;
|
||||
virtual QString getCardProviderId() const override = 0;
|
||||
virtual void setCardProviderId(const QString &_cardProviderId) = 0;
|
||||
virtual QString getCardSetShortName() const override = 0;
|
||||
virtual void setCardSetShortName(const QString &_cardSetShortName) = 0;
|
||||
virtual QString getCardCollectorNumber() const override = 0;
|
||||
virtual void setCardCollectorNumber(const QString &_cardSetNumber) = 0;
|
||||
int height() const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
bool compare(AbstractDecklistNode *other) const override;
|
||||
bool compareNumber(AbstractDecklistNode *other) const;
|
||||
bool compareName(AbstractDecklistNode *other) const;
|
||||
|
||||
bool readElement(QXmlStreamReader *xml) override;
|
||||
void writeElement(QXmlStreamWriter *xml) override;
|
||||
};
|
||||
|
||||
class DecklistCardNode : public AbstractDecklistCardNode
|
||||
{
|
||||
QString name;
|
||||
int number;
|
||||
QString cardSetShortName;
|
||||
QString cardSetNumber;
|
||||
QString cardProviderId;
|
||||
|
||||
public:
|
||||
explicit DecklistCardNode(QString _name = QString(),
|
||||
int _number = 1,
|
||||
InnerDecklistNode *_parent = nullptr,
|
||||
int position = -1,
|
||||
QString _cardSetShortName = QString(),
|
||||
QString _cardSetNumber = QString(),
|
||||
QString _cardProviderId = QString())
|
||||
: AbstractDecklistCardNode(_parent, position), name(std::move(_name)), number(_number),
|
||||
cardSetShortName(std::move(_cardSetShortName)), cardSetNumber(std::move(_cardSetNumber)),
|
||||
cardProviderId(std::move(_cardProviderId))
|
||||
{
|
||||
}
|
||||
explicit DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent);
|
||||
int getNumber() const override
|
||||
{
|
||||
return number;
|
||||
}
|
||||
void setNumber(int _number) override
|
||||
{
|
||||
number = _number;
|
||||
}
|
||||
QString getName() const override
|
||||
{
|
||||
return name;
|
||||
}
|
||||
void setName(const QString &_name) override
|
||||
{
|
||||
name = _name;
|
||||
}
|
||||
QString getCardProviderId() const override
|
||||
{
|
||||
return cardProviderId;
|
||||
}
|
||||
void setCardProviderId(const QString &_providerId) override
|
||||
{
|
||||
cardProviderId = _providerId;
|
||||
}
|
||||
|
||||
QString getCardSetShortName() const override
|
||||
{
|
||||
return cardSetShortName;
|
||||
}
|
||||
void setCardSetShortName(const QString &_cardSetShortName) override
|
||||
{
|
||||
cardSetShortName = _cardSetShortName;
|
||||
}
|
||||
QString getCardCollectorNumber() const override
|
||||
{
|
||||
return cardSetNumber;
|
||||
}
|
||||
void setCardCollectorNumber(const QString &_cardSetNumber) override
|
||||
{
|
||||
cardSetNumber = _cardSetNumber;
|
||||
}
|
||||
[[nodiscard]] bool isDeckHeader() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
CardRef toCardRef() const
|
||||
{
|
||||
return {name, cardProviderId};
|
||||
}
|
||||
};
|
||||
|
||||
class DeckList : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QString name, comments;
|
||||
CardRef bannerCard;
|
||||
QString lastLoadedTimestamp;
|
||||
QStringList tags;
|
||||
QMap<QString, SideboardPlan *> sideboardPlans;
|
||||
InnerDecklistNode *root;
|
||||
static void getCardListHelper(InnerDecklistNode *node, QSet<QString> &result);
|
||||
static void getCardRefListHelper(InnerDecklistNode *item, QList<CardRef> &result);
|
||||
InnerDecklistNode *getZoneObjFromName(const QString &zoneName);
|
||||
|
||||
/**
|
||||
* Empty string indicates invalidated cache.
|
||||
*/
|
||||
mutable QString cachedDeckHash;
|
||||
|
||||
protected:
|
||||
virtual QString getCardZoneFromName(const QString /*cardName*/, QString currentZoneName)
|
||||
{
|
||||
return currentZoneName;
|
||||
};
|
||||
virtual QString getCompleteCardName(const QString &cardName) const
|
||||
{
|
||||
return cardName;
|
||||
};
|
||||
|
||||
signals:
|
||||
void deckHashChanged();
|
||||
void deckTagsChanged();
|
||||
|
||||
public slots:
|
||||
void setName(const QString &_name = QString())
|
||||
{
|
||||
name = _name;
|
||||
}
|
||||
void setComments(const QString &_comments = QString())
|
||||
{
|
||||
comments = _comments;
|
||||
}
|
||||
void setTags(const QStringList &_tags = QStringList())
|
||||
{
|
||||
tags = _tags;
|
||||
emit deckTagsChanged();
|
||||
}
|
||||
void addTag(const QString &_tag)
|
||||
{
|
||||
tags.append(_tag);
|
||||
emit deckTagsChanged();
|
||||
}
|
||||
void clearTags()
|
||||
{
|
||||
tags.clear();
|
||||
emit deckTagsChanged();
|
||||
}
|
||||
void setBannerCard(const CardRef &_bannerCard = {})
|
||||
{
|
||||
bannerCard = _bannerCard;
|
||||
}
|
||||
void setLastLoadedTimestamp(const QString &_lastLoadedTimestamp = QString())
|
||||
{
|
||||
lastLoadedTimestamp = _lastLoadedTimestamp;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit DeckList();
|
||||
DeckList(const DeckList &other);
|
||||
explicit DeckList(const QString &nativeString);
|
||||
~DeckList() override;
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
QString getComments() const
|
||||
{
|
||||
return comments;
|
||||
}
|
||||
QStringList getTags() const
|
||||
{
|
||||
return tags;
|
||||
}
|
||||
CardRef getBannerCard() const
|
||||
{
|
||||
return bannerCard;
|
||||
}
|
||||
QString getLastLoadedTimestamp() const
|
||||
{
|
||||
return lastLoadedTimestamp;
|
||||
}
|
||||
QList<MoveCard_ToZone> getCurrentSideboardPlan();
|
||||
void setCurrentSideboardPlan(const QList<MoveCard_ToZone> &plan);
|
||||
const QMap<QString, SideboardPlan *> &getSideboardPlans() const
|
||||
{
|
||||
return sideboardPlans;
|
||||
}
|
||||
|
||||
bool readElement(QXmlStreamReader *xml);
|
||||
void write(QXmlStreamWriter *xml) const;
|
||||
bool loadFromXml(QXmlStreamReader *xml);
|
||||
bool loadFromString_Native(const QString &nativeString);
|
||||
QString writeToString_Native() const;
|
||||
bool loadFromFile_Native(QIODevice *device);
|
||||
bool saveToFile_Native(QIODevice *device);
|
||||
bool loadFromStream_Plain(QTextStream &stream, bool preserveMetadata);
|
||||
bool loadFromFile_Plain(QIODevice *device);
|
||||
bool saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards);
|
||||
bool saveToFile_Plain(QIODevice *device, bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false);
|
||||
QString writeToString_Plain(bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false);
|
||||
|
||||
void cleanList(bool preserveMetadata = false);
|
||||
bool isEmpty() const
|
||||
{
|
||||
return root->isEmpty() && name.isEmpty() && comments.isEmpty() && sideboardPlans.isEmpty();
|
||||
}
|
||||
QStringList getCardList() const;
|
||||
QList<CardRef> getCardRefList() const;
|
||||
|
||||
int getSideboardSize() const;
|
||||
|
||||
InnerDecklistNode *getRoot() const
|
||||
{
|
||||
return root;
|
||||
}
|
||||
DecklistCardNode *addCard(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
int position,
|
||||
const QString &cardSetName = QString(),
|
||||
const QString &cardSetCollectorNumber = QString(),
|
||||
const QString &cardProviderId = QString());
|
||||
bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr);
|
||||
|
||||
QString getDeckHash() const;
|
||||
void refreshDeckHash();
|
||||
|
||||
void forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func);
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue