mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-09 17:44:01 -07:00
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:
parent
bed79ef89e
commit
7ac22a6ce8
149 changed files with 218 additions and 218 deletions
193
cockatrice/src/filters/deck_filter_string.cpp
Normal file
193
cockatrice/src/filters/deck_filter_string.cpp
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#include "deck_filter_string.h"
|
||||
|
||||
#include "../database/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; };
|
||||
}
|
||||
}
|
||||
51
cockatrice/src/filters/deck_filter_string.h
Normal file
51
cockatrice/src/filters/deck_filter_string.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#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
|
||||
78
cockatrice/src/filters/filter_builder.cpp
Normal file
78
cockatrice/src/filters/filter_builder.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#include "filter_builder.h"
|
||||
|
||||
#include "../deck/custom_line_edit.h"
|
||||
#include "filter_card.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
|
||||
FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
filterCombo = new QComboBox;
|
||||
filterCombo->setObjectName("filterCombo");
|
||||
for (int i = 0; i < CardFilter::AttrEnd; i++)
|
||||
filterCombo->addItem(CardFilter::attrName(static_cast<CardFilter::Attr>(i)), QVariant(i));
|
||||
|
||||
typeCombo = new QComboBox;
|
||||
typeCombo->setObjectName("typeCombo");
|
||||
for (int i = 0; i < CardFilter::TypeEnd; i++)
|
||||
typeCombo->addItem(CardFilter::typeName(static_cast<CardFilter::Type>(i)), QVariant(i));
|
||||
|
||||
QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString());
|
||||
ok->setObjectName("ok");
|
||||
ok->setMaximumSize(20, 20);
|
||||
|
||||
edit = new LineEditUnfocusable;
|
||||
edit->setObjectName("edit");
|
||||
edit->setPlaceholderText(tr("Type your filter here"));
|
||||
edit->setClearButtonEnabled(true);
|
||||
edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
QGridLayout *layout = new QGridLayout;
|
||||
layout->setObjectName("layout");
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
layout->addWidget(typeCombo, 0, 0, 1, 2);
|
||||
layout->addWidget(filterCombo, 0, 2, 1, 2);
|
||||
layout->addWidget(edit, 1, 0, 1, 3);
|
||||
layout->addWidget(ok, 1, 3);
|
||||
|
||||
setLayout(layout);
|
||||
|
||||
connect(filterCombo, 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();
|
||||
}
|
||||
37
cockatrice/src/filters/filter_builder.h
Normal file
37
cockatrice/src/filters/filter_builder.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef FILTERBUILDER_H
|
||||
#define FILTERBUILDER_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class LineEditUnfocusable;
|
||||
class CardFilter;
|
||||
|
||||
class FilterBuilder : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QComboBox *typeCombo;
|
||||
QComboBox *filterCombo;
|
||||
LineEditUnfocusable *edit;
|
||||
CardFilter *fltr;
|
||||
|
||||
void destroyFilter();
|
||||
|
||||
public:
|
||||
explicit FilterBuilder(QWidget *parent = nullptr);
|
||||
~FilterBuilder() override;
|
||||
|
||||
signals:
|
||||
void add(const CardFilter *f);
|
||||
|
||||
public slots:
|
||||
private slots:
|
||||
void emit_add();
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
#endif
|
||||
92
cockatrice/src/filters/filter_card.cpp
Normal file
92
cockatrice/src/filters/filter_card.cpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#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("");
|
||||
}
|
||||
}
|
||||
70
cockatrice/src/filters/filter_card.h
Normal file
70
cockatrice/src/filters/filter_card.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#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
|
||||
413
cockatrice/src/filters/filter_string.cpp
Normal file
413
cockatrice/src/filters/filter_string.cpp
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
#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; };
|
||||
}
|
||||
}
|
||||
56
cockatrice/src/filters/filter_string.h
Normal file
56
cockatrice/src/filters/filter_string.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#ifndef FILTER_STRING_H
|
||||
#define FILTER_STRING_H
|
||||
|
||||
#include "../card/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
|
||||
565
cockatrice/src/filters/filter_tree.cpp
Normal file
565
cockatrice/src/filters/filter_tree.cpp
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
#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();
|
||||
}
|
||||
277
cockatrice/src/filters/filter_tree.h
Normal file
277
cockatrice/src/filters/filter_tree.h
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
#ifndef FILTERTREE_H
|
||||
#define FILTERTREE_H
|
||||
|
||||
#include "../database/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
|
||||
329
cockatrice/src/filters/filter_tree_model.cpp
Normal file
329
cockatrice/src/filters/filter_tree_model.cpp
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
#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();
|
||||
}
|
||||
53
cockatrice/src/filters/filter_tree_model.h
Normal file
53
cockatrice/src/filters/filter_tree_model.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#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
|
||||
83
cockatrice/src/filters/syntax_help.cpp
Normal file
83
cockatrice/src/filters/syntax_help.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#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;
|
||||
}
|
||||
14
cockatrice/src/filters/syntax_help.h
Normal file
14
cockatrice/src/filters/syntax_help.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#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
|
||||
Loading…
Add table
Add a link
Reference in a new issue