mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2025-12-12 15:49:28 -08:00
Split filters into libraries where applicable. (#6293)
* Split filters into libraries where applicable. Took 23 minutes Took 2 minutes * Include filter string. Took 5 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
24
libcockatrice_filters/CMakeLists.txt
Normal file
24
libcockatrice_filters/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(HEADERS libcockatrice/filters/filter_card.h libcockatrice/filters/filter_string.h
|
||||
libcockatrice/filters/filter_tree.h
|
||||
)
|
||||
|
||||
if(Qt6_FOUND)
|
||||
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
elseif(Qt5_FOUND)
|
||||
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
endif()
|
||||
|
||||
add_library(
|
||||
libcockatrice_filters STATIC ${MOC_SOURCES} libcockatrice/filters/filter_card.cpp
|
||||
libcockatrice/filters/filter_string.cpp libcockatrice/filters/filter_tree.cpp
|
||||
)
|
||||
|
||||
add_dependencies(libcockatrice_filters libcockatrice_card)
|
||||
|
||||
target_include_directories(libcockatrice_filters PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(libcockatrice_filters PUBLIC libcockatrice_card ${QT_CORE_MODULE})
|
||||
92
libcockatrice_filters/libcockatrice/filters/filter_card.cpp
Normal file
92
libcockatrice_filters/libcockatrice/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("");
|
||||
}
|
||||
}
|
||||
78
libcockatrice_filters/libcockatrice/filters/filter_card.h
Normal file
78
libcockatrice_filters/libcockatrice/filters/filter_card.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @file filter_card.h
|
||||
* @ingroup CardDatabaseModelFilters
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#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
|
||||
412
libcockatrice_filters/libcockatrice/filters/filter_string.cpp
Normal file
412
libcockatrice_filters/libcockatrice/filters/filter_string.cpp
Normal file
@@ -0,0 +1,412 @@
|
||||
#include "filter_string.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <libcockatrice/utility/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 / 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; };
|
||||
}
|
||||
}
|
||||
62
libcockatrice_filters/libcockatrice/filters/filter_string.h
Normal file
62
libcockatrice_filters/libcockatrice/filters/filter_string.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @file filter_string.h
|
||||
* @ingroup CardDatabaseModelFilters
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef FILTER_STRING_H
|
||||
#define FILTER_STRING_H
|
||||
|
||||
#include "filter_tree.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
#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
libcockatrice_filters/libcockatrice/filters/filter_tree.cpp
Normal file
565
libcockatrice_filters/libcockatrice/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();
|
||||
}
|
||||
283
libcockatrice_filters/libcockatrice/filters/filter_tree.h
Normal file
283
libcockatrice_filters/libcockatrice/filters/filter_tree.h
Normal file
@@ -0,0 +1,283 @@
|
||||
/**
|
||||
* @file filter_tree.h
|
||||
* @ingroup CardDatabaseModelFilters
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef FILTERTREE_H
|
||||
#define FILTERTREE_H
|
||||
|
||||
#include "filter_card.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#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
|
||||
Reference in New Issue
Block a user