Move cards and filters folder out of game (#6145)

* big move

* also move game_specific_terms

* fix imports

* alphabetize cmake

* fix build failure

* create database folder and move files into it

* fix includes

* run formatter
This commit is contained in:
RickyRister 2025-09-16 03:02:57 -07:00 committed by GitHub
parent bed79ef89e
commit 7ac22a6ce8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
149 changed files with 218 additions and 218 deletions

View file

@ -1,9 +1,9 @@
#include "abstract_card_item.h"
#include "../../client/ui/picture_loader/picture_loader.h"
#include "../../database/card_database.h"
#include "../../database/card_database_manager.h"
#include "../../settings/cache_settings.h"
#include "../cards/card_database.h"
#include "../cards/card_database_manager.h"
#include "../game_scene.h"
#include <QCursor>

View file

@ -1,7 +1,7 @@
#ifndef ABSTRACTCARDITEM_H
#define ABSTRACTCARDITEM_H
#include "../cards/exact_card.h"
#include "../../card/exact_card.h"
#include "arrow_target.h"
#include "card_ref.h"
#include "graphics_item_type.h"

View file

@ -1,8 +1,8 @@
#define _USE_MATH_DEFINES
#include "arrow_item.h"
#include "../../card/card_info.h"
#include "../../settings/cache_settings.h"
#include "../cards/card_info.h"
#include "../player/player.h"
#include "../player/player_target.h"
#include "../zones/card_zone.h"

View file

@ -1,9 +1,9 @@
#include "card_item.h"
#include "../../card/card_info.h"
#include "../../client/tabs/tab_game.h"
#include "../../settings/cache_settings.h"
#include "../../settings/card_counter_settings.h"
#include "../cards/card_info.h"
#include "../game_scene.h"
#include "../player/player.h"
#include "../zones/card_zone.h"

View file

@ -1,6 +1,6 @@
#include "card_list.h"
#include "../cards/card_info.h"
#include "../../card/card_info.h"
#include "card_item.h"
#include <QDebug>

View file

@ -1,18 +0,0 @@
#include "card_completer_proxy_model.h"
CardCompleterProxyModel::CardCompleterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
}
bool CardCompleterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if (filterRegularExpression().pattern().isEmpty()) {
return true;
}
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
QString data = index.data(Qt::DisplayRole).toString();
// Ensure substring matching
return data.contains(filterRegularExpression());
}

View file

@ -1,16 +0,0 @@
#ifndef CARD_COMPLETER_PROXY_MODEL_H
#define CARD_COMPLETER_PROXY_MODEL_H
#include <QSortFilterProxyModel>
class CardCompleterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit CardCompleterProxyModel(QObject *parent = nullptr);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
#endif // CARD_COMPLETER_PROXY_MODEL_H

View file

@ -1,627 +0,0 @@
#include "card_database.h"
#include "../../client/ui/picture_loader/picture_loader.h"
#include "../../settings/cache_settings.h"
#include "../../utility/card_set_comparator.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 <qrandom.h>
#include <utility>
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, &ICardDatabaseParser::addCard, this, &CardDatabase::addCard, Qt::DirectConnection);
connect(parser, &ICardDatabaseParser::addSet, this, &CardDatabase::addSet, Qt::DirectConnection);
}
connect(&SettingsCache::instance(), &SettingsCache::cardDatabasePathChanged, this,
&CardDatabase::loadCardDatabases);
}
CardDatabase::~CardDatabase()
{
clear();
qDeleteAll(availableParsers);
}
void CardDatabase::clear()
{
clearDatabaseMutex->lock();
for (const auto &card : cards.values()) {
if (card) {
removeCard(card);
}
}
cards.clear();
simpleNameCards.clear();
sets.clear();
ICardDatabaseParser::clearSetlist();
loadStatus = NotLoaded;
clearDatabaseMutex->unlock();
}
void CardDatabase::addCard(CardInfoPtr card)
{
if (card == nullptr) {
qCWarning(CardDatabaseLog) << "CardDatabase::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 auto &printings : card->getSets()) {
for (const PrintingInfo &printing : printings) {
sameCard->addToSet(printing.getSet(), printing);
}
}
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()) {
qCWarning(CardDatabaseLog) << "CardDatabase::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);
}
/**
* Looks up the cardInfo corresponding to the cardName.
*
* @param cardName The card name to look up
* @return A CardInfoPtr, or null if not corresponding CardInfo is found.
*/
CardInfoPtr CardDatabase::getCardInfo(const QString &cardName) const
{
return cards.value(cardName);
}
/**
* Looks up the cardInfos for a list of card names.
*
* @param cardNames The card names to look up
* @return A List of CardInfoPtr. Any failed lookups will be ignored and dropped from the resulting list
*/
QList<CardInfoPtr> CardDatabase::getCardInfos(const QStringList &cardNames) const
{
QList<CardInfoPtr> cardInfos;
for (const QString &cardName : cardNames) {
CardInfoPtr ptr = cards.value(cardName);
if (ptr)
cardInfos.append(ptr);
}
return cardInfos;
}
/**
* Looks up the cards corresponding to the CardRefs.
* If the providerId is empty, will default to the preferred printing.
* If providerId is given but not found, the PrintingInfo will be empty.
*
* @param cardRefs The cards to look up. If providerId is empty for an entry, will default to the preferred printing for
* that entry. If providerId is given but not found, the PrintingInfo will be empty for that entry.
* @return A list of cards. Any failed lookups will be ignored and dropped from the resulting list.
*/
QList<ExactCard> CardDatabase::getCards(const QList<CardRef> &cardRefs) const
{
QList<ExactCard> cards;
for (const auto &cardRef : cardRefs) {
ExactCard card = getCard(cardRef);
if (card)
cards.append(card);
}
return cards;
}
/**
* Looks up the card corresponding to the CardRef.
* If the providerId is empty, will default to the preferred printing.
* If providerId is given but not found, the PrintingInfo will be empty.
*
* @param cardRef The card to look up.
* @return A specific printing of a card, or empty if not found.
*/
ExactCard CardDatabase::getCard(const CardRef &cardRef) const
{
auto info = getCardInfo(cardRef.name);
if (info.isNull()) {
return {};
}
if (cardRef.providerId.isEmpty() || cardRef.providerId.isNull()) {
return ExactCard(info, getPreferredPrinting(info));
}
return ExactCard(info, findPrintingWithId(info, cardRef.providerId));
}
CardInfoPtr CardDatabase::getCardBySimpleName(const QString &cardName) const
{
return simpleNameCards.value(CardInfo::simplifyName(cardName));
}
/**
* Looks up the card by CardRef, simplifying the name if required.
* If the providerId is empty, will default to the preferred printing.
* If providerId is given but not found, the PrintingInfo will be empty.
*
* @param cardRef The card to look up.
* @return A specific printing of a card, or empty if not found.
*/
ExactCard CardDatabase::guessCard(const CardRef &cardRef) const
{
CardInfoPtr temp = getCardInfo(cardRef.name);
if (temp == nullptr) { // get card by simple name instead
temp = getCardBySimpleName(cardRef.name);
if (temp == nullptr) { // still could not find the card, so simplify the cardName too
const auto &simpleCardName = CardInfo::simplifyName(cardRef.name);
temp = getCardBySimpleName(simpleCardName);
}
}
if (cardRef.providerId.isEmpty() || cardRef.providerId.isNull()) {
return ExactCard(temp, getPreferredPrinting(temp));
}
return ExactCard(temp, findPrintingWithId(temp, cardRef.providerId));
}
ExactCard CardDatabase::getRandomCard()
{
if (cards.isEmpty())
return {};
const auto keys = cards.keys();
int randomIndex = QRandomGenerator::global()->bounded(keys.size());
const QString &randomKey = keys.at(randomIndex);
CardInfoPtr randomCard = getCardInfo(randomKey);
return ExactCard{randomCard, getPreferredPrinting(randomCard)};
}
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;
for (auto set : sets.values()) {
result << set;
}
return result;
}
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());
qCInfo(CardDatabaseLoadingLog) << "Loaded card database: Path =" << path << "Status =" << tempLoadStatus
<< "Cards =" << cards.size() << "Sets =" << sets.size()
<< QString("%1ms").arg(msecs);
return tempLoadStatus;
}
LoadStatus CardDatabase::loadCardDatabases()
{
reloadDatabaseMutex->lock();
qCInfo(CardDatabaseLoadingLog) << "Card Database Loading Started";
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);
qCInfo(CardDatabaseLoadingLog) << "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
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success";
emit cardDatabaseLoadingFinished();
} else {
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed";
emit cardDatabaseLoadingFailed(); // bring up the settings dialog
}
reloadDatabaseMutex->unlock();
return loadStatus;
}
/**
* Gets the card representing the preferred printing of the cardInfo
*
* @param cardInfo The cardInfo to find the preferred printing for
* @return A specific printing of a card
*/
ExactCard CardDatabase::getPreferredCard(const CardInfoPtr &cardInfo) const
{
return ExactCard(cardInfo, getPreferredPrinting(cardInfo));
}
PrintingInfo CardDatabase::getSpecificPrinting(const CardRef &cardRef) const
{
CardInfoPtr cardInfo = getCardInfo(cardRef.name);
if (!cardInfo) {
return PrintingInfo(nullptr);
}
return findPrintingWithId(cardInfo, cardRef.providerId);
}
/**
* Finds the PrintingInfo in the cardInfo that has the given uuid field.
*
* @param cardInfo The CardInfo to search
* @param providerId The uuid to look for
* @return The PrintingInfo, or a default-constructed PrintingInfo if not found.
*/
PrintingInfo CardDatabase::findPrintingWithId(const CardInfoPtr &cardInfo, const QString &providerId)
{
for (const auto &printings : cardInfo->getSets()) {
for (const auto &printing : printings) {
if (printing.getUuid() == providerId) {
return printing;
}
}
}
return PrintingInfo();
}
PrintingInfo CardDatabase::getPreferredPrinting(const QString &cardName) const
{
CardInfoPtr cardInfo = getCardInfo(cardName);
return getPreferredPrinting(cardInfo);
}
PrintingInfo CardDatabase::getPreferredPrinting(const CardInfoPtr &cardInfo) const
{
if (!cardInfo) {
return PrintingInfo(nullptr);
}
SetToPrintingsMap setMap = cardInfo->getSets();
if (setMap.empty()) {
return PrintingInfo(nullptr);
}
CardSetPtr preferredSet = nullptr;
PrintingInfo preferredPrinting;
SetPriorityComparator comparator;
for (const auto &printings : setMap) {
for (auto &printing : printings) {
CardSetPtr currentSet = printing.getSet();
if (!preferredSet || comparator(currentSet, preferredSet)) {
preferredSet = currentSet;
preferredPrinting = printing;
}
}
}
if (preferredSet) {
return preferredPrinting;
}
return PrintingInfo(nullptr);
}
PrintingInfo CardDatabase::getSpecificPrinting(const QString &cardName,
const QString &setShortName,
const QString &collectorNumber) const
{
CardInfoPtr cardInfo = getCardInfo(cardName);
if (!cardInfo) {
return PrintingInfo(nullptr);
}
SetToPrintingsMap setMap = cardInfo->getSets();
if (setMap.empty()) {
return PrintingInfo(nullptr);
}
for (const auto &printings : setMap) {
for (auto &cardInfoForSet : printings) {
if (collectorNumber != nullptr) {
if (cardInfoForSet.getSet()->getShortName() == setShortName &&
cardInfoForSet.getProperty("num") == collectorNumber) {
return cardInfoForSet;
}
} else {
if (cardInfoForSet.getSet()->getShortName() == setShortName) {
return cardInfoForSet;
}
}
}
}
return PrintingInfo(nullptr);
}
QString CardDatabase::getPreferredPrintingProviderId(const QString &cardName) const
{
PrintingInfo preferredPrinting = getPreferredPrinting(cardName);
QString uuid = preferredPrinting.getUuid();
if (!uuid.isEmpty()) {
return uuid;
}
CardInfoPtr defaultCardInfo = getCardInfo(cardName);
if (defaultCardInfo.isNull()) {
return cardName;
}
return defaultCardInfo->getName();
}
bool CardDatabase::isPreferredPrinting(const CardRef &cardRef) const
{
if (cardRef.providerId.startsWith("card_")) {
return cardRef.providerId ==
QLatin1String("card_") + cardRef.name + QString("_") + getPreferredPrintingProviderId(cardRef.name);
}
return cardRef.providerId == getPreferredPrintingProviderId(cardRef.name);
}
ExactCard CardDatabase::getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const
{
// The source card does not have a printing defined, which means we can't get a card from the same set.
if (otherPrinting == PrintingInfo()) {
return getCard({cardName});
}
// The source card does have a printing defined, which means we can attempt to get a card from the same set.
PrintingInfo relatedPrinting = getSpecificPrinting(cardName, otherPrinting.getSet()->getCorrectedShortName(), "");
ExactCard relatedCard = ExactCard(guessCard({cardName}).getCardPtr(), relatedPrinting);
// We didn't find a card from the same set, just try to find any card with the same name.
if (!relatedCard) {
return getCard({cardName});
}
return relatedCard;
}
void CardDatabase::refreshCachedReverseRelatedCards()
{
for (const CardInfoPtr &card : cards)
card->resetReverseRelatedCards2Me();
for (const CardInfoPtr &card : cards) {
if (card->getReverseRelatedCards().isEmpty()) {
continue;
}
for (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;
for (const auto &card : cards.values()) {
types.insert(card->getMainCardType());
}
return types.values();
}
QMap<QString, int> CardDatabase::getAllMainCardTypesWithCount() const
{
QMap<QString, int> typeCounts;
for (const auto &card : cards.values()) {
QString type = card->getMainCardType();
typeCounts[type]++;
}
return typeCounts;
}
QMap<QString, int> CardDatabase::getAllSubCardTypesWithCount() const
{
QMap<QString, int> typeCounts;
for (const auto &card : cards.values()) {
QString type = card->getCardType();
QStringList parts = type.split("");
if (parts.size() > 1) { // Ensure there are subtypes
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts);
#else
QStringList subtypes = parts[1].split(" ", QString::SkipEmptyParts);
#endif
for (const QString &subtype : subtypes) {
typeCounts[subtype]++;
}
}
}
return typeCounts;
}
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() + "/" + CardSet::TOKENS_SETNAME + ".xml";
SetNameMap tmpSets;
CardSetPtr customTokensSet = getSet(CardSet::TOKENS_SETNAME);
tmpSets.insert(CardSet::TOKENS_SETNAME, customTokensSet);
CardNameMap tmpCards;
for (const CardInfoPtr &card : cards) {
if (card->getSets().contains(CardSet::TOKENS_SETNAME)) {
tmpCards.insert(card->getName(), card);
}
}
availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName);
return true;
}

View file

@ -1,132 +0,0 @@
#ifndef CARDDATABASE_H
#define CARDDATABASE_H
#include "../common/card_ref.h"
#include "exact_card.h"
#include <QBasicMutex>
#include <QDate>
#include <QHash>
#include <QList>
#include <QLoggingCategory>
#include <QStringList>
#include <QVector>
#include <utility>
inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database");
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading");
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure");
class ICardDatabaseParser;
enum LoadStatus
{
Ok,
VersionTooOld,
Invalid,
NotLoaded,
FileError,
NoCards
};
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:
void checkUnknownSets();
void refreshCachedReverseRelatedCards();
QBasicMutex *reloadDatabaseMutex = new QBasicMutex(), *clearDatabaseMutex = new QBasicMutex(),
*loadFromFileMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(),
*removeCardMutex = new QBasicMutex();
public:
explicit CardDatabase(QObject *parent = nullptr);
~CardDatabase() override;
void clear();
void removeCard(CardInfoPtr card);
[[nodiscard]] CardInfoPtr getCardInfo(const QString &cardName) const;
[[nodiscard]] QList<CardInfoPtr> getCardInfos(const QStringList &cardNames) const;
QList<ExactCard> getCards(const QList<CardRef> &cardRefs) const;
[[nodiscard]] ExactCard getCard(const CardRef &cardRef) const;
[[nodiscard]] ExactCard getPreferredCard(const CardInfoPtr &cardInfo) const;
static PrintingInfo findPrintingWithId(const CardInfoPtr &cardInfo, const QString &providerId);
[[nodiscard]] PrintingInfo getPreferredPrinting(const QString &cardName) const;
[[nodiscard]] PrintingInfo getPreferredPrinting(const CardInfoPtr &cardInfo) const;
[[nodiscard]] PrintingInfo getSpecificPrinting(const CardRef &cardRef) const;
PrintingInfo
getSpecificPrinting(const QString &cardName, const QString &setShortName, const QString &collectorNumber) const;
QString getPreferredPrintingProviderId(const QString &cardName) const;
bool isPreferredPrinting(const CardRef &cardRef) const;
ExactCard getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const;
[[nodiscard]] ExactCard guessCard(const CardRef &cardRef) const;
[[nodiscard]] ExactCard getRandomCard();
/*
* Get a card by its simple name. The name will be simplified in this
* function, so you don't need to simplify it beforehand.
*/
[[nodiscard]] CardInfoPtr getCardBySimpleName(const QString &cardName) const;
CardSetPtr getSet(const QString &setName);
const CardNameMap &getCardList() const
{
return cards;
}
SetList getSetList() const;
CardInfoPtr getCardFromMap(const CardNameMap &cardMap, const QString &cardName) const;
LoadStatus loadFromFile(const QString &fileName);
bool saveCustomTokensToFile();
QStringList getAllMainCardTypes() const;
QMap<QString, int> getAllMainCardTypesWithCount() const;
QMap<QString, int> getAllSubCardTypesWithCount() 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 cardDatabaseLoadingFinished();
void cardDatabaseLoadingFailed();
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
void cardDatabaseAllNewSetsEnabled();
void cardDatabaseEnabledSetsChanged();
void cardAdded(CardInfoPtr card);
void cardRemoved(CardInfoPtr card);
};
#endif

View file

@ -1,7 +0,0 @@
#include "card_database_manager.h"
CardDatabase *CardDatabaseManager::getInstance()
{
static CardDatabase instance; // Created only once, on first access
return &instance;
}

View file

@ -1,23 +0,0 @@
#ifndef CARD_DATABASE_ACCESSOR_H
#define CARD_DATABASE_ACCESSOR_H
#pragma once
#include "card_database.h"
class CardDatabaseManager
{
public:
// Delete copy constructor and assignment operator to enforce singleton
CardDatabaseManager(const CardDatabaseManager &) = delete;
CardDatabaseManager &operator=(const CardDatabaseManager &) = delete;
// Static method to access the singleton instance
static CardDatabase *getInstance();
private:
CardDatabaseManager() = default; // Private constructor
~CardDatabaseManager() = default;
};
#endif // CARD_DATABASE_ACCESSOR_H

View file

@ -1,397 +0,0 @@
#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, &CardDatabase::cardAdded, this, &CardDatabaseModel::cardAdded);
connect(db, &CardDatabase::cardRemoved, this, &CardDatabaseModel::cardRemoved);
connect(db, &CardDatabase::cardDatabaseEnabledSetsChanged, this,
&CardDatabaseModel::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 &printings : card->getSets()) {
for (const auto &printing : printings) {
if (printing.getSet()->getEnabled())
return true;
}
}
return false;
}
void CardDatabaseModel::cardDatabaseEnabledSetsChanged()
{
// remove all the cards no more present in at least one enabled set
for (const CardInfoPtr &card : cardList) {
if (!checkCardHasAtLeastOneEnabledSet(card)) {
cardRemoved(card);
}
}
// re-check all the card currently not shown, maybe their part of a newly-enabled set
for (const CardInfoPtr &card : db->getCardList()) {
if (!cardListSet.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);
cardListSet.insert(card);
connect(card.data(), &CardInfo::cardInfoChanged, this, &CardDatabaseModel::cardInfoChanged);
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);
cardListSet.remove(card);
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);
if (itemsToFetch == 0) {
return;
}
const auto startIndex = qMin(rowCount(QModelIndex()), loadedRowCount);
beginInsertRows(QModelIndex(), startIndex, startIndex + itemsToFetch - 1);
loadedRowCount += itemsToFetch;
endInsertRows();
}
int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const
{
return QSortFilterProxyModel::rowCount(parent);
}
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, &FilterTree::changed, this, &CardDatabaseDisplayModel::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(CardSet::TOKENS_SETNAME) && rowMatchesCardName(info);
}
int TokenEditModel::rowCount(const QModelIndex &parent) const
{
// always load all tokens at start
return QSortFilterProxyModel::rowCount(parent);
}

View file

@ -1,163 +0,0 @@
#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;
QSet<CardInfoPtr> cardListSet; // Supports faster lookups in cardDatabaseEnabledSetsChanged()
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;
emit modelDirty();
dirty();
}
void setCardName(const QString &_cardName)
{
if (filterString != nullptr) {
delete filterString;
filterString = nullptr;
}
cardName = sanitizeCardName(_cardName, characterTranslation);
emit modelDirty();
dirty();
}
void setStringFilter(const QString &_src)
{
delete filterString;
filterString = new FilterString(_src);
emit modelDirty();
dirty();
}
void setCardNameSet(const QSet<QString> &_cardNameSet)
{
cardNameSet = _cardNameSet;
emit modelDirty();
dirty();
}
void dirty()
{
dirtyTimer.start(20);
}
void clearFilterAll();
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
signals:
void modelDirty();
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;
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

@ -1,29 +0,0 @@
#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,
const CardSet::Priority priority)
{
if (sets.contains(setName)) {
return sets.value(setName);
}
CardSetPtr newSet = CardSet::newInstance(setName);
newSet->setLongName(longName);
newSet->setSetType(setType);
newSet->setReleaseDate(releaseDate);
newSet->setPriority(priority);
sets.insert(setName, newSet);
emit addSet(newSet);
return newSet;
}

View file

@ -1,45 +0,0 @@
#ifndef CARDDATABASE_PARSER_H
#define CARDDATABASE_PARSER_H
#include "../card_info.h"
#include <QIODevice>
#include <QString>
#define COCKATRICE_XML_XSI_NAMESPACE "http://www.w3.org/2001/XMLSchema-instance"
class ICardDatabaseParser : public QObject
{
Q_OBJECT
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(),
const CardSet::Priority priority = CardSet::PriorityFallback);
signals:
void addCard(CardInfoPtr card);
void addSet(CardSetPtr set);
};
Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser")
#endif

View file

@ -1,480 +0,0 @@
#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)
{
qCInfo(CockatriceXml3Log) << "Trying to parse: " << fileName;
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
qCInfo(CockatriceXml3Log) << "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 {
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong version" << version;
return false;
}
} else {
qCInfo(CockatriceXml3Log) << "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()) {
qCInfo(CockatriceXml3Log) << "Unknown item" << name << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
}
}
if (xml.hasError()) {
QString preamble = tr("Parse error at line %1 col %2:").arg(xml.lineNumber()).arg(xml.columnNumber());
qCWarning(CockatriceXml3Log).noquote() << preamble << xml.errorString();
}
}
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()) {
qCInfo(CockatriceXml3Log) << "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 = SetToPrintingsMap();
int tableRow = 0;
bool cipt = false;
bool landscapeOrientation = 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" || xmlName == "colors") {
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 == "landscapeOrientation") {
landscapeOrientation = (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);
PrintingInfo 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[setName].append(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()) {
qCInfo(CockatriceXml3Log) << "Unknown card property" << xmlName << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
if (name.isEmpty()) {
qCWarning(CockatriceXml3Log) << "Encountered card with empty name; skipping";
continue;
}
properties.insert("colors", colors);
CardInfoPtr newCard =
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
landscapeOrientation, tableRow, upsideDown);
emit addCard(newCard);
}
}
}
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
{
if (set.isNull()) {
qCWarning(CockatriceXml3Log) << "&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()) {
qCWarning(CockatriceXml3Log) << "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 SetToPrintingsMap setMap = info->getSets();
for (const auto &printings : setMap) {
for (const PrintingInfo &set : printings) {
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.getSet()->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->getLandscapeOrientation()) {
xml.writeTextElement("landscapeOrientation", "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

@ -1,32 +0,0 @@
#ifndef COCKATRICE_XML3_H
#define COCKATRICE_XML3_H
#include "card_database_parser.h"
#include <QLoggingCategory>
#include <QXmlStreamReader>
inline Q_LOGGING_CATEGORY(CockatriceXml3Log, "cockatrice_xml.xml_3_parser");
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);
};
#endif

View file

@ -1,440 +0,0 @@
#include "cockatrice_xml_4.h"
#include "../../../settings/cache_settings.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)
{
qCInfo(CockatriceXml4Log) << "Trying to parse: " << fileName;
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
qCInfo(CockatriceXml4Log) << "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 {
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong version" << version;
return false;
}
} else {
qCInfo(CockatriceXml4Log) << "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()) {
qCInfo(CockatriceXml4Log) << "Unknown item" << xmlName << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
}
}
if (xml.hasError()) {
QString preamble = tr("Parse error at line %1 col %2:").arg(xml.lineNumber()).arg(xml.columnNumber());
qCWarning(CockatriceXml4Log).noquote() << preamble << xml.errorString();
}
}
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;
short priority;
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 == "priority") {
priority = xml.readElementText(QXmlStreamReader::IncludeChildElements).toShort();
} else if (!xmlName.isEmpty()) {
qCInfo(CockatriceXml4Log) << "Unknown set property" << xmlName << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
internalAddSet(shortName, longName, setType, releaseDate, static_cast<CardSet::Priority>(priority));
}
}
}
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)
{
bool includeRebalancedCards = SettingsCache::instance().getIncludeRebalancedCards();
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 = SetToPrintingsMap();
int tableRow = 0;
bool cipt = false;
bool landscapeOrientation = 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 == "landscapeOrientation") {
landscapeOrientation = (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()) {
PrintingInfo printingInfo(set);
for (QXmlStreamAttribute attr : attrs) {
QString attrName = attr.name().toString();
if (attrName == "picURL")
attrName = "picurl";
printingInfo.setProperty(attrName, attr.value().toString());
}
// This is very much a hack and not the right place to
// put this check, as it requires a reload of Cockatrice
// to be apply.
//
// However, this is also true of the `set->getEnabled()`
// check above (which is currently bugged as well), so
// we'll fix both at the same time.
if (includeRebalancedCards || printingInfo.getProperty("isRebalanced") != "true") {
_sets[setName].append(printingInfo);
}
}
// 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()) {
qCInfo(CockatriceXml4Log) << "Unknown card property" << xmlName << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
if (name.isEmpty()) {
qCWarning(CockatriceXml4Log) << "Encountered card with empty name; skipping";
continue;
}
CardInfoPtr newCard =
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
landscapeOrientation, tableRow, upsideDown);
emit addCard(newCard);
}
}
}
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
{
if (set.isNull()) {
qCWarning(CockatriceXml4Log) << "&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.writeTextElement("priority", QString::number(set->getPriority()));
xml.writeEndElement();
return xml;
}
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info)
{
if (info.isNull()) {
qCWarning(CockatriceXml4Log) << "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 (const auto &printings : info->getSets()) {
for (const PrintingInfo &set : printings) {
xml.writeStartElement("set");
for (const QString &propName : set.getProperties()) {
xml.writeAttribute(propName, set.getProperty(propName));
}
xml.writeCharacters(set.getSet()->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->getLandscapeOrientation()) {
xml.writeTextElement("landscapeOrientation", "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

@ -1,32 +0,0 @@
#ifndef COCKATRICE_XML4_H
#define COCKATRICE_XML4_H
#include "card_database_parser.h"
#include <QLoggingCategory>
#include <QXmlStreamReader>
inline Q_LOGGING_CATEGORY(CockatriceXml4Log, "cockatrice_xml.xml_4_parser");
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);
};
#endif

View file

@ -1,426 +0,0 @@
#include "card_info.h"
#include "../../client/ui/picture_loader/picture_loader.h"
#include "../../settings/cache_settings.h"
#include "../game_specific_terms.h"
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QRegularExpression>
#include <algorithm>
#include <utility>
const char *CardSet::TOKENS_SETNAME = "TK";
CardSet::CardSet(const QString &_shortName,
const QString &_longName,
const QString &_setType,
const QDate &_releaseDate,
const CardSet::Priority _priority)
: shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType), priority(_priority)
{
loadSetOptions();
}
CardSetPtr CardSet::newInstance(const QString &_shortName,
const QString &_longName,
const QString &_setType,
const QDate &_releaseDate,
const Priority _priority)
{
CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate, _priority));
// 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()) {
qCWarning(CardInfoLog) << "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) {
qCWarning(CardInfoLog) << "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()
{
defaultSort();
for (int i = 0; i < size(); ++i) {
CardSetPtr set = at(i);
if (set.isNull()) {
qCWarning(CardInfoLog) << "guessSortKeys set is null";
continue;
}
set->setSortKey(i);
}
}
void SetList::defaultSort()
{
std::sort(begin(), end(), [](const CardSetPtr &a, const CardSetPtr &b) {
// Sort by priority, then by release date, then by short name
if (a->getPriority() != b->getPriority()) {
return a->getPriority() < b->getPriority(); // lowest first
} else if (a->getReleaseDate() != b->getReleaseDate()) {
return a->getReleaseDate() > b->getReleaseDate(); // most recent first
} else {
return a->getShortName() < b->getShortName(); // alphabetically
}
});
}
PrintingInfo::PrintingInfo(const CardSetPtr &_set) : set(_set)
{
}
/**
* Gets the uuid property of the printing, or an empty string if the property isn't present
*/
QString PrintingInfo::getUuid() const
{
return properties.value("uuid").toString();
}
CardInfo::CardInfo(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt)
: name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), cipt(_cipt),
landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt)
{
simpleName = CardInfo::simplifyName(name);
refreshCachedSetNames();
}
CardInfoPtr CardInfo::newInstance(const QString &_name)
{
return newInstance(_name, QString(), false, QVariantHash(), QList<CardRelation *>(), QList<CardRelation *>(),
SetToPrintingsMap(), false, false, 0, false);
}
CardInfoPtr CardInfo::newInstance(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt)
{
CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards,
_sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt));
ptr->setSmartPointer(ptr);
for (const auto &printings : _sets) {
for (const PrintingInfo &printing : printings) {
printing.getSet()->append(ptr);
break;
}
}
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 PrintingInfo _info)
{
if (!_set->contains(smartThis)) {
_set->append(smartThis);
}
if (!setsToPrintings[_set->getShortName()].contains(_info)) {
setsToPrintings[_set->getShortName()].append(_info);
}
refreshCachedSetNames();
}
void CardInfo::combineLegalities(const QVariantHash &props)
{
QHashIterator<QString, QVariant> it(props);
while (it.hasNext()) {
it.next();
if (it.key().startsWith("format-")) {
smartThis->setProperty(it.key(), it.value().toString());
}
}
}
void CardInfo::refreshCachedSetNames()
{
QStringList setList;
// update the cached list of set names
for (const auto &printings : setsToPrintings) {
for (const auto &printing : printings) {
if (printing.getSet()->getEnabled()) {
setList << printing.getSet()->getShortName();
}
break;
}
}
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');
}
}
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()
{
for (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

@ -1,470 +0,0 @@
#ifndef CARD_INFO_H
#define CARD_INFO_H
#include <QDate>
#include <QHash>
#include <QList>
#include <QLoggingCategory>
#include <QMap>
#include <QMetaType>
#include <QSharedPointer>
#include <QStringList>
#include <QVariant>
#include <utility>
inline Q_LOGGING_CATEGORY(CardInfoLog, "card_info");
class CardInfo;
class PrintingInfo;
class CardSet;
class CardRelation;
class ICardDatabaseParser;
typedef QSharedPointer<CardInfo> CardInfoPtr;
typedef QSharedPointer<CardSet> CardSetPtr;
typedef QMap<QString, QList<PrintingInfo>> SetToPrintingsMap;
typedef QHash<QString, CardInfoPtr> CardNameMap;
typedef QHash<QString, CardSetPtr> SetNameMap;
Q_DECLARE_METATYPE(CardInfoPtr)
class CardSet : public QList<CardInfoPtr>
{
public:
enum Priority
{
PriorityFallback = 0,
PriorityPrimary = 10,
PrioritySecondary = 20,
PriorityReprint = 30,
PriorityOther = 40,
PriorityLowest = 100,
};
static const char *TOKENS_SETNAME;
private:
QString shortName, longName;
unsigned int sortKey;
QDate releaseDate;
QString setType;
Priority priority;
bool enabled, isknown;
public:
explicit CardSet(const QString &_shortName = QString(),
const QString &_longName = QString(),
const QString &_setType = QString(),
const QDate &_releaseDate = QDate(),
const Priority _priority = PriorityFallback);
static CardSetPtr newInstance(const QString &_shortName = QString(),
const QString &_longName = QString(),
const QString &_setType = QString(),
const QDate &_releaseDate = QDate(),
const Priority _priority = PriorityFallback);
QString getCorrectedShortName() const;
QString getShortName() const
{
return shortName;
}
QString getLongName() const
{
return longName;
}
QString getSetType() const
{
return setType;
}
QDate getReleaseDate() const
{
return releaseDate;
}
Priority getPriority() const
{
return priority;
}
void setLongName(const QString &_longName)
{
longName = _longName;
}
void setSetType(const QString &_setType)
{
setType = _setType;
}
void setReleaseDate(const QDate &_releaseDate)
{
releaseDate = _releaseDate;
}
void setPriority(const Priority _priority)
{
priority = _priority;
}
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();
void defaultSort();
};
/**
* Info relating to a specific printing for a card.
*/
class PrintingInfo
{
public:
explicit PrintingInfo(const CardSetPtr &_set = nullptr);
~PrintingInfo() = default;
bool operator==(const PrintingInfo &other) const
{
return this->set == other.set && this->properties == other.properties;
}
private:
CardSetPtr set;
// per-printing card properties;
QVariantHash properties;
public:
CardSetPtr getSet() const
{
return set;
}
QStringList getProperties() const
{
return properties.keys();
}
QString getProperty(const QString &propertyName) const
{
return properties.value(propertyName).toString();
}
void setProperty(const QString &_name, const QString &_value)
{
properties.insert(_name, _value);
}
QString getUuid() const;
};
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;
// 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
SetToPrintingsMap setsToPrintings;
// cached set names
QString setsNames;
// positioning properties; used by UI
bool cipt;
bool landscapeOrientation;
int tableRow;
bool upsideDownArt;
public:
explicit CardInfo(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
CardInfo(const CardInfo &other)
: QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text),
isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
setsToPrintings(other.setsToPrintings), setsNames(other.setsNames), cipt(other.cipt),
landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt)
{
}
static CardInfoPtr newInstance(const QString &_name);
static CardInfoPtr newInstance(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
CardInfoPtr clone() const
{
// Use the copy constructor to create a new instance
CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this));
newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance
return newCardInfo;
}
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 &getText() const
{
return text;
}
void setText(const QString &_text)
{
text = _text;
emit cardInfoChanged(smartThis);
}
bool getIsToken() const
{
return isToken;
}
QStringList getProperties() const
{
return properties.keys();
}
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 SetToPrintingsMap &getSets() const
{
return setsToPrintings;
}
const QString &getSetsNames() const
{
return setsNames;
}
// related cards
const QList<CardRelation *> &getRelatedCards() const
{
return relatedCards;
}
const QList<CardRelation *> &getReverseRelatedCards() const
{
return reverseRelatedCards;
}
const QList<CardRelation *> &getReverseRelatedCards2Me() const
{
return reverseRelatedCardsToMe;
}
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;
}
bool getLandscapeOrientation() const
{
return landscapeOrientation;
}
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);
QString getCorrectedName() const;
void addToSet(const CardSetPtr &_set, PrintingInfo _info = PrintingInfo());
void combineLegalities(const QVariantHash &props);
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:
/**
* Emit this when a pixmap for this card finishes loading.
* @param printing The specific printing the pixmap is for.
*/
void pixmapUpdated(const PrintingInfo &printing);
void cardInfoChanged(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

@ -1,71 +0,0 @@
#include "card_search_model.h"
#include "../../utility/levenshtein.h"
#include <algorithm>
CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent)
: QAbstractListModel(parent), sourceModel(sourceModel)
{
}
int CardSearchModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return searchResults.size();
}
QVariant CardSearchModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= searchResults.size())
return QVariant();
if (role == Qt::DisplayRole) {
return searchResults.at(index.row()).card->getName();
}
return QVariant();
}
void CardSearchModel::updateSearchResults(const QString &query)
{
beginResetModel();
searchResults.clear();
if (query.isEmpty() || !sourceModel)
return;
// Set the filter for the display model
sourceModel->setCardName(query);
// Collect matching cards and compute Levenshtein distance
for (int i = 0; i < sourceModel->rowCount(); ++i) {
QModelIndex modelIndex = sourceModel->index(i, 0);
QModelIndex sourceIndex = sourceModel->mapToSource(modelIndex);
CardDatabaseModel *sourceDbModel = qobject_cast<CardDatabaseModel *>(sourceModel->sourceModel());
if (!sourceDbModel || !sourceIndex.isValid())
return;
CardInfoPtr card = sourceDbModel->getCard(sourceIndex.row());
if (!card)
continue;
int distance = levenshteinDistance(query.toLower(), card->getName().toLower());
searchResults.append({card, distance});
}
// Sort by Levenshtein distance (lower distance = better match)
std::sort(searchResults.begin(), searchResults.end(),
[](const SearchResult &a, const SearchResult &b) { return a.distance < b.distance; });
// Keep only the top 5 results
if (searchResults.size() > 10)
searchResults = searchResults.mid(0, 10);
emit dataChanged(index(0, 0), index(rowCount() - 1, 0));
emit layoutChanged();
endResetModel();
}

View file

@ -1,30 +0,0 @@
#ifndef CARD_SEARCH_MODEL_H
#define CARD_SEARCH_MODEL_H
#include "card_database_model.h"
#include <QAbstractListModel>
class CardSearchModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void updateSearchResults(const QString &query); // Update results based on input
private:
struct SearchResult
{
CardInfoPtr card;
int distance;
};
CardDatabaseDisplayModel *sourceModel;
QList<SearchResult> searchResults;
};
#endif // CARD_SEARCH_MODEL_H

View file

@ -1,79 +0,0 @@
#include "exact_card.h"
/**
* Default constructor.
* This will set the CardInfoPtr to null.
* The printing will be the default-constructed PrintingInfo.
*/
ExactCard::ExactCard()
{
}
/**
* @param _card The card. Can be null.
* @param _printing The printing. Can be empty.
*/
ExactCard::ExactCard(const CardInfoPtr &_card, const PrintingInfo &_printing) : card(_card), printing(_printing)
{
}
bool ExactCard::operator==(const ExactCard &other) const
{
return this->card == other.card && this->printing == other.printing;
}
/**
* Convenience method to safely get the card's name.
* @return The name in the CardInfo, or an empty string if card is null
*/
QString ExactCard::getName() const
{
return card.isNull() ? "" : card->getName();
}
/**
* Gets a view of the underlying cardInfoPtr.
* @return A const reference to the CardInfo, or an empty CardInfo if card is null
*/
const CardInfo &ExactCard::getInfo() const
{
if (card.isNull()) {
static CardInfoPtr emptyCard = CardInfo::newInstance("");
return *emptyCard;
}
return *card;
}
/**
* The key used to identify this exact printing in the cache
*/
QString ExactCard::getPixmapCacheKey() const
{
QString uuid = printing.getUuid();
QString suffix = uuid.isEmpty() ? "" : "_" + uuid;
return QLatin1String("card_") + card->getName() + suffix;
}
/**
* Checks if the card is null or empty.
*/
bool ExactCard::isEmpty() const
{
return card.isNull() || card->getName().isEmpty();
}
/**
* Returns true if isEmpty() is false
*/
ExactCard::operator bool() const
{
return !isEmpty();
}
/**
* Gets the CardInfo to emit the pixmapUpdated signal
*/
void ExactCard::emitPixmapUpdated() const
{
emit card->pixmapUpdated(printing);
}

View file

@ -1,46 +0,0 @@
#ifndef EXACT_CARD_H
#define EXACT_CARD_H
#include "card_info.h"
/**
* Identifies the card along with its exact printing
*/
class ExactCard
{
CardInfoPtr card;
PrintingInfo printing;
public:
ExactCard();
explicit ExactCard(const CardInfoPtr &_card, const PrintingInfo &_printing = PrintingInfo());
/**
* Gets the CardInfoPtr. Can be null.
*/
CardInfoPtr getCardPtr() const
{
return card;
}
/**
* Gets the PrintingInfo. Can be empty.
*/
PrintingInfo getPrinting() const
{
return printing;
}
bool operator==(const ExactCard &other) const;
QString getName() const;
const CardInfo &getInfo() const;
QString getPixmapCacheKey() const;
bool isEmpty() const;
explicit operator bool() const;
void emitPixmapUpdated() const;
};
#endif // EXACT_CARD_H

View file

@ -1,7 +1,7 @@
#include "deck_view.h"
#include "../../card/card_info.h"
#include "../../client/ui/theme_manager.h"
#include "../../game/cards/card_info.h"
#include "../../settings/cache_settings.h"
#include "deck_list.h"
#include "deck_list_card_node.h"

View file

@ -2,6 +2,8 @@
#include "../../client/tabs/tab_game.h"
#include "../../client/ui/picture_loader/picture_loader.h"
#include "../../database/card_database.h"
#include "../../database/card_database_manager.h"
#include "../../deck/deck_loader.h"
#include "../../dialogs/dlg_load_deck.h"
#include "../../dialogs/dlg_load_deck_from_clipboard.h"
@ -9,8 +11,6 @@
#include "../../dialogs/dlg_load_remote_deck.h"
#include "../../server/pending_command.h"
#include "../../settings/cache_settings.h"
#include "../cards/card_database.h"
#include "../cards/card_database_manager.h"
#include "../game_scene.h"
#include "deck_view.h"
#include "pb/command_deck_select.pb.h"

View file

@ -1,193 +0,0 @@
#include "deck_filter_string.h"
#include "../cards/card_database_manager.h"
#include "filter_string.h"
#include "lib/peglib.h"
static peg::parser search(R"(
Start <- QueryPartList
~ws <- [ ]+
QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws*
ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / GenericQuery
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
DeckContentQuery <- CardSearch NumericExpression?
CardSearch <- '[[' CardFilterString ']]'
CardFilterString <- (!']]'.)*
DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String
FileNameQuery <- [Ff] ('ile' 'name'?)? [:] String
PathQuery <- [Pp] 'ath'? [:] String
GenericQuery <- String
NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["].
NonSingleQuoteUnlessEscaped <- "\\\'". / !['].
UnescapedStringListPart <- !['":<>=! ].
SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)*
String <- SingleApostropheString / UnescapedStringListPart+ / ["] <NonDoubleQuoteUnlessEscaped*> ["] / ['] <NonSingleQuoteUnlessEscaped*> [']
NumericExpression <- NumericOperator ws? NumericValue
NumericOperator <- [=:] / <[><!][=]?>
NumericValue <- [0-9]+
)");
static std::once_flag init;
static void setupParserRules()
{
// plumbing
auto passthru = [](const peg::SemanticValues &sv) -> DeckFilter {
return !sv.empty() ? std::any_cast<DeckFilter>(sv[0]) : nullptr;
};
search["Start"] = passthru;
search["QueryPartList"] = [](const peg::SemanticValues &sv) -> DeckFilter {
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) {
auto matchesFilter = [&deck, &info](const std::any &query) {
return std::any_cast<DeckFilter>(query)(deck, info);
};
return std::all_of(sv.begin(), sv.end(), matchesFilter);
};
};
search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> DeckFilter {
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) {
auto matchesFilter = [&deck, &info](const std::any &query) {
return std::any_cast<DeckFilter>(query)(deck, info);
};
return std::any_of(sv.begin(), sv.end(), matchesFilter);
};
};
search["SomewhatComplexQueryPart"] = passthru;
search["QueryPart"] = passthru;
search["NotQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
const auto dependent = std::any_cast<DeckFilter>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) -> bool {
return !dependent(deck, info);
};
};
search["String"] = [](const peg::SemanticValues &sv) -> QString {
if (sv.choice() == 0) {
return QString::fromStdString(std::string(sv.sv()));
}
return QString::fromStdString(std::string(sv.token(0)));
};
search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher {
const auto arg = std::any_cast<int>(sv[1]);
const auto op = std::any_cast<QString>(sv[0]);
if (op == ">")
return [=](const int s) { return s > arg; };
if (op == ">=")
return [=](const int s) { return s >= arg; };
if (op == "<")
return [=](const int s) { return s < arg; };
if (op == "<=")
return [=](const int s) { return s <= arg; };
if (op == "=")
return [=](const int s) { return s == arg; };
if (op == ":")
return [=](const int s) { return s == arg; };
if (op == "!=")
return [=](const int s) { return s != arg; };
return [](int) { return false; };
};
search["NumericValue"] = [](const peg::SemanticValues &sv) -> int {
return QString::fromStdString(std::string(sv.sv())).toInt();
};
search["NumericOperator"] = [](const peg::SemanticValues &sv) -> QString {
return QString::fromStdString(std::string(sv.sv()));
};
// actual functionality
search["DeckContentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto cardFilter = FilterString(std::any_cast<QString>(sv[0]));
auto numberMatcher = sv.size() > 1 ? std::any_cast<NumberMatcher>(sv[1]) : [](int count) { return count > 0; };
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool {
int count = 0;
deck->deckLoader->forEachCard([&](InnerDecklistNode *, const DecklistCardNode *node) {
auto cardInfoPtr = CardDatabaseManager::getInstance()->getCardInfo(node->getName());
if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) {
count += node->getNumber();
}
});
return numberMatcher(count);
};
};
search["CardSearch"] = [](const peg::SemanticValues &sv) -> QString { return std::any_cast<QString>(sv[0]); };
search["CardFilterString"] = [](const peg::SemanticValues &sv) -> QString {
return QString::fromStdString(std::string(sv.sv()));
};
search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
return deck->deckLoader->getName().contains(name, Qt::CaseInsensitive);
};
};
search["FileNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
auto filename = QFileInfo(deck->filePath).fileName();
return filename.contains(name, Qt::CaseInsensitive);
};
};
search["PathQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *, const ExtraDeckSearchInfo &info) {
return info.relativeFilePath.contains(name, Qt::CaseInsensitive);
};
};
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast<QString>(sv[0]);
return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
return deck->getDisplayName().contains(name, Qt::CaseInsensitive);
};
};
}
DeckFilterString::DeckFilterString()
{
filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; };
_error = "Not initialized";
}
DeckFilterString::DeckFilterString(const QString &expr)
{
QByteArray ba = expr.simplified().toUtf8();
std::call_once(init, setupParserRules);
_error = QString();
if (ba.isEmpty()) {
filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return true; };
return;
}
search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) {
_error = QString("Error at position %1: %2").arg(col).arg(QString::fromStdString(msg));
});
if (!search.parse(ba.data(), filter)) {
qCInfo(DeckFilterStringLog).nospace() << "DeckFilterString error for " << expr << "; " << qPrintable(_error);
filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; };
}
}

View file

@ -1,51 +0,0 @@
#ifndef DECK_FILTER_STRING_H
#define DECK_FILTER_STRING_H
#include "../../client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h"
#include <QLoggingCategory>
#include <QMap>
#include <QString>
#include <functional>
#include <utility>
inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string");
/**
* Extra info relevant to filtering that isn't present in the DeckPreviewWidget
*/
struct ExtraDeckSearchInfo
{
/**
* The relative filepath starting from the deck folder
*/
QString relativeFilePath;
};
typedef std::function<bool(const DeckPreviewWidget *, const ExtraDeckSearchInfo &)> DeckFilter;
class DeckFilterString
{
public:
DeckFilterString();
explicit DeckFilterString(const QString &expr);
bool check(const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) const
{
return filter(deck, info);
}
bool valid() const
{
return _error.isEmpty();
}
QString error()
{
return _error;
}
private:
QString _error;
DeckFilter filter;
};
#endif // DECK_FILTER_STRING_H

View file

@ -1,78 +0,0 @@
#include "filter_builder.h"
#include "../../deck/custom_line_edit.h"
#include "filter_card.h"
#include <QComboBox>
#include <QGridLayout>
#include <QPushButton>
FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
{
filterCombo = new QComboBox;
filterCombo->setObjectName("filterCombo");
for (int i = 0; i < CardFilter::AttrEnd; i++)
filterCombo->addItem(CardFilter::attrName(static_cast<CardFilter::Attr>(i)), QVariant(i));
typeCombo = new QComboBox;
typeCombo->setObjectName("typeCombo");
for (int i = 0; i < CardFilter::TypeEnd; i++)
typeCombo->addItem(CardFilter::typeName(static_cast<CardFilter::Type>(i)), QVariant(i));
QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString());
ok->setObjectName("ok");
ok->setMaximumSize(20, 20);
edit = new LineEditUnfocusable;
edit->setObjectName("edit");
edit->setPlaceholderText(tr("Type your filter here"));
edit->setClearButtonEnabled(true);
edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QGridLayout *layout = new QGridLayout;
layout->setObjectName("layout");
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(typeCombo, 0, 0, 1, 2);
layout->addWidget(filterCombo, 0, 2, 1, 2);
layout->addWidget(edit, 1, 0, 1, 3);
layout->addWidget(ok, 1, 3);
setLayout(layout);
connect(filterCombo, qOverload<int>(&QComboBox::activated), edit, [this](int) { setFocus(); });
connect(edit, &LineEditUnfocusable::returnPressed, this, &FilterBuilder::emit_add);
connect(ok, &QPushButton::released, this, &FilterBuilder::emit_add);
fltr = NULL;
}
FilterBuilder::~FilterBuilder()
{
destroyFilter();
}
void FilterBuilder::destroyFilter()
{
if (fltr)
delete fltr;
}
static int comboCurrentIntData(const QComboBox *combo)
{
return combo->itemData(combo->currentIndex()).toInt();
}
void FilterBuilder::emit_add()
{
QString txt;
txt = edit->text();
if (txt.length() < 1)
return;
destroyFilter();
fltr = new CardFilter(txt, static_cast<CardFilter::Type>(comboCurrentIntData(typeCombo)),
static_cast<CardFilter::Attr>(comboCurrentIntData(filterCombo)));
emit add(fltr);
edit->clear();
}

View file

@ -1,37 +0,0 @@
#ifndef FILTERBUILDER_H
#define FILTERBUILDER_H
#include <QWidget>
class QCheckBox;
class QComboBox;
class LineEditUnfocusable;
class CardFilter;
class FilterBuilder : public QWidget
{
Q_OBJECT
private:
QComboBox *typeCombo;
QComboBox *filterCombo;
LineEditUnfocusable *edit;
CardFilter *fltr;
void destroyFilter();
public:
explicit FilterBuilder(QWidget *parent = nullptr);
~FilterBuilder() override;
signals:
void add(const CardFilter *f);
public slots:
private slots:
void emit_add();
protected:
};
#endif

View file

@ -1,92 +0,0 @@
#include "filter_card.h"
#include <QJsonObject>
QJsonObject CardFilter::toJson() const
{
QJsonObject obj;
obj["term"] = trm;
obj["type"] = typeName(t);
obj["attr"] = attrName(a);
return obj;
}
CardFilter *CardFilter::fromJson(const QJsonObject &obj)
{
QString term = obj["term"].toString();
QString typeStr = obj["type"].toString();
QString attrStr = obj["attr"].toString();
Type type = TypeEnd;
Attr attr = AttrEnd;
// Convert type string back to enum
for (int i = 0; i < TypeEnd; i++) {
if (typeName(static_cast<Type>(i)) == typeStr) {
type = static_cast<Type>(i);
break;
}
}
// Convert attr string back to enum
for (int i = 0; i < AttrEnd; i++) {
if (attrName(static_cast<Attr>(i)) == attrStr) {
attr = static_cast<Attr>(i);
break;
}
}
return new CardFilter(term, type, attr);
}
const QString CardFilter::typeName(Type t)
{
switch (t) {
case TypeAnd:
return tr("AND", "Logical conjunction operator used in card filter");
case TypeOr:
return tr("OR", "Logical disjunction operator used in card filter");
case TypeAndNot:
return tr("AND NOT", "Negated logical conjunction operator used in card filter");
case TypeOrNot:
return tr("OR NOT", "Negated logical disjunction operator used in card filter");
default:
return QString("");
}
}
const QString CardFilter::attrName(Attr a)
{
switch (a) {
case AttrName:
return tr("Name");
case AttrType:
return tr("Type");
case AttrColor:
return tr("Color");
case AttrText:
return tr("Text");
case AttrSet:
return tr("Set");
case AttrManaCost:
return tr("Mana Cost");
case AttrCmc:
return tr("Mana Value");
case AttrRarity:
return tr("Rarity");
case AttrPow:
return tr("Power");
case AttrTough:
return tr("Toughness");
case AttrLoyalty:
return tr("Loyalty");
case AttrFormat:
return tr("Format");
case AttrMainType:
return tr("Main Type");
case AttrSubType:
return tr("Sub Type");
default:
return QString("");
}
}

View file

@ -1,70 +0,0 @@
#ifndef CARDFILTER_H
#define CARDFILTER_H
#include <QObject>
#include <QString>
#include <utility>
class CardFilter : public QObject
{
Q_OBJECT
public:
enum Type
{
TypeAnd = 0,
TypeOr,
TypeAndNot,
TypeOrNot,
TypeEnd
};
/* if you add an attribute here you also need to
* add its string representation in attrName */
enum Attr
{
AttrCmc = 0,
AttrColor,
AttrLoyalty,
AttrManaCost,
AttrName,
AttrPow,
AttrRarity,
AttrSet,
AttrText,
AttrTough,
AttrType,
AttrMainType,
AttrSubType,
AttrFormat,
AttrEnd,
};
private:
QString trm;
enum Type t;
enum Attr a;
public:
CardFilter(QString &term, Type type, Attr attr) : trm(term), t(type), a(attr){};
Type type() const
{
return t;
}
const QString &term() const
{
return trm;
}
Attr attr() const
{
return a;
}
QJsonObject toJson() const;
static CardFilter *fromJson(const QJsonObject &json);
static const QString typeName(Type t);
static const QString attrName(Attr a);
};
#endif

View file

@ -1,413 +0,0 @@
#include "filter_string.h"
#include "../../../../common/lib/peglib.h"
#include <QByteArray>
#include <QDebug>
#include <QRegularExpression>
#include <QString>
#include <functional>
static peg::parser search(R"(
Start <- QueryPartList
~ws <- [ ]+
QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws*
ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart
SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart
QueryPart <- NotQuery / SetQuery / RarityQuery / CMCQuery / FormatQuery / PowerQuery / ToughnessQuery / ColorQuery / TypeQuery / OracleQuery / FieldQuery / GenericQuery
NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart
SetQuery <- ('e'/'set') [:] FlexStringValue
OracleQuery <- 'o' [:] MatcherString
CMCQuery <- ('cmc'/'mv') ws? NumericExpression
PowerQuery <- [Pp] 'ow' 'er'? ws? NumericExpression
ToughnessQuery <- [Tt] 'ou' 'ghness'? ws? NumericExpression
RarityQuery <- [rR] ':' Rarity
Rarity <- [Cc] 'ommon'? / [Uu] 'ncommon'? / [Rr] 'are'? / [Mm] 'ythic'? / [Ss] 'pecial'? / [a-zA-Z] [a-z]*
FormatQuery <- 'f' ':' Format / Legality ':' Format
Format <- [a-zA-Z] [a-z]*
Legality <- [Ll] 'egal'? / [Bb] 'anned'? / [Rr] 'estricted'
TypeQuery <- [tT] 'ype'? [:] StringValue
Color <- < [Ww] 'hite'? / [Uu] / [Bb] 'lack'? / [Rr] 'ed'? / [Gg] 'reen'? / [Bb] 'lue'? >
ColorEx <- Color / [mc]
ColorQuery <- [cC] 'olor'? <[iI]?> <[:!]> ColorEx*
FieldQuery <- String [:] MatcherString / String ws? NumericExpression
NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["].
NonSingleQuoteUnlessEscaped <- "\\\'". / !['].
UnescapedStringListPart <- !['":<>=! ].
SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)*
String <- SingleApostropheString / UnescapedStringListPart+ / ["] <NonDoubleQuoteUnlessEscaped*> ["] / ['] <NonSingleQuoteUnlessEscaped*> [']
StringValue <- String / [(] StringList [)]
StringList <- StringListString (ws? [,] ws? StringListString)*
StringListString <- UnescapedStringListPart+
GenericQuery <- MatcherString
# A String that can either be a normal string or a regex search string
MatcherString <- RegexMatcher / NormalMatcher
NormalMatcher <- String
RegexMatcher <- '/' RegexMatcherString '/'
RegexMatcherString <- ('\\/' / !'/' .)+
FlexStringValue <- CompactStringSet / String / [(] StringList [)]
CompactStringSet <- StringListString ([,+] StringListString)+
NumericExpression <- NumericOperator ws? NumericValue
NumericOperator <- [=:] / <[><!][=]?>
NumericValue <- [0-9]+
)");
static std::once_flag init;
static void setupParserRules()
{
auto passthru = [](const peg::SemanticValues &sv) -> Filter {
return !sv.empty() ? std::any_cast<Filter>(sv[0]) : nullptr;
};
search["Start"] = passthru;
search["QueryPartList"] = [](const peg::SemanticValues &sv) -> Filter {
return [=](const CardData &x) {
auto matchesFilter = [&x](const std::any &query) { return std::any_cast<Filter>(query)(x); };
return std::all_of(sv.begin(), sv.end(), matchesFilter);
};
};
search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> Filter {
return [=](const CardData &x) {
auto matchesFilter = [&x](const std::any &query) { return std::any_cast<Filter>(query)(x); };
return std::any_of(sv.begin(), sv.end(), matchesFilter);
};
};
search["SomewhatComplexQueryPart"] = passthru;
search["QueryPart"] = passthru;
search["NotQuery"] = [](const peg::SemanticValues &sv) -> Filter {
const auto dependent = std::any_cast<Filter>(sv[0]);
return [=](const CardData &x) -> bool { return !dependent(x); };
};
search["TypeQuery"] = [](const peg::SemanticValues &sv) -> Filter {
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
return [=](const CardData &x) -> bool { return matcher(x->getCardType()); };
};
search["SetQuery"] = [](const peg::SemanticValues &sv) -> Filter {
auto matcher = std::any_cast<StringMatcher>(sv[0]);
return [=](const CardData &x) -> bool {
QList<QString> sets = x->getSets().keys();
auto matchesSet = [&matcher](const QString &set) { return matcher(set); };
return std::any_of(sets.begin(), sets.end(), matchesSet);
};
};
search["Rarity"] = [](const peg::SemanticValues &sv) -> QString {
switch (tolower(std::string(sv.sv())[0])) {
case 'c':
return "common";
case 'u':
return "uncommon";
case 'r':
return "rare";
case 'm':
return "mythic";
case 's':
return "special";
default:
return QString::fromStdString(std::string(sv.sv()));
}
};
search["RarityQuery"] = [](const peg::SemanticValues &sv) -> Filter {
const auto rarity = std::any_cast<QString>(sv[0]);
return [=](const CardData &x) -> bool {
QList<PrintingInfo> infos;
for (const auto &printings : x->getSets()) {
for (const auto &printing : printings) {
infos.append(printing);
}
}
auto matchesRarity = [&rarity](const PrintingInfo &info) { return rarity == info.getProperty("rarity"); };
return std::any_of(infos.begin(), infos.end(), matchesRarity);
};
};
search["FormatQuery"] = [](const peg::SemanticValues &sv) -> Filter {
if (sv.choice() == 0) {
const auto format = std::any_cast<QString>(sv[0]);
return
[=](const CardData &x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == "legal"; };
}
const auto format = std::any_cast<QString>(sv[1]);
const auto legality = std::any_cast<QString>(sv[0]);
return [=](const CardData &x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == legality; };
};
search["Legality"] = [](const peg::SemanticValues &sv) -> QString {
switch (tolower(std::string(sv.sv())[0])) {
case 'l':
return "legal";
case 'b':
return "banned";
case 'r':
return "restricted";
default:
return "";
}
};
search["Format"] = [](const peg::SemanticValues &sv) -> QString {
if (sv.size() == 1) {
switch (tolower(std::string(sv.sv())[0])) {
case 'm':
return "modern";
case 's':
return "standard";
case 'v':
return "vintage";
case 'l':
return "legacy";
case 'c':
return "commander";
case 'p':
return "pioneer";
default:
return "";
}
}
return QString::fromStdString(std::string(sv.sv())).toLower();
};
search["StringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher {
// Helper function for word boundary matching
auto createWordBoundaryMatcher = [](const QString &target) {
QString pattern = QString("\\b%1\\b").arg(QRegularExpression::escape(target));
QRegularExpression regex(pattern, QRegularExpression::CaseInsensitiveOption);
return [regex](const QString &s) { return regex.match(s).hasMatch(); };
};
if (sv.choice() == 0) {
const auto target = std::any_cast<QString>(sv[0]);
return createWordBoundaryMatcher(target);
}
const auto target = std::any_cast<QStringList>(sv[0]);
return [=](const QString &s) {
auto containsString = [&s, &createWordBoundaryMatcher](const QString &str) {
return createWordBoundaryMatcher(str)(s);
};
return std::any_of(target.begin(), target.end(), containsString);
};
};
search["String"] = [](const peg::SemanticValues &sv) -> QString {
if (sv.choice() == 0) {
return QString::fromStdString(std::string(sv.sv()));
}
return QString::fromStdString(std::string(sv.token(0)));
};
search["FlexStringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher {
if (sv.choice() != 1) {
const auto target = std::any_cast<QStringList>(sv[0]);
return [=](const QString &s) {
auto containsString = [&s](const QString &str) {
return s.split(" ").contains(str, Qt::CaseInsensitive);
};
return std::any_of(target.begin(), target.end(), containsString);
};
}
const auto target = std::any_cast<QString>(sv[0]);
return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); };
};
search["CompactStringSet"] = [](const peg::SemanticValues &sv) -> QStringList {
QStringList result;
for (const auto &i : sv) {
result.append(std::any_cast<QString>(i));
}
return result;
};
search["StringList"] = [](const peg::SemanticValues &sv) -> QStringList {
QStringList result;
for (const auto &i : sv) {
result.append(std::any_cast<QString>(i));
}
return result;
};
search["StringListString"] = [](const peg::SemanticValues &sv) -> QString {
return QString::fromStdString(std::string(sv.sv()));
};
search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher {
const auto arg = std::any_cast<int>(sv[1]);
const auto op = std::any_cast<QString>(sv[0]);
if (op == ">")
return [=](const int s) { return s > arg; };
if (op == ">=")
return [=](const int s) { return s >= arg; };
if (op == "<")
return [=](const int s) { return s < arg; };
if (op == "<=")
return [=](const int s) { return s <= arg; };
if (op == "=")
return [=](const int s) { return s == arg; };
if (op == ":")
return [=](const int s) { return s == arg; };
if (op == "!=")
return [=](const int s) { return s != arg; };
return [](int) { return false; };
};
search["NumericValue"] = [](const peg::SemanticValues &sv) -> int {
return QString::fromStdString(std::string(sv.sv())).toInt();
};
search["NumericOperator"] = [](const peg::SemanticValues &sv) -> QString {
return QString::fromStdString(std::string(sv.sv()));
};
search["NormalMatcher"] = [](const peg::SemanticValues &sv) -> StringMatcher {
auto target = std::any_cast<QString>(sv[0]);
auto sanitizedTarget = QString(target);
sanitizedTarget.replace("\\\"", "\"");
sanitizedTarget.replace("\\'", "'");
return [=](const QString &s) { return s.contains(sanitizedTarget, Qt::CaseInsensitive); };
};
search["RegexMatcher"] = [](const peg::SemanticValues &sv) -> StringMatcher {
auto target = std::any_cast<QString>(sv[0]);
auto regex = QRegularExpression(target, QRegularExpression::CaseInsensitiveOption);
return [=](const QString &s) { return regex.match(s).hasMatch(); };
};
search["RegexMatcherString"] = [](const peg::SemanticValues &sv) -> QString {
return QString::fromStdString(sv.token_to_string());
};
search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter {
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
return [=](const CardData &x) { return matcher(x->getText()); };
};
search["ColorQuery"] = [](const peg::SemanticValues &sv) -> Filter {
QString parts;
for (const auto &i : sv) {
parts += std::any_cast<char>(i);
}
const bool identity = sv.tokens[0].empty() || sv.tokens[0][0] != 'i';
if (sv.tokens[1][0] == ':') {
return [=](const CardData &x) {
QString match = identity ? x->getColors() : x->getProperty("coloridentity");
if (parts.contains("m") && match.length() < 2) {
return false;
}
if (parts == "m") {
return true;
}
if (parts.contains("c") && match.length() == 0)
return true;
auto containsColor = [&parts](const QString &s) { return parts.contains(s); };
return std::any_of(match.begin(), match.end(), containsColor);
};
}
return [=](const CardData &x) {
QString match = identity ? x->getColors() : x->getProperty("colorIdentity");
if (parts.contains("m") && match.length() < 2) {
return false;
}
if (parts.contains("c") && match.length() != 0) {
return false;
}
for (const auto &part : parts) {
if (!match.contains(part)) {
return false;
}
}
auto containsColor = [&parts](const QString &s) { return parts.contains(s); };
return std::all_of(match.begin(), match.end(), containsColor);
};
};
search["CMCQuery"] = [](const peg::SemanticValues &sv) -> Filter {
const auto matcher = std::any_cast<NumberMatcher>(sv[0]);
return [=](const CardData &x) -> bool { return matcher(x->getProperty("cmc").toInt()); };
};
search["PowerQuery"] = [](const peg::SemanticValues &sv) -> Filter {
const auto matcher = std::any_cast<NumberMatcher>(sv[0]);
return [=](const CardData &x) -> bool { return matcher(x->getPowTough().split("/")[0].toInt()); };
};
search["ToughnessQuery"] = [](const peg::SemanticValues &sv) -> Filter {
const auto matcher = std::any_cast<NumberMatcher>(sv[0]);
return [=](const CardData &x) -> bool {
auto parts = x->getPowTough().split("/");
return matcher(parts.length() == 2 ? parts[1].toInt() : 0);
};
};
search["FieldQuery"] = [](const peg::SemanticValues &sv) -> Filter {
const auto field = std::any_cast<QString>(sv[0]);
if (sv.choice() == 0) {
const auto matcher = std::any_cast<StringMatcher>(sv[1]);
return [=](const CardData &x) -> bool { return x->hasProperty(field) && matcher(x->getProperty(field)); };
}
const auto matcher = std::any_cast<NumberMatcher>(sv[1]);
return
[=](const CardData &x) -> bool { return x->hasProperty(field) && matcher(x->getProperty(field).toInt()); };
};
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter {
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
return [=](const CardData &x) { return matcher(x->getName()); };
};
search["Color"] = [](const peg::SemanticValues &sv) -> char { return "WUBRGU"[sv.choice()]; };
search["ColorEx"] = [](const peg::SemanticValues &sv) -> char {
return sv.choice() == 0 ? std::any_cast<char>(sv[0]) : *std::string(sv.sv()).c_str();
};
}
FilterString::FilterString()
{
result = [](const CardData &) -> bool { return false; };
_error = "Not initialized";
}
FilterString::FilterString(const QString &expr)
{
QByteArray ba = expr.simplified().toUtf8();
std::call_once(init, setupParserRules);
_error = QString();
if (ba.isEmpty()) {
result = [](const CardData &) -> bool { return true; };
return;
}
search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) {
_error = QString("Error at position %1: %2").arg(col).arg(QString::fromStdString(msg));
});
if (!search.parse(ba.data(), result)) {
qCInfo(FilterStringLog).nospace() << "FilterString error for " << expr << "; " << qPrintable(_error);
result = [](const CardData &) -> bool { return false; };
}
}

View file

@ -1,56 +0,0 @@
#ifndef FILTER_STRING_H
#define FILTER_STRING_H
#include "../cards/card_info.h"
#include "filter_tree.h"
#include <QLoggingCategory>
#include <QMap>
#include <QString>
#include <functional>
#include <utility>
inline Q_LOGGING_CATEGORY(FilterStringLog, "filter_string");
typedef CardInfoPtr CardData;
typedef std::function<bool(const CardData &)> Filter;
typedef std::function<bool(const QString &)> StringMatcher;
typedef std::function<bool(int)> NumberMatcher;
namespace peg
{
template <typename Annotation> struct AstBase;
struct EmptyType;
typedef AstBase<EmptyType> Ast;
} // namespace peg
class FilterString
{
public:
FilterString();
explicit FilterString(const QString &exp);
bool check(const CardData &card) const
{
if (card.isNull()) {
static CardInfoPtr blankCard = CardInfo::newInstance("");
return result(blankCard);
}
return result(card);
}
bool valid()
{
return _error.isEmpty();
}
QString error()
{
return _error;
}
private:
QString _error;
Filter result;
};
#endif

View file

@ -1,565 +0,0 @@
#include "filter_tree.h"
#include "filter_card.h"
#include <QList>
template <class T> FilterTreeNode *FilterTreeBranch<T>::nodeAt(int i) const
{
return (childNodes.size() > i) ? childNodes.at(i) : nullptr;
}
template <class T> void FilterTreeBranch<T>::deleteAt(int i)
{
preRemoveChild(this, i);
delete childNodes.takeAt(i);
postRemoveChild(this, i);
nodeChanged();
}
template <class T> int FilterTreeBranch<T>::childIndex(const FilterTreeNode *node) const
{
auto *unconst = const_cast<FilterTreeNode *>(node);
auto downcasted = dynamic_cast<T>(unconst);
return (downcasted) ? childNodes.indexOf(downcasted) : -1;
}
template <class T> FilterTreeBranch<T>::~FilterTreeBranch()
{
while (!childNodes.isEmpty()) {
delete childNodes.takeFirst();
}
}
const FilterItemList *LogicMap::findTypeList(CardFilter::Type type) const
{
QList<FilterItemList *>::const_iterator i;
for (i = childNodes.constBegin(); i != childNodes.constEnd(); ++i) {
if ((*i)->type == type) {
return *i;
}
}
return nullptr;
}
FilterItemList *LogicMap::typeList(CardFilter::Type type)
{
QList<FilterItemList *>::iterator i;
int count = 0;
for (i = childNodes.begin(); i != childNodes.end(); ++i) {
if ((*i)->type == type) {
break;
}
count++;
}
if (i == childNodes.end()) {
preInsertChild(this, count);
i = childNodes.insert(i, new FilterItemList(type, this));
postInsertChild(this, count);
nodeChanged();
}
return *i;
}
FilterTreeNode *LogicMap::parent() const
{
return p;
}
int FilterItemList::termIndex(const QString &term) const
{
for (int i = 0; i < childNodes.count(); i++) {
if ((childNodes.at(i))->term == term) {
return i;
}
}
return -1;
}
FilterTreeNode *FilterItemList::termNode(const QString &term)
{
int i = termIndex(term);
if (i < 0) {
FilterItem *fi = new FilterItem(term, this);
int count = childNodes.count();
preInsertChild(this, count);
childNodes.append(fi);
postInsertChild(this, count);
nodeChanged();
return fi;
}
return childNodes.at(i);
}
bool FilterItemList::testTypeAnd(const CardInfoPtr info, CardFilter::Attr attr) const
{
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
if (!(*i)->isEnabled()) {
continue;
}
if (!(*i)->acceptCardAttr(info, attr)) {
return false;
}
}
return true;
}
bool FilterItemList::testTypeAndNot(const CardInfoPtr info, CardFilter::Attr attr) const
{
// if any one in the list is true, return false
return !testTypeOr(info, attr);
}
bool FilterItemList::testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) const
{
bool noChildEnabledChild = true;
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
if (!(*i)->isEnabled()) {
continue;
}
if (noChildEnabledChild) {
noChildEnabledChild = false;
}
if ((*i)->acceptCardAttr(info, attr)) {
return true;
}
}
return noChildEnabledChild;
}
bool FilterItemList::testTypeOrNot(const CardInfoPtr info, CardFilter::Attr attr) const
{
// if any one in the list is false, return true
return !testTypeAnd(info, attr);
}
bool FilterItem::acceptName(const CardInfoPtr info) const
{
return info->getName().contains(term, Qt::CaseInsensitive);
}
bool FilterItem::acceptType(const CardInfoPtr info) const
{
return info->getCardType().contains(term, Qt::CaseInsensitive);
}
bool FilterItem::acceptMainType(const CardInfoPtr info) const
{
const QStringList typeParts = info->getCardType().split("");
return !typeParts.isEmpty() && typeParts[0].contains(term, Qt::CaseInsensitive);
}
bool FilterItem::acceptSubType(const CardInfoPtr info) const
{
const QStringList typeParts = info->getCardType().split("");
return typeParts.size() > 1 && typeParts[1].contains(term, Qt::CaseInsensitive);
}
bool FilterItem::acceptColor(const CardInfoPtr info) const
{
QString converted_term = term.trimmed();
converted_term.replace("green", "g", Qt::CaseInsensitive);
converted_term.replace("grn", "g", Qt::CaseInsensitive);
converted_term.replace("blue", "u", Qt::CaseInsensitive);
converted_term.replace("blu", "u", Qt::CaseInsensitive);
converted_term.replace("black", "b", Qt::CaseInsensitive);
converted_term.replace("blk", "b", Qt::CaseInsensitive);
converted_term.replace("red", "r", Qt::CaseInsensitive);
converted_term.replace("white", "w", Qt::CaseInsensitive);
converted_term.replace("wht", "w", Qt::CaseInsensitive);
converted_term.replace("colorless", "c", Qt::CaseInsensitive);
converted_term.replace("colourless", "c", Qt::CaseInsensitive);
converted_term.replace("none", "c", Qt::CaseInsensitive);
converted_term.replace(QString(" "), QString(""), Qt::CaseInsensitive);
// Colorless card filter
if (converted_term.toLower() == "c" && info->getColors().length() < 1) {
return true;
}
/*
* This is a tricky part, if the filter has multiple colors in it, like UGW,
* then we should match all of them to the card's colors
*/
int match_count = 0;
for (auto &it : converted_term) {
if (info->getColors().contains(it, Qt::CaseInsensitive))
match_count++;
}
return match_count == converted_term.length();
}
bool FilterItem::acceptText(const CardInfoPtr info) const
{
return info->getText().contains(term, Qt::CaseInsensitive);
}
bool FilterItem::acceptSet(const CardInfoPtr info) const
{
bool status = false;
for (const auto &printings : info->getSets()) {
for (const auto &set : printings) {
if (set.getSet()->getShortName().compare(term, Qt::CaseInsensitive) == 0 ||
set.getSet()->getLongName().compare(term, Qt::CaseInsensitive) == 0) {
status = true;
break;
}
}
}
return status;
}
bool FilterItem::acceptManaCost(const CardInfoPtr info) const
{
QString partialCost = term.toUpper();
// Sort the mana cost so it will be easy to find
std::sort(partialCost.begin(), partialCost.end());
// Try to seperate the mana cost in case it's a split card
// if it's not a split card the loop will run only once
for (QString fullManaCost : info->getManaCost().split("//")) {
std::sort(fullManaCost.begin(), fullManaCost.end());
// If the partial is found in the full, return true
if (fullManaCost.contains(partialCost)) {
return true;
}
}
return false;
}
bool FilterItem::acceptCmc(const CardInfoPtr info) const
{
bool convertSuccess;
int cmcInt = info->getCmc().toInt(&convertSuccess);
// if conversion failed, check for the "//" separator used in split cards
if (!convertSuccess) {
int cmcSum = 0;
for (const QString &cmc : info->getCmc().split("//")) {
cmcInt = cmc.toInt();
cmcSum += cmcInt;
if (relationCheck(cmcInt)) {
return true;
}
}
return relationCheck(cmcSum);
} else {
return relationCheck(cmcInt);
}
}
bool FilterItem::acceptFormat(const CardInfoPtr info) const
{
return info->getProperty(QString("format-%1").arg(term.toLower())) == "legal";
}
bool FilterItem::acceptLoyalty(const CardInfoPtr info) const
{
if (info->getLoyalty().isEmpty()) {
return false;
} else {
bool success;
// if loyalty can't be converted to "int" it must be "X"
int loyalty = info->getLoyalty().toInt(&success);
if (success) {
return relationCheck(loyalty);
} else {
return term.trimmed().toUpper() == info->getLoyalty();
}
}
}
bool FilterItem::acceptPowerToughness(const CardInfoPtr info, CardFilter::Attr attr) const
{
int slash = info->getPowTough().indexOf("/");
if (slash == -1) {
return false;
}
QString valueString;
if (attr == CardFilter::AttrPow) {
valueString = info->getPowTough().mid(0, slash);
} else {
valueString = info->getPowTough().mid(slash + 1);
}
if (term == valueString) {
return true;
}
// advanced filtering should only happen after fast string comparison failed
bool conversion;
int value = valueString.toInt(&conversion);
return conversion ? relationCheck(value) : false;
}
bool FilterItem::acceptRarity(const CardInfoPtr info) const
{
QString converted_term = term.trimmed();
/*
* The purpose of this loop is to only apply one of the replacement
* policies and then escape. If we attempt to layer them on top of
* each other, we will get awkward results (i.e. comythic rare mythic rareon)
* Conditional statement will exit once a case is successful in
* replacement OR we go through all possible cases.
* Will also need to replace just "mythic"
*/
for (int i = 0; converted_term.length() <= 3 && i <= 6; i++) {
switch (i) {
case 0:
converted_term.replace("mr", "mythic", Qt::CaseInsensitive);
break;
case 1:
converted_term.replace("m r", "mythic", Qt::CaseInsensitive);
break;
case 2:
converted_term.replace("m", "mythic", Qt::CaseInsensitive);
break;
case 3:
converted_term.replace("c", "common", Qt::CaseInsensitive);
break;
case 4:
converted_term.replace("u", "uncommon", Qt::CaseInsensitive);
break;
case 5:
converted_term.replace("r", "rare", Qt::CaseInsensitive);
break;
case 6:
converted_term.replace("s", "special", Qt::CaseInsensitive);
break;
default:
break;
}
}
for (const auto &printings : info->getSets()) {
for (const auto &printing : printings) {
if (printing.getProperty("rarity").compare(converted_term, Qt::CaseInsensitive) == 0) {
return true;
}
}
}
return false;
}
bool FilterItem::relationCheck(int cardInfo) const
{
bool result, conversion;
// if int conversion fails, there's probably an operator at the start
result = (cardInfo == term.toInt(&conversion));
if (!conversion) {
// leading whitespaces could cause indexing to fail
QString trimmedTerm = term.trimmed();
// check whether it's a 2 char operator (<=, >=, or ==)
if (trimmedTerm[1] == '=') {
int termInt = trimmedTerm.mid(2).toInt();
if (trimmedTerm.startsWith('<')) {
result = (cardInfo <= termInt);
} else if (trimmedTerm.startsWith('>')) {
result = (cardInfo >= termInt);
} else {
result = (cardInfo == termInt);
}
} else {
int termInt = trimmedTerm.mid(1).toInt();
if (trimmedTerm.startsWith('<')) {
result = (cardInfo < termInt);
} else if (trimmedTerm.startsWith('>')) {
result = (cardInfo > termInt);
} else if (trimmedTerm.startsWith("=")) {
result = (cardInfo == termInt);
} else {
// the int conversion hasn't failed due to an operator at the start
result = false;
}
}
}
return result;
}
bool FilterItem::acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) const
{
switch (attr) {
case CardFilter::AttrName:
return acceptName(info);
case CardFilter::AttrType:
return acceptType(info);
case CardFilter::AttrColor:
return acceptColor(info);
case CardFilter::AttrText:
return acceptText(info);
case CardFilter::AttrSet:
return acceptSet(info);
case CardFilter::AttrManaCost:
return acceptManaCost(info);
case CardFilter::AttrCmc:
return acceptCmc(info);
case CardFilter::AttrRarity:
return acceptRarity(info);
case CardFilter::AttrPow:
case CardFilter::AttrTough:
// intentional fallthrough
return acceptPowerToughness(info, attr);
case CardFilter::AttrLoyalty:
return acceptLoyalty(info);
case CardFilter::AttrFormat:
return acceptFormat(info);
case CardFilter::AttrMainType:
return acceptMainType(info);
case CardFilter::AttrSubType:
return acceptSubType(info);
default:
return true; /* ignore this attribute */
}
}
/*
* Need to define these here to make QT happy, otherwise
* moc doesnt find some of the FilterTreeBranch symbols.
*/
FilterTree::FilterTree() = default;
FilterTree::~FilterTree() = default;
LogicMap *FilterTree::attrLogicMap(CardFilter::Attr attr)
{
QList<LogicMap *>::iterator i;
int count = 0;
for (i = childNodes.begin(); i != childNodes.end(); i++) {
if ((*i)->attr == attr) {
break;
}
count++;
}
if (i == childNodes.end()) {
preInsertChild(this, count);
i = childNodes.insert(i, new LogicMap(attr, this));
postInsertChild(this, count);
nodeChanged();
}
return *i;
}
FilterItemList *FilterTree::attrTypeList(CardFilter::Attr attr, CardFilter::Type type)
{
return attrLogicMap(attr)->typeList(type);
}
FilterTreeNode *FilterTree::termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term)
{
return attrTypeList(attr, type)->termNode(term);
}
FilterTreeNode *FilterTree::termNode(const CardFilter *f)
{
return termNode(f->attr(), f->type(), f->term());
}
bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm) const
{
const FilterItemList *fil;
bool status = true;
fil = lm->findTypeList(CardFilter::TypeAnd);
if (fil && fil->isEnabled() && !fil->testTypeAnd(info, lm->attr)) {
return false;
}
fil = lm->findTypeList(CardFilter::TypeAndNot);
if (fil && fil->isEnabled() && !fil->testTypeAndNot(info, lm->attr)) {
return false;
}
fil = lm->findTypeList(CardFilter::TypeOr);
if (fil && fil->isEnabled()) {
status = false;
// if this is true we can return because it is OR'd with the OrNot list
if (fil->testTypeOr(info, lm->attr)) {
return true;
}
}
fil = lm->findTypeList(CardFilter::TypeOrNot);
if (fil && fil->isEnabled() && fil->testTypeOrNot(info, lm->attr)) {
return true;
}
return status;
}
bool FilterTree::acceptsCard(const CardInfoPtr info) const
{
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
if ((*i)->isEnabled() && !testAttr(info, *i)) {
return false;
}
}
return true;
}
void FilterTree::removeFiltersByAttr(CardFilter::Attr filterType)
{
for (int i = childNodes.size() - 1; i >= 0; --i) {
auto *child = dynamic_cast<LogicMap *>(childNodes.at(i));
if (child && child->attr == filterType) {
deleteAt(i);
}
}
}
void FilterTree::removeFilter(const CardFilter *toRemove)
{
for (int i = childNodes.size() - 1; i >= 0; --i) {
auto *logicMap = dynamic_cast<LogicMap *>(childNodes.at(i));
if (!logicMap || logicMap->attr != toRemove->attr())
continue;
FilterItemList *typeList = logicMap->typeList(toRemove->type());
if (!typeList)
continue;
int termIdx = typeList->termIndex(toRemove->term());
if (termIdx != -1) {
typeList->deleteAt(termIdx);
emit typeList->nodeChanged();
if (typeList->childCount() == 0) {
int logicIndex = logicMap->childIndex(typeList);
if (logicIndex != -1) {
logicMap->deleteAt(logicIndex);
}
}
}
}
}
void FilterTree::clear()
{
while (childCount() > 0) {
deleteAt(0);
}
emit changed();
}

View file

@ -1,277 +0,0 @@
#ifndef FILTERTREE_H
#define FILTERTREE_H
#include "../cards/card_database.h"
#include "filter_card.h"
#include <QList>
#include <QMap>
#include <QObject>
#include <utility>
class FilterTreeNode
{
private:
bool enabled;
public:
FilterTreeNode() : enabled(true)
{
}
virtual bool isEnabled() const
{
return enabled;
}
virtual void enable()
{
enabled = true;
nodeChanged();
}
virtual void disable()
{
enabled = false;
nodeChanged();
}
virtual FilterTreeNode *parent() const
{
return nullptr;
}
virtual FilterTreeNode *nodeAt(int /* i */) const
{
return nullptr;
}
virtual void deleteAt(int /* i */)
{
}
virtual int childCount() const
{
return 0;
}
virtual int childIndex(const FilterTreeNode * /* node */) const
{
return -1;
}
virtual int index() const
{
return (parent() != nullptr) ? parent()->childIndex(this) : -1;
}
virtual const QString text() const
{
return QString("");
}
virtual bool isLeaf() const
{
return false;
}
virtual void nodeChanged() const
{
if (parent() != nullptr)
parent()->nodeChanged();
}
virtual void preInsertChild(const FilterTreeNode *p, int i) const
{
if (parent() != nullptr)
parent()->preInsertChild(p, i);
}
virtual void postInsertChild(const FilterTreeNode *p, int i) const
{
if (parent() != nullptr)
parent()->postInsertChild(p, i);
}
virtual void preRemoveChild(const FilterTreeNode *p, int i) const
{
if (parent() != nullptr)
parent()->preRemoveChild(p, i);
}
virtual void postRemoveChild(const FilterTreeNode *p, int i) const
{
if (parent() != nullptr)
parent()->postRemoveChild(p, i);
}
};
template <class T> class FilterTreeBranch : public FilterTreeNode
{
protected:
QList<T> childNodes;
public:
virtual ~FilterTreeBranch();
void removeFiltersByAttr(CardFilter::Attr filterType);
FilterTreeNode *nodeAt(int i) const override;
void deleteAt(int i) override;
int childCount() const override
{
return childNodes.size();
}
int childIndex(const FilterTreeNode *node) const override;
};
class FilterItemList;
class FilterTree;
class LogicMap : public FilterTreeBranch<FilterItemList *>
{
private:
FilterTree *const p;
public:
const CardFilter::Attr attr;
LogicMap(CardFilter::Attr a, FilterTree *parent) : p(parent), attr(a)
{
}
const FilterItemList *findTypeList(CardFilter::Type type) const;
FilterItemList *typeList(CardFilter::Type type);
FilterTreeNode *parent() const override;
const QString text() const override
{
return CardFilter::attrName(attr);
}
};
class FilterItem;
class FilterItemList : public FilterTreeBranch<FilterItem *>
{
private:
LogicMap *const p;
public:
const CardFilter::Type type;
FilterItemList(CardFilter::Type t, LogicMap *parent) : p(parent), type(t)
{
}
CardFilter::Attr attr() const
{
return p->attr;
}
FilterTreeNode *parent() const override
{
return p;
}
int termIndex(const QString &term) const;
FilterTreeNode *termNode(const QString &term);
const QString text() const override
{
return CardFilter::typeName(type);
}
bool testTypeAnd(CardInfoPtr info, CardFilter::Attr attr) const;
bool testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr) const;
bool testTypeOr(CardInfoPtr info, CardFilter::Attr attr) const;
bool testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr) const;
};
class FilterItem : public FilterTreeNode
{
private:
FilterItemList *const p;
public:
const QString term;
FilterItem(QString trm, FilterItemList *parent) : p(parent), term(std::move(trm))
{
}
virtual ~FilterItem() = default;
CardFilter::Attr attr() const
{
return p->attr();
}
CardFilter::Type type() const
{
return p->type;
}
FilterTreeNode *parent() const override
{
return p;
}
const QString text() const override
{
return term;
}
bool isLeaf() const override
{
return true;
}
bool acceptName(CardInfoPtr info) const;
bool acceptType(CardInfoPtr info) const;
bool acceptMainType(CardInfoPtr info) const;
bool acceptSubType(CardInfoPtr info) const;
bool acceptColor(CardInfoPtr info) const;
bool acceptText(CardInfoPtr info) const;
bool acceptSet(CardInfoPtr info) const;
bool acceptManaCost(CardInfoPtr info) const;
bool acceptCmc(CardInfoPtr info) const;
bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const;
bool acceptLoyalty(CardInfoPtr info) const;
bool acceptRarity(CardInfoPtr info) const;
bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const;
bool acceptFormat(CardInfoPtr info) const;
bool relationCheck(int cardInfo) const;
};
class FilterTree : public QObject, public FilterTreeBranch<LogicMap *>
{
Q_OBJECT
signals:
void preInsertRow(const FilterTreeNode *parent, int i) const;
void postInsertRow(const FilterTreeNode *parent, int i) const;
void preRemoveRow(const FilterTreeNode *parent, int i) const;
void postRemoveRow(const FilterTreeNode *parent, int i) const;
void changed() const;
private:
LogicMap *attrLogicMap(CardFilter::Attr attr);
FilterItemList *attrTypeList(CardFilter::Attr attr, CardFilter::Type type);
bool testAttr(CardInfoPtr info, const LogicMap *lm) const;
void nodeChanged() const override
{
emit changed();
}
void preInsertChild(const FilterTreeNode *p, int i) const override
{
emit preInsertRow(p, i);
}
void postInsertChild(const FilterTreeNode *p, int i) const override
{
emit postInsertRow(p, i);
}
void preRemoveChild(const FilterTreeNode *p, int i) const override
{
emit preRemoveRow(p, i);
}
void postRemoveChild(const FilterTreeNode *p, int i) const override
{
emit postRemoveRow(p, i);
}
public:
FilterTree();
~FilterTree() override;
FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term);
FilterTreeNode *termNode(const CardFilter *f);
const QString text() const override
{
return QString("root");
}
int index() const override
{
return 0;
}
bool acceptsCard(CardInfoPtr info) const;
void removeFiltersByAttr(CardFilter::Attr filterType);
void removeFilter(const CardFilter *toRemove);
void clear();
};
#endif

View file

@ -1,329 +0,0 @@
#include "filter_tree_model.h"
#include "filter_card.h"
#include "filter_tree.h"
#include <QFont>
FilterTreeModel::FilterTreeModel(QObject *parent) : QAbstractItemModel(parent)
{
fTree = new FilterTree;
connect(fTree, &FilterTree::preInsertRow, this, &FilterTreeModel::proxyBeginInsertRow);
connect(fTree, &FilterTree::postInsertRow, this, &FilterTreeModel::proxyEndInsertRow);
connect(fTree, &FilterTree::preRemoveRow, this, &FilterTreeModel::proxyBeginRemoveRow);
connect(fTree, &FilterTree::postRemoveRow, this, &FilterTreeModel::proxyEndRemoveRow);
}
FilterTreeModel::~FilterTreeModel()
{
delete fTree;
}
void FilterTreeModel::proxyBeginInsertRow(const FilterTreeNode *node, int i)
{
int idx;
idx = node->index();
if (idx >= 0)
beginInsertRows(createIndex(idx, 0, (void *)node), i, i);
}
void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int)
{
int idx;
idx = node->index();
if (idx >= 0)
endInsertRows();
}
void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i)
{
int idx;
idx = node->index();
if (idx >= 0)
beginRemoveRows(createIndex(idx, 0, (void *)node), i, i);
}
void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int)
{
int idx;
idx = node->index();
if (idx >= 0)
endRemoveRows();
}
FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const
{
void *ip;
FilterTreeNode *node;
if (!idx.isValid())
return fTree;
ip = idx.internalPointer();
if (ip == NULL)
return fTree;
node = static_cast<FilterTreeNode *>(ip);
return node;
}
void FilterTreeModel::addFilter(const CardFilter *f)
{
emit layoutAboutToBeChanged();
fTree->termNode(f);
emit layoutChanged();
}
void FilterTreeModel::removeFilter(const CardFilter *f)
{
emit layoutAboutToBeChanged();
fTree->removeFilter(f);
emit layoutChanged();
}
void FilterTreeModel::clearFiltersOfType(CardFilter::Attr filterType)
{
emit layoutAboutToBeChanged();
// Recursively remove all nodes with the given filter type
fTree->removeFiltersByAttr(filterType);
emit layoutChanged();
}
QList<const CardFilter *> FilterTreeModel::getFiltersOfType(CardFilter::Attr filterType) const
{
QList<const CardFilter *> filters;
if (!fTree) {
return filters;
}
std::function<void(const FilterTreeNode *)> traverse;
traverse = [&](const FilterTreeNode *node) {
if (const auto *filterItem = dynamic_cast<const FilterItem *>(node)) {
if (filterItem->attr() == filterType) {
QString text = filterItem->text();
filters.append(new CardFilter(text, filterItem->type(), filterItem->attr()));
}
}
for (int i = 0; i < node->childCount(); ++i) {
traverse(node->nodeAt(i));
}
};
traverse(fTree);
return filters;
}
QList<const CardFilter *> FilterTreeModel::allFilters() const
{
QList<const CardFilter *> filters;
if (!fTree) {
return filters;
}
std::function<void(const FilterTreeNode *)> traverse;
traverse = [&](const FilterTreeNode *node) {
if (const auto *filterItem = dynamic_cast<const FilterItem *>(node)) {
QString text = filterItem->text();
filters.append(new CardFilter(text, filterItem->type(), filterItem->attr()));
}
for (int i = 0; i < node->childCount(); ++i) {
traverse(node->nodeAt(i));
}
};
traverse(fTree);
return filters;
}
int FilterTreeModel::rowCount(const QModelIndex &parent) const
{
const FilterTreeNode *node;
int result;
if (parent.column() > 0)
return 0;
node = indexToNode(parent);
if (node)
result = node->childCount();
else
result = 0;
return result;
}
int FilterTreeModel::columnCount(const QModelIndex & /*parent*/) const
{
return 1;
}
QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
{
const FilterTreeNode *node;
if (!index.isValid())
return QVariant();
if (index.column() >= columnCount())
return QVariant();
node = indexToNode(index);
if (node == NULL)
return QVariant();
switch (role) {
case Qt::FontRole:
if (!node->isLeaf()) {
QFont f;
f.setBold(true);
return f;
}
break;
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole:
case Qt::StatusTipRole:
case Qt::WhatsThisRole:
return node->text();
case Qt::CheckStateRole:
if (node->isEnabled())
return Qt::Checked;
else
return Qt::Unchecked;
default:
return QVariant();
}
return QVariant();
}
bool FilterTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
FilterTreeNode *node;
if (!index.isValid())
return false;
if (index.column() >= columnCount())
return false;
if (role != Qt::CheckStateRole)
return false;
node = indexToNode(index);
if (node == NULL || node == fTree)
return false;
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
if (state == Qt::Checked)
node->enable();
else
node->disable();
emit dataChanged(index, index);
return true;
}
Qt::ItemFlags FilterTreeModel::flags(const QModelIndex &index) const
{
const FilterTreeNode *node;
Qt::ItemFlags result;
if (!index.isValid())
return Qt::NoItemFlags;
node = indexToNode(index);
if (node == NULL)
return Qt::NoItemFlags;
result = Qt::ItemIsEnabled;
if (node == fTree)
return result;
result |= Qt::ItemIsSelectable;
result |= Qt::ItemIsUserCheckable;
return result;
}
QModelIndex FilterTreeModel::nodeIndex(const FilterTreeNode *node, int row, int column) const
{
FilterTreeNode *child;
if (column > 0 || row >= node->childCount())
return QModelIndex();
child = node->nodeAt(row);
return createIndex(row, column, child);
}
QModelIndex FilterTreeModel::index(int row, int column, const QModelIndex &parent) const
{
const FilterTreeNode *node;
if (!hasIndex(row, column, parent))
return QModelIndex();
node = indexToNode(parent);
if (node == NULL)
return QModelIndex();
return nodeIndex(node, row, column);
}
QModelIndex FilterTreeModel::parent(const QModelIndex &ind) const
{
const FilterTreeNode *node;
FilterTreeNode *parent;
QModelIndex idx;
if (!ind.isValid())
return QModelIndex();
node = indexToNode(ind);
if (node == NULL || node == fTree)
return QModelIndex();
parent = node->parent();
if (parent) {
int row = parent->index();
if (row < 0)
return QModelIndex();
idx = createIndex(row, 0, parent);
return idx;
}
return QModelIndex();
}
bool FilterTreeModel::removeRows(int row, int count, const QModelIndex &parent)
{
FilterTreeNode *node;
int i, last;
last = row + count - 1;
if (!parent.isValid() || count < 1 || row < 0)
return false;
node = indexToNode(parent);
if (node == NULL || last >= node->childCount())
return false;
for (i = 0; i < count; i++)
node->deleteAt(row);
if (node != fTree && node->childCount() < 1)
return removeRow(parent.row(), parent.parent());
return true;
}
void FilterTreeModel::clear()
{
emit layoutAboutToBeChanged();
fTree->clear();
emit layoutChanged();
}

View file

@ -1,53 +0,0 @@
#ifndef FILTERTREEMODEL_H
#define FILTERTREEMODEL_H
#include "filter_card.h"
#include <QAbstractItemModel>
class FilterTree;
class CardFilter;
class FilterTreeNode;
class FilterTreeModel : public QAbstractItemModel
{
Q_OBJECT
private:
FilterTree *fTree;
public slots:
void addFilter(const CardFilter *f);
void removeFilter(const CardFilter *f);
void clearFiltersOfType(CardFilter::Attr filterType);
QList<const CardFilter *> getFiltersOfType(CardFilter::Attr filterType) const;
QList<const CardFilter *> allFilters() const;
private slots:
void proxyBeginInsertRow(const FilterTreeNode *, int);
void proxyEndInsertRow(const FilterTreeNode *, int);
void proxyBeginRemoveRow(const FilterTreeNode *, int);
void proxyEndRemoveRow(const FilterTreeNode *, int);
private:
FilterTreeNode *indexToNode(const QModelIndex &idx) const;
QModelIndex nodeIndex(const FilterTreeNode *node, int row, int column) const;
public:
FilterTreeModel(QObject *parent = nullptr);
~FilterTreeModel() override;
FilterTree *filterTree() const
{
return fTree;
}
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;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QModelIndex parent(const QModelIndex &ind) const override;
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
bool removeRows(int row, int count, const QModelIndex &parent) override;
void clear();
};
#endif

View file

@ -1,83 +0,0 @@
#include "syntax_help.h"
#include <QFile>
#include <QRegularExpression>
#include <QTextStream>
/**
* Creates the card search syntax help window
*
* @return the QTextBrowser
*/
static QTextBrowser *createBrowser(const QString &helpFile)
{
QFile file(helpFile);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
qCWarning(SyntaxHelpLog) << "Could not open syntax help file: " << helpFile;
return nullptr;
}
QTextStream in(&file);
QString text = in.readAll();
file.close();
// Poor Markdown Converter
auto opts = QRegularExpression::MultilineOption;
text = text.replace(QRegularExpression("^(###)(.*)", opts), "<h3>\\2</h3>")
.replace(QRegularExpression("^(##)(.*)", opts), "<h2>\\2</h2>")
.replace(QRegularExpression("^(#)(.*)", opts), "<h1>\\2</h1>")
.replace(QRegularExpression("^------*", opts), "<hr />")
.replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(<a href='\2'>\1</a>)");
auto browser = new QTextBrowser();
browser->setParent(nullptr, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint |
Qt::WindowFullscreenButtonHint);
browser->setWindowTitle("Search Help");
browser->setReadOnly(true);
browser->setMinimumSize({500, 600});
QString sheet = QString("a { text-decoration: underline; color: rgb(71,158,252) };");
browser->document()->setDefaultStyleSheet(sheet);
browser->setHtml(text);
browser->show();
return browser;
}
/**
* Creates the card search syntax help window and connects its anchorClicked signal to the given QLineEdit.
* The window will automatically close when the QLineEdit is destroyed.
*
* @return the QTextBrowser
*/
QTextBrowser *createSearchSyntaxHelpWindow(QLineEdit *lineEdit)
{
auto browser = createBrowser("theme:help/search.md");
QObject::connect(browser, &QTextBrowser::anchorClicked,
[lineEdit](const QUrl &link) { lineEdit->setText(link.fragment()); });
QObject::connect(lineEdit, &QObject::destroyed, browser, &QTextBrowser::close);
return browser;
}
/**
* Creates the deck search syntax help window and connects its anchorClicked signal to the given QLineEdit.
* The window will automatically close when the QLineEdit is destroyed.
*
* @return the QTextBrowser
*/
QTextBrowser *createDeckSearchSyntaxHelpWindow(QLineEdit *lineEdit)
{
auto browser = createBrowser("theme:help/deck_search.md");
QObject::connect(browser, &QTextBrowser::anchorClicked, [lineEdit](const QUrl &link) {
if (link.fragment() == "cardSearchSyntaxHelp") {
createSearchSyntaxHelpWindow(lineEdit);
} else {
lineEdit->setText(link.fragment());
}
});
QObject::connect(lineEdit, &QObject::destroyed, browser, &QTextBrowser::close);
return browser;
}

View file

@ -1,14 +0,0 @@
#ifndef SEARCH_SYNTAX_HELP_H
#define SEARCH_SYNTAX_HELP_H
#include <QLineEdit>
#include <QLoggingCategory>
#include <QTextBrowser>
inline Q_LOGGING_CATEGORY(SyntaxHelpLog, "syntax_help");
QTextBrowser *createSearchSyntaxHelpWindow(QLineEdit *lineEdit);
QTextBrowser *createDeckSearchSyntaxHelpWindow(QLineEdit *lineEdit);
#endif // SEARCH_SYNTAX_HELP_H

View file

@ -1,52 +0,0 @@
#ifndef GAME_SPECIFIC_TERMS_H
#define GAME_SPECIFIC_TERMS_H
#include <QCoreApplication>
#include <QString>
/*
* Collection of traslatable property names used in games,
* so we can use Game::Property instead of hardcoding strings.
* Note: Mtg = "Maybe that game"
*/
namespace Mtg
{
QString const CardType("type");
QString const ConvertedManaCost("cmc");
QString const Colors("colors");
QString const Loyalty("loyalty");
QString const MainCardType("maintype");
QString const ManaCost("manacost");
QString const PowTough("pt");
QString const Side("side");
QString const Layout("layout");
QString const ColorIdentity("coloridentity");
inline static const QString getNicePropertyName(QString key)
{
if (key == CardType)
return QCoreApplication::translate("Mtg", "Card Type");
if (key == ConvertedManaCost)
return QCoreApplication::translate("Mtg", "Mana Value");
if (key == Colors)
return QCoreApplication::translate("Mtg", "Color(s)");
if (key == Loyalty)
return QCoreApplication::translate("Mtg", "Loyalty");
if (key == MainCardType)
return QCoreApplication::translate("Mtg", "Main Card Type");
if (key == ManaCost)
return QCoreApplication::translate("Mtg", "Mana Cost");
if (key == PowTough)
return QCoreApplication::translate("Mtg", "P/T");
if (key == Side)
return QCoreApplication::translate("Mtg", "Side");
if (key == Layout)
return QCoreApplication::translate("Mtg", "Layout");
if (key == ColorIdentity)
return QCoreApplication::translate("Mtg", "Color Identity");
return key;
}
}; // namespace Mtg
#endif

View file

@ -1,9 +1,9 @@
#include "card_menu.h"
#include "../../../client/tabs/tab_game.h"
#include "../../../database/card_database_manager.h"
#include "../../../settings/card_counter_settings.h"
#include "../../board/card_item.h"
#include "../../cards/card_database_manager.h"
#include "../../zones/logic/view_zone_logic.h"
#include "../card_menu_action_type.h"
#include "../player.h"

View file

@ -2,8 +2,8 @@
#include "../../../client/tabs/tab_game.h"
#include "../../../common/pb/command_reveal_cards.pb.h"
#include "../../../database/card_database_manager.h"
#include "../../board/card_item.h"
#include "../../cards/card_database_manager.h"
#include "../../zones/hand_zone.h"
#include "../card_menu_action_type.h"
#include "../player_actions.h"

View file

@ -1,11 +1,11 @@
#ifndef PLAYER_H
#define PLAYER_H
#include "../../card/card_info.h"
#include "../../client/tearoff_menu.h"
#include "../../dialogs/dlg_create_token.h"
#include "../../filters/filter_string.h"
#include "../board/abstract_graphics_item.h"
#include "../cards/card_info.h"
#include "../filters/filter_string.h"
#include "menu/player_menu.h"
#include "pb/card_attributes.pb.h"
#include "pb/game_event.pb.h"

View file

@ -3,10 +3,10 @@
#include "../../../common/pb/context_move_card.pb.h"
#include "../../client/get_text_with_max.h"
#include "../../client/tabs/tab_game.h"
#include "../../database/card_database_manager.h"
#include "../../dialogs/dlg_move_top_cards_until.h"
#include "../../dialogs/dlg_roll_dice.h"
#include "../board/card_item.h"
#include "../cards/card_database_manager.h"
#include "../zones/logic/view_zone_logic.h"
#include "card_menu_action_type.h"
#include "pb/command_attach_card.pb.h"

View file

@ -1,7 +1,7 @@
#include "card_zone_logic.h"
#include "../../../database/card_database_manager.h"
#include "../../board/card_item.h"
#include "../../cards/card_database_manager.h"
#include "../../player/player.h"
#include "../pile_zone.h"
#include "../view_zone.h"

View file

@ -1,11 +1,11 @@
#include "table_zone.h"
#include "../../card/card_info.h"
#include "../../client/ui/theme_manager.h"
#include "../../settings/cache_settings.h"
#include "../board/arrow_item.h"
#include "../board/card_drag_item.h"
#include "../board/card_item.h"
#include "../cards/card_info.h"
#include "../player/player.h"
#include "logic/table_zone_logic.h"
#include "pb/command_move_card.pb.h"

View file

@ -1,9 +1,9 @@
#include "view_zone.h"
#include "../../card/card_info.h"
#include "../../server/pending_command.h"
#include "../board/card_drag_item.h"
#include "../board/card_item.h"
#include "../cards/card_info.h"
#include "../player/player.h"
#include "logic/view_zone_logic.h"
#include "pb/command_dump_zone.pb.h"

View file

@ -1,7 +1,7 @@
#ifndef ZONEVIEWERZONE_H
#define ZONEVIEWERZONE_H
#include "../filters/filter_string.h"
#include "../../filters/filter_string.h"
#include "logic/view_zone_logic.h"
#include "select_zone.h"

View file

@ -1,9 +1,9 @@
#include "view_zone_widget.h"
#include "../../client/ui/pixel_map_generator.h"
#include "../../filters/syntax_help.h"
#include "../../settings/cache_settings.h"
#include "../board/card_item.h"
#include "../filters/syntax_help.h"
#include "../game_scene.h"
#include "../player/player.h"
#include "pb/command_shuffle.pb.h"