Filter Strings for Deck Editor search (#3582)

* Add MagicCards.info like fitler parser.

* Use FilterString whenever one of [:=<>] is in the edit box.

* Opts

* Opt

* - Capture errors
- Allow querying any property by full name

* clang format

* Update cockatrice/src/filter_string.cpp

Co-Authored-By: basicer <basicer@basicer.com>

* - Some refactoring for clarity
- More filters
- Add filter help

* Clangify

* Add icon

* Fix test name

* Remove stay debug

* - Add Rarity filter
- Make " trigger filter string mode

* You have to pass both filter types

* clangify

* - Allow filtering by legality
- Import legality into card.xml

* Add format filter to filtertree

* More color search options

* RIP extended

* More fixes

* Fix c:m

* set syntax help parent

* Fix warning

* add additional explanations to syntax help

* Allow multiple ands/ors to be chained

* Cleanup and refactor

Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com>

* Move utility into guards

Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com>

* I heard you like refactors so I put a refactor inside your refactor (#3594)

* I heard you like refactors so I put a refactor inside your refactor

so you can refactor while you refactor

* clangify

* Update tab_deck_editor.h
This commit is contained in:
Rob Blanckaert 2019-03-01 11:30:32 -08:00 committed by Zach H
parent 4427ad1451
commit eb60fec8e2
24 changed files with 780 additions and 122 deletions

View file

@ -123,6 +123,7 @@ SET(cockatrice_SOURCES
src/carddbparser/carddatabaseparser.cpp src/carddbparser/carddatabaseparser.cpp
src/carddbparser/cockatricexml3.cpp src/carddbparser/cockatricexml3.cpp
src/carddbparser/cockatricexml4.cpp src/carddbparser/cockatricexml4.cpp
src/filter_string.cpp
${VERSION_STRING_CPP} ${VERSION_STRING_CPP}
) )

View file

@ -18,6 +18,7 @@
<file>resources/icons/delete.svg</file> <file>resources/icons/delete.svg</file>
<file>resources/icons/forgot_password.svg</file> <file>resources/icons/forgot_password.svg</file>
<file>resources/icons/increment.svg</file> <file>resources/icons/increment.svg</file>
<file>resources/icons/info.svg</file>
<file>resources/icons/lock.svg</file> <file>resources/icons/lock.svg</file>
<file>resources/icons/not_ready_start.svg</file> <file>resources/icons/not_ready_start.svg</file>
<file>resources/icons/pencil.svg</file> <file>resources/icons/pencil.svg</file>
@ -360,5 +361,6 @@
<file>resources/tips/images/themes.png</file> <file>resources/tips/images/themes.png</file>
<file>resources/tips/images/tip_of_the_day.png</file> <file>resources/tips/images/tip_of_the_day.png</file>
<file>resources/help/search.md</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -0,0 +1,60 @@
## Syntax Help
-----
The search bar recognizes a set of special commands similar to some other card databases. Here is a list with examples. Each entry can be clicked to test the query and has a small explanation. Note that all searches are case insensitive.
<dl>
<dt>Name:</dt>
<dd>[birds of paradise](#birds of paradise) <small>(Any card name containing the words birds, of, and paradise)</small></dd>
<dd>["birds of paradise"](#%22birds of paradise%22) <small>(Any card name containing the exact phrase "birds of paradise")</small></dd>
<dt>Rules Text (<u>O</u>racle):</dt>
<dd>[o:flying](#o:flying) <small>(Any card text that has the word flying)</small></dd>
<dd>[o:"first strike"](#o:%22first strike%22) <small>(Any card text that has the exact phrase "first strike")</small></dd>
<dd>[o:"{T}" o:"add one mana of any color"](#o:%22{T}%22 o:%22add one mana of any color%22) <small>(Any card text that has a tap symbol and the phrase "add one mana of any color")</small></dd>
<dt><u>T</u>ypes:</dt>
<dd>[t:angel](#t:angel) <small>(Any card with the type angel)</small></dd>
<dd>[t:angel t:legendary](#t:angel t:legendary) <small>(Any angel that's also legendary)</small></dd>
<dd>[t:basic](#t:basic) <small>(Any card with the type basic)</small></dd>
<dd>[t:arcane t:instant](#t:arcane t:instant) <small>(Any card with the types arcane and instant)</small></dd>
<dt><u>C</u>olors:</dt>
<dd>[c:w](#c:w) <small>(Any card that is white)</small></dd>
<dd>[c:wu](#c:wu) <small>(Any card that is white or blue)</small></dd>
<dd>[c:wum](#c:wum) <small>(Any card that is white or blue, and multicolored)</small></dd>
<!--
<dd>[c!w](#c!w) <small>(Cards that are only white)</small></dd>
<dd>[c!wu](#c!wu) <small>(Cards that are only white or blue, or both)</small></dd>
<dd>[c!wum](#c!wum) <small>(Cards that are only white and blue, and multicolored)</small></dd>
<dd>[c=wubrg](#c%3Dwubrg) <small>(Cards that are all five colors)</small></dd>
-->
<dd>[c:c](#c:c) <small>(Any colorless card)</small></dd>
<dt><u>Pow</u>er, <u>Tou</u>ghness, <u>C</u>onverted <u>M</u>ana <u>C</u>ost:</dt>
<dd>[tou:1](#tou:1) <small>(Any card with a toughness of 1)</small></dd>
<dd>[pow>=8](#pow>=8) <small>(Any card with a power greater than or equal to 8)</small></dd>
<dd>[cmc=7](#cmc=7) <small>(Any card with a converted mana cost equal to 7)</small></dd>
<dt><u>R</u>arity:</dt>
<dd>[r:mythic](#r:mythic) <small>(Any card that has the mythic-rare rarity)</small></dd>
<dt><u>F</u>ormat:</dt>
<dd>[f:standard](#f:standard) <small>(Any card that can be played in standard)</small></dd>
<dd>[banned:modern](#banned:modern) <small>(Any card that is banned in modern)</small></dd>
<dd>[restricted:vintage](#restricted:vintage) <small>(Any card that is restricted in vintage)</small></dd>
<dd>[legal:pauper](#legal:pauper) <small>(Any card that is legal in pauper)</small></dd>
<dt><u>E</u>dition:</dt>
<dd>[set:lea](#set:lea) <small>(Cards that appear in Alpha, which has the set code LEA)</small></dd>
<dd>[e:lea,leb](#e:lea,leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
<dd><a href="#e:lea,leb -(e:lea e:leb)">e:lea,leb -(e:lea e:leb)</a> <small>(Cards that appear in Alpha or Beta but not in both editions)</small></dd>
<dt>Inverse:</dt>
<dd>[c:wu -c:m](#c:wu -c:m) <small>(Any card that is white or blue, but not multicolored)</small></dd>
<dt>Branching:</dt>
<dd>[t:sliver or o:changeling](#t:sliver or o:changeling) <small>(Any card that is either a sliver or has changeling)</small></dd>
<dt>Grouping:</dt>
<dd><a href="#t:angel -(angel or c:w)">t:angel -(angel or c:w)</a> <small>(Any angel that doesn't have angel in its name and isn't white)</small></dd>
</dl>

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
id="svg2858"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="info.svg">
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Triangle"
style="display:inline">
<path
sodipodi:type="arc"
style="fill:#aaaaaa;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
id="path3758-1"
sodipodi:cx="53.900002"
sodipodi:cy="78.5"
sodipodi:rx="46.5"
sodipodi:ry="46.5"
d="M 100.4,78.5 A 46.5,46.5 0 1 1 7.4000015,78.5 46.5,46.5 0 1 1 100.4,78.5 z"
transform="matrix(1.05866,0,0,1.05866,-7.0617752,-32.704809)" />
<g
style="font-size:133.49534607px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.06498075;stroke-opacity:1;font-family:'Magic:the Gathering';-inkscape-font-specification:'Magic:the Gathering'"
id="text3759">
<path
d="m 46.854005,16.085692 c 0.977733,-2.411704 1.683885,-4.193378 2.118456,-5.345028 0.369355,-1.1514901 0.771318,-1.9988718 1.205891,-2.5421482 0.456264,-0.4344718 1.347101,-0.825571 2.672514,-1.1732989 1.23846,-0.2823765 2.705082,-0.4670623 4.399871,-0.5540578 0.369346,8.46e-5 0.727854,8.46e-5 1.075524,0 1.694737,8.46e-5 2.672485,0.5758696 2.933248,1.7273567 -3e-5,0.1956326 -3e-5,0.3803183 0,0.5540579 -3e-5,1.3254743 -0.662726,2.9224633 -1.98809,4.7909703 -1.520969,2.238035 -3.43301,4.24785 -5.736128,6.029453 -2.32489,1.760018 -4.551983,2.781222 -6.681286,3.063614 -1.260224,-0.543125 -1.977239,-1.075454 -2.151048,-1.59699 -1.3e-5,-0.08684 -1.3e-5,-0.184616 0,-0.293325 -1.3e-5,-0.434484 0.228128,-1.010269 0.684424,-1.727357 0.521453,-0.890765 1.010327,-1.868513 1.466624,-2.933247 z M 31.372977,37.856906 c 2.650785,-0.716962 6.159814,-1.651255 10.527099,-2.802881 4.280354,-1.173242 7.398284,-1.803346 9.3538,-1.890315 0.08689,5.8e-5 0.173803,5.8e-5 0.260733,0 1.06464,5.8e-5 1.82511,0.358566 2.281415,1.075524 0.260711,0.630161 0.391077,1.520998 0.391099,2.672514 -2.2e-5,0.260787 -2.2e-5,0.532384 0,0.814791 -0.173844,1.586178 -0.260755,2.781204 -0.260733,3.58508 l 0,39.533656 c 1.238459,1e-5 2.976678,-0.673549 5.214662,-2.020681 1.673009,-0.977736 2.868034,-1.46661 3.58508,-1.466624 0.173791,1.4e-5 0.304158,0.04347 0.3911,0.130367 0.543162,0.369385 0.814759,0.771348 0.814791,1.20589 -3.2e-5,1.151583 -1.868617,2.66166 -5.605762,4.530238 -1.238507,0.717023 -3.063637,1.922912 -5.475395,3.617671 -2.49871,1.781679 -4.769259,3.204846 -6.811652,4.269505 -0.347658,0.347644 -0.923443,0.662696 -1.727357,0.945157 -0.260745,0.08691 -0.532342,0.130366 -0.814791,0.130367 -0.521477,-10e-7 -1.010351,-0.271598 -1.466623,-0.814791 C 41.856611,91.111641 41.7697,81.594892 41.76971,62.8221 c -1e-5,-1.955468 -1e-5,-4.008739 0,-6.15982 -1e-5,-2.129283 -0.08692,-4.128235 -0.260734,-5.996861 -0.260742,-1.781634 -0.706161,-3.204801 -1.336257,-4.269504 -0.717023,-1.347075 -1.60786,-2.368279 -2.672514,-3.063614 -1.151576,-0.630057 -2.357465,-1.260161 -3.617672,-1.890315 -1.325394,-0.521416 -2.161911,-1.053746 -2.509556,-1.59699 -0.195549,-0.173771 -0.293324,-0.434504 -0.293324,-0.7822 -0.08691,-0.369319 0.01086,-0.771282 0.293324,-1.20589 z"
style=""
id="path2993" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -21,7 +21,6 @@ class CardRelation;
class ICardDatabaseParser; class ICardDatabaseParser;
typedef QMap<QString, QString> QStringMap; typedef QMap<QString, QString> QStringMap;
typedef QMap<QString, int> MuidMap;
typedef QSharedPointer<CardInfo> CardInfoPtr; typedef QSharedPointer<CardInfo> CardInfoPtr;
typedef QSharedPointer<CardSet> CardSetPtr; typedef QSharedPointer<CardSet> CardSetPtr;
typedef QMap<QString, CardInfoPerSet> CardInfoPerSetMap; typedef QMap<QString, CardInfoPerSet> CardInfoPerSetMap;
@ -248,6 +247,10 @@ public:
properties.insert(_name, _value); properties.insert(_name, _value);
emit cardInfoChanged(smartThis); emit cardInfoChanged(smartThis);
} }
bool hasProperty(const QString &propertyName) const
{
return properties.contains(propertyName);
}
const CardInfoPerSetMap &getSets() const const CardInfoPerSetMap &getSets() const
{ {
return sets; return sets;

View file

@ -143,12 +143,16 @@ void CardDatabaseModel::cardRemoved(CardInfoPtr card)
endRemoveRows(); endRemoveRows();
} }
CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent) : QSortFilterProxyModel(parent), isToken(ShowAll) CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent)
: QSortFilterProxyModel(parent), isToken(ShowAll), filterString(nullptr)
{ {
filterTree = nullptr; filterTree = nullptr;
setFilterCaseSensitivity(Qt::CaseInsensitive); setFilterCaseSensitivity(Qt::CaseInsensitive);
setSortCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive);
dirtyTimer.setSingleShot(true);
connect(&dirtyTimer, &QTimer::timeout, this, &CardDatabaseDisplayModel::invalidate);
loadedRowCount = 0; loadedRowCount = 0;
} }
@ -285,6 +289,13 @@ bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex
if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken())) if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken()))
return false; return false;
if (filterString != nullptr) {
if (filterTree != nullptr && !filterTree->acceptsCard(info)) {
return false;
}
return filterString->check(info);
}
return rowMatchesCardName(info); return rowMatchesCardName(info);
} }

View file

@ -2,10 +2,12 @@
#define CARDDATABASEMODEL_H #define CARDDATABASEMODEL_H
#include "carddatabase.h" #include "carddatabase.h"
#include "filter_string.h"
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QList> #include <QList>
#include <QSet> #include <QSet>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QTimer>
class FilterTree; class FilterTree;
@ -67,11 +69,12 @@ public:
private: private:
FilterBool isToken; FilterBool isToken;
QString cardNameBeginning, cardName, cardText; QString cardName, cardText;
QString searchTerm;
QSet<QString> cardNameSet, cardTypes, cardColors; QSet<QString> cardNameSet, cardTypes, cardColors;
FilterTree *filterTree; FilterTree *filterTree;
FilterString *filterString;
int loadedRowCount; int loadedRowCount;
QTimer dirtyTimer;
/** The translation table that will be used for sanitizeCardName. */ /** The translation table that will be used for sanitizeCardName. */
static QMap<wchar_t, wchar_t> characterTranslation; static QMap<wchar_t, wchar_t> characterTranslation;
@ -82,41 +85,33 @@ public:
void setIsToken(FilterBool _isToken) void setIsToken(FilterBool _isToken)
{ {
isToken = _isToken; isToken = _isToken;
invalidate(); dirty();
}
void setCardNameBeginning(const QString &_beginning)
{
cardNameBeginning = _beginning;
invalidate();
} }
void setCardName(const QString &_cardName) void setCardName(const QString &_cardName)
{ {
if (filterString != nullptr) {
delete filterString;
filterString = nullptr;
}
cardName = sanitizeCardName(_cardName, characterTranslation); cardName = sanitizeCardName(_cardName, characterTranslation);
invalidate(); dirty();
}
void setStringFilter(const QString &_src)
{
delete filterString;
filterString = new FilterString(_src);
dirty();
} }
void setCardNameSet(const QSet<QString> &_cardNameSet) void setCardNameSet(const QSet<QString> &_cardNameSet)
{ {
cardNameSet = _cardNameSet; cardNameSet = _cardNameSet;
invalidate(); dirty();
} }
void setSearchTerm(const QString &_searchTerm)
void dirty()
{ {
searchTerm = _searchTerm; dirtyTimer.start(20);
}
void setCardText(const QString &_cardText)
{
cardText = _cardText;
invalidate();
}
void setCardTypes(const QSet<QString> &_cardTypes)
{
cardTypes = _cardTypes;
invalidate();
}
void setCardColors(const QSet<QString> &_cardColors)
{
cardColors = _cardColors;
invalidate();
} }
void clearFilterAll(); void clearFilterAll();
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;

View file

@ -41,6 +41,8 @@ const QString CardFilter::attrName(Attr a)
return tr("Toughness"); return tr("Toughness");
case AttrLoyalty: case AttrLoyalty:
return tr("Loyalty"); return tr("Loyalty");
case AttrFormat:
return tr("Format");
default: default:
return QString(""); return QString("");
} }

View file

@ -3,6 +3,7 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <utility>
class CardFilter : public QObject class CardFilter : public QObject
{ {
@ -18,7 +19,7 @@ public:
TypeEnd TypeEnd
}; };
/* if you add an atribute here you also need to /* if you add an attribute here you also need to
* add its string representation in attrName */ * add its string representation in attrName */
enum Attr enum Attr
{ {
@ -33,6 +34,7 @@ public:
AttrText, AttrText,
AttrTough, AttrTough,
AttrType, AttrType,
AttrFormat,
AttrEnd AttrEnd
}; };
@ -42,7 +44,7 @@ private:
enum Attr a; enum Attr a;
public: public:
CardFilter(QString term, Type type, Attr attr) : trm(term), t(type), a(attr){}; CardFilter(QString &term, Type type, Attr attr) : trm(term), t(type), a(attr){};
Type type() const Type type() const
{ {

View file

@ -16,7 +16,7 @@ CardInfoText::CardInfoText(QWidget *parent) : QFrame(parent), info(nullptr)
textLabel = new QTextEdit(); textLabel = new QTextEdit();
textLabel->setReadOnly(true); textLabel->setReadOnly(true);
QGridLayout *grid = new QGridLayout(this); auto *grid = new QGridLayout(this);
grid->addWidget(nameLabel, 0, 0); grid->addWidget(nameLabel, 0, 0);
grid->addWidget(textLabel, 1, 0, -1, 2); grid->addWidget(textLabel, 1, 0, -1, 2);
grid->setRowStretch(1, 1); grid->setRowStretch(1, 1);
@ -39,6 +39,8 @@ void CardInfoText::setCard(CardInfoPtr card)
QStringList cardProps = card->getProperties(); QStringList cardProps = card->getProperties();
foreach (QString key, cardProps) { foreach (QString key, cardProps) {
if (key.contains("-"))
continue;
QString keyText = Mtg::getNicePropertyName(key).toHtmlEscaped() + ":"; QString keyText = Mtg::getNicePropertyName(key).toHtmlEscaped() + ":";
text += text +=
QString("<tr><td>%1</td><td></td><td>%2</td></tr>").arg(keyText, card->getProperty(key).toHtmlEscaped()); QString("<tr><td>%1</td><td></td><td>%2</td></tr>").arg(keyText, card->getProperty(key).toHtmlEscaped());
@ -46,16 +48,16 @@ void CardInfoText::setCard(CardInfoPtr card)
auto relatedCards = card->getRelatedCards(); auto relatedCards = card->getRelatedCards();
auto reverserelatedCards2Me = card->getReverseRelatedCards2Me(); auto reverserelatedCards2Me = card->getReverseRelatedCards2Me();
if (relatedCards.size() || reverserelatedCards2Me.size()) { if (!relatedCards.empty() || !reverserelatedCards2Me.empty()) {
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>").arg(tr("Related cards:")); text += QString("<tr><td>%1</td><td width=\"5\"></td><td>").arg(tr("Related cards:"));
for (int i = 0; i < relatedCards.size(); ++i) { for (auto *relatedCard : relatedCards) {
QString tmp = relatedCards.at(i)->getName().toHtmlEscaped(); QString tmp = relatedCard->getName().toHtmlEscaped();
text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>"; text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>";
} }
for (int i = 0; i < reverserelatedCards2Me.size(); ++i) { for (auto *i : reverserelatedCards2Me) {
QString tmp = reverserelatedCards2Me.at(i)->getName().toHtmlEscaped(); QString tmp = i->getName().toHtmlEscaped();
text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>"; text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>";
} }

View file

@ -17,7 +17,7 @@ private:
CardInfoPtr info; CardInfoPtr info;
public: public:
CardInfoText(QWidget *parent = 0); explicit CardInfoText(QWidget *parent = nullptr);
void retranslateUi(); void retranslateUi();
void setInvalidCardName(const QString &cardName); void setInvalidCardName(const QString &cardName);

View file

@ -0,0 +1,352 @@
#include "filter_string.h"
#include "../../common/lib/peglib.h"
#include <QByteArray>
#include <QString>
#include <cmath>
#include <functional>
peg::parser search(R"(
Start <- QueryPartList
~ws <- [ ]+
QueryPartList <- ComplexQueryPart ( ws ("and" ws)? ComplexQueryPart)* ws*
ComplexQueryPart <- SomewhatComplexQueryPart ws $or<[oO][rR]> 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' [:] RegexString
CMCQuery <- 'cmc' ws? NumericExpression
PowerQuery <- [Pp] 'ow' 'er'? ws? NumericExpression
ToughnessQuery <- [Tt] 'ou' 'ghness'? ws? NumericExpression
RarityQuery <- [rR] ':' RegexString
FormatQuery <- 'f' ':' Format / Legality ':' Format
Format <- [Mm] 'odern'? / [Ss] 'tandard'? / [Vv] 'intage'? / [Ll] 'egacy'? / [Cc] 'ommander'?
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 [:] RegexString / String ws? NumericExpression
NonQuote <- !["].
UnescapedStringListPart <- [a-zA-Z0-9']+
String <- UnescapedStringListPart / ["] <NonQuote*> ["]
StringValue <- String / [(] StringList [)]
StringList <- StringListString (ws? [,] ws? StringListString)*
StringListString <- UnescapedStringListPart
GenericQuery <- RegexString
RegexString <- String
FlexStringValue <- CompactStringSet / String / [(] StringList [)]
CompactStringSet <- StringListString ([,+] StringListString)+
NumericExpression <- NumericOperator ws? NumericValue
NumericOperator <- [=:] / <[><!][=]?>
NumericValue <- [0-9]+
)");
std::once_flag init;
static void setupParserRules()
{
auto passthru = [](const peg::SemanticValues &sv) -> Filter { return !sv.empty() ? sv[0].get<Filter>() : nullptr; };
search["Start"] = passthru;
search["QueryPartList"] = [](const peg::SemanticValues &sv) -> Filter {
return [=](CardData x) {
for (int i = 0; i < sv.size(); ++i) {
if (!sv[i].get<Filter>()(x))
return false;
}
return true;
};
};
search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> Filter {
return [=](CardData x) {
for (int i = 0; i < sv.size(); ++i) {
if (sv[i].get<Filter>()(x))
return true;
}
return false;
};
};
search["SomewhatComplexQueryPart"] = passthru;
search["QueryPart"] = passthru;
search["NotQuery"] = [](const peg::SemanticValues &sv) -> Filter {
Filter dependent = sv[0].get<Filter>();
return [=](CardData x) -> bool { return !dependent(x); };
};
search["TypeQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
return [=](CardData x) -> bool { return matcher(x->getCardType()); };
};
search["SetQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
return [=](CardData x) -> bool {
for (const auto &set : x->getSets().keys()) {
if (matcher(set))
return true;
}
return false;
};
};
search["RarityQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
return [=](CardData x) -> bool {
for (const auto &set : x->getSets().values()) {
if (matcher(set.getProperty("rarity")))
return true;
}
return false;
};
};
search["FormatQuery"] = [](const peg::SemanticValues &sv) -> Filter {
if (sv.choice() == 0) {
QString format = sv[0].get<QString>();
return [=](CardData x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == "legal"; };
} else {
QString format = sv[1].get<QString>();
QString legality = sv[0].get<QString>();
return [=](CardData x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == legality; };
}
};
search["Legality"] = [](const peg::SemanticValues &sv) -> QString {
switch (tolower(sv.str()[0])) {
case 'l':
return "legal";
case 'b':
return "banned";
case 'r':
return "restricted";
default:
return "";
}
};
search["Format"] = [](const peg::SemanticValues &sv) -> QString {
switch (tolower(sv.str()[0])) {
case 'm':
return "modern";
case 's':
return "standard";
case 'v':
return "vintage";
case 'l':
return "legacy";
case 'c':
return "commander";
default:
return "";
}
};
search["StringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher {
if (sv.choice() == 0) {
auto target = sv[0].get<QString>();
return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); };
} else {
auto target = sv[0].get<QStringList>();
return [=](const QString &s) {
for (const QString &str : target) {
if (s.split(" ").contains(str, Qt::CaseInsensitive)) {
return true;
}
}
return false;
};
}
};
search["String"] = [](const peg::SemanticValues &sv) -> QString {
if (sv.choice() == 0) {
return QString::fromStdString(sv.str());
} else {
return QString::fromStdString(sv.token(0));
}
};
search["FlexStringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher {
if (sv.choice() != 1) {
auto target = sv[0].get<QStringList>();
return [=](const QString &s) {
for (const QString &str : target) {
if (s.split(" ").contains(str, Qt::CaseInsensitive)) {
return true;
}
}
return false;
};
} else {
auto target = sv[0].get<QString>();
return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); };
}
};
search["CompactStringSet"] = search["StringList"] = [](const peg::SemanticValues &sv) -> QStringList {
QStringList result;
for (int i = 0; i < sv.size(); ++i) {
result.append(sv[i].get<QString>());
}
return result;
};
search["StringListString"] = [](const peg::SemanticValues &sv) -> QString {
return QString::fromStdString(sv.str());
};
search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher {
auto arg = sv[1].get<int>();
auto op = sv[0].get<QString>();
if (op == ">")
return [=](int s) { return s > arg; };
if (op == ">=")
return [=](int s) { return s >= arg; };
if (op == "<")
return [=](int s) { return s < arg; };
if (op == "<=")
return [=](int s) { return s <= arg; };
if (op == "=")
return [=](int s) { return s == arg; };
if (op == ":")
return [=](int s) { return s == arg; };
if (op == "!=")
return [=](int s) { return s != arg; };
return [](int) { return false; };
};
search["NumericValue"] = [](const peg::SemanticValues &sv) -> int {
return QString::fromStdString(sv.str()).toInt();
};
search["NumericOperator"] = [](const peg::SemanticValues &sv) -> QString {
return QString::fromStdString(sv.str());
};
search["RegexString"] = [](const peg::SemanticValues &sv) -> StringMatcher {
auto target = sv[0].get<QString>();
return [=](const QString &s) { return s.QString::contains(target, Qt::CaseInsensitive); };
};
search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
return [=](CardData x) { return matcher(x->getText()); };
};
search["ColorQuery"] = [](const peg::SemanticValues &sv) -> Filter {
QString parts;
for (int i = 0; i < sv.size(); ++i) {
parts += sv[i].get<char>();
}
bool idenity = sv.tokens[0].first[0] != 'i';
if (sv.tokens[1].first[0] == ':') {
return [=](CardData x) {
QString match = idenity ? x->getColors() : x->getProperty("coloridentity");
if (parts.contains("m") && match.length() < 2) {
return false;
} else if (parts == "m") {
return true;
}
if (parts.contains("c") && match.length() == 0)
return true;
for (const auto &i : match) {
if (parts.contains(i))
return true;
}
return false;
};
} else {
return [=](CardData x) {
QString match = idenity ? 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;
}
for (const auto &i : match) {
if (!parts.contains(i))
return false;
}
return true;
};
}
};
search["CMCQuery"] = [](const peg::SemanticValues &sv) -> Filter {
NumberMatcher matcher = sv[0].get<NumberMatcher>();
return [=](CardData x) -> bool { return matcher(x->getProperty("cmc").toInt()); };
};
search["PowerQuery"] = [](const peg::SemanticValues &sv) -> Filter {
NumberMatcher matcher = sv[0].get<NumberMatcher>();
return [=](CardData x) -> bool { return matcher(x->getPowTough().split("/")[0].toInt()); };
};
search["ToughnessQuery"] = [](const peg::SemanticValues &sv) -> Filter {
NumberMatcher matcher = sv[0].get<NumberMatcher>();
return [=](CardData x) -> bool {
auto parts = x->getPowTough().split("/");
return matcher(parts.length() == 2 ? parts[1].toInt() : 0);
};
};
search["FieldQuery"] = [](const peg::SemanticValues &sv) -> Filter {
QString field = sv[0].get<QString>();
if (sv.choice() == 0) {
StringMatcher matcher = sv[1].get<StringMatcher>();
return [=](CardData x) -> bool { return x->hasProperty(field) ? matcher(x->getProperty(field)) : false; };
} else {
NumberMatcher matcher = sv[1].get<NumberMatcher>();
return [=](CardData x) -> bool {
return x->hasProperty(field) ? matcher(x->getProperty(field).toInt()) : false;
};
}
};
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
return [=](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 ? sv[0].get<char>() : *sv.c_str();
};
}
FilterString::FilterString(const QString &expr)
{
QByteArray ba = expr.toLocal8Bit();
std::call_once(init, setupParserRules);
_error = QString();
if (ba.isEmpty()) {
result = [](CardData) -> bool { return true; };
return;
}
search.log = [&](size_t ln, size_t col, const std::string &msg) {
_error = QString("%1:%2: %3").arg(ln).arg(col).arg(QString::fromStdString(msg));
};
if (!search.parse(ba.data(), result)) {
std::cout << "Error!" << _error.toStdString() << std::endl;
result = [](CardData) -> bool { return false; };
}
}

View file

@ -0,0 +1,48 @@
#ifndef FILTER_STRING_H
#define FILTER_STRING_H
#include "carddatabase.h"
#include "filtertree.h"
#include <QMap>
#include <QString>
#include <functional>
#include <utility>
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:
explicit FilterString(const QString &exp);
bool check(const CardData &card)
{
return result(card);
}
bool valid()
{
return _error.isEmpty();
}
QString error()
{
return _error;
}
private:
QString _error;
Filter result;
};
#endif

View file

@ -253,6 +253,11 @@ bool FilterItem::acceptCmc(const CardInfoPtr info) const
} }
} }
bool FilterItem::acceptFormat(const CardInfoPtr info) const
{
return info->getProperty(QString("format-%1").arg(term.toLower())) == "legal";
}
bool FilterItem::acceptLoyalty(const CardInfoPtr info) const bool FilterItem::acceptLoyalty(const CardInfoPtr info) const
{ {
if (info->getLoyalty().isEmpty()) { if (info->getLoyalty().isEmpty()) {
@ -400,6 +405,8 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) c
return acceptPowerToughness(info, attr); return acceptPowerToughness(info, attr);
case CardFilter::AttrLoyalty: case CardFilter::AttrLoyalty:
return acceptLoyalty(info); return acceptLoyalty(info);
case CardFilter::AttrFormat:
return acceptFormat(info);
default: default:
return true; /* ignore this attribute */ return true; /* ignore this attribute */
} }
@ -439,16 +446,6 @@ FilterItemList *FilterTree::attrTypeList(CardFilter::Attr attr, CardFilter::Type
return attrLogicMap(attr)->typeList(type); return attrLogicMap(attr)->typeList(type);
} }
int FilterTree::findTermIndex(CardFilter::Attr attr, CardFilter::Type type, const QString &term)
{
return attrTypeList(attr, type)->termIndex(term);
}
int FilterTree::findTermIndex(const CardFilter *f)
{
return findTermIndex(f->attr(), f->type(), f->term());
}
FilterTreeNode *FilterTree::termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term) FilterTreeNode *FilterTree::termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term)
{ {
return attrTypeList(attr, type)->termNode(term); return attrTypeList(attr, type)->termNode(term);
@ -459,11 +456,6 @@ FilterTreeNode *FilterTree::termNode(const CardFilter *f)
return termNode(f->attr(), f->type(), f->term()); return termNode(f->attr(), f->type(), f->term());
} }
FilterTreeNode *FilterTree::attrTypeNode(CardFilter::Attr attr, CardFilter::Type type)
{
return attrTypeList(attr, type);
}
bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm) const bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm) const
{ {
const FilterItemList *fil; const FilterItemList *fil;

View file

@ -208,6 +208,7 @@ public:
bool acceptLoyalty(CardInfoPtr info) const; bool acceptLoyalty(CardInfoPtr info) const;
bool acceptRarity(CardInfoPtr info) const; bool acceptRarity(CardInfoPtr info) const;
bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const; bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const;
bool acceptFormat(CardInfoPtr info) const;
bool relationCheck(int cardInfo) const; bool relationCheck(int cardInfo) const;
}; };
@ -252,11 +253,10 @@ private:
public: public:
FilterTree(); FilterTree();
~FilterTree() override; ~FilterTree() override;
int findTermIndex(CardFilter::Attr attr, CardFilter::Type type, const QString &term);
int findTermIndex(const CardFilter *f);
FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term); FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term);
FilterTreeNode *termNode(const CardFilter *f); FilterTreeNode *termNode(const CardFilter *f);
FilterTreeNode *attrTypeNode(CardFilter::Attr attr, CardFilter::Type type);
const QString text() const override const QString text() const override
{ {
return QString("root"); return QString("root");

View file

@ -21,6 +21,7 @@ QString const ManaCost("manacost");
QString const PowTough("pt"); QString const PowTough("pt");
QString const Side("side"); QString const Side("side");
QString const Layout("layout"); QString const Layout("layout");
QString const ColorIdentity("coloridentity");
inline static const QString getNicePropertyName(QString key) inline static const QString getNicePropertyName(QString key)
{ {
@ -42,6 +43,8 @@ inline static const QString getNicePropertyName(QString key)
return QCoreApplication::translate("Mtg", "Side"); return QCoreApplication::translate("Mtg", "Side");
if (key == Layout) if (key == Layout)
return QCoreApplication::translate("Mtg", "Layout"); return QCoreApplication::translate("Mtg", "Layout");
if (key == ColorIdentity)
return QCoreApplication::translate("Mtg", "Color Identity");
return key; return key;
} }
}; // namespace Mtg }; // namespace Mtg

View file

@ -33,8 +33,10 @@
#include <QPrintPreviewDialog> #include <QPrintPreviewDialog>
#include <QProcessEnvironment> #include <QProcessEnvironment>
#include <QPushButton> #include <QPushButton>
#include <QRegularExpression>
#include <QSignalMapper> #include <QSignalMapper>
#include <QSplitter> #include <QSplitter>
#include <QTextBrowser>
#include <QTextEdit> #include <QTextEdit>
#include <QTextStream> #include <QTextStream>
#include <QTimer> #include <QTimer>
@ -349,6 +351,7 @@ void TabDeckEditor::createCentralFrame()
searchEdit->setPlaceholderText(tr("Search by card name")); searchEdit->setPlaceholderText(tr("Search by card name"));
searchEdit->setClearButtonEnabled(true); searchEdit->setClearButtonEnabled(true);
searchEdit->addAction(QPixmap("theme:icons/search"), QLineEdit::LeadingPosition); searchEdit->addAction(QPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
searchEdit->installEventFilter(&searchKeySignals); searchEdit->installEventFilter(&searchKeySignals);
setFocusProxy(searchEdit); setFocusProxy(searchEdit);
@ -363,6 +366,7 @@ void TabDeckEditor::createCentralFrame()
connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard())); connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard()));
connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard())); connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard()));
connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard())); connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard()));
connect(help, &QAction::triggered, this, &TabDeckEditor::showSearchSyntaxHelp);
databaseModel = new CardDatabaseModel(db, true, this); databaseModel = new CardDatabaseModel(db, true, this);
databaseModel->setObjectName("databaseModel"); databaseModel->setObjectName("databaseModel");
@ -700,7 +704,7 @@ void TabDeckEditor::updateCardInfoRight(const QModelIndex &current, const QModel
void TabDeckEditor::updateSearch(const QString &search) void TabDeckEditor::updateSearch(const QString &search)
{ {
databaseDisplayModel->setCardName(search); databaseDisplayModel->setStringFilter(search);
QModelIndexList sel = databaseView->selectionModel()->selectedRows(); QModelIndexList sel = databaseView->selectionModel()->selectedRows();
if (sel.isEmpty() && databaseDisplayModel->rowCount()) if (sel.isEmpty() && databaseDisplayModel->rowCount())
databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0), databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0),
@ -1212,3 +1216,35 @@ void TabDeckEditor::setSaveStatus(bool newStatus)
aPrintDeck->setEnabled(newStatus); aPrintDeck->setEnabled(newStatus);
analyzeDeckMenu->setEnabled(newStatus); analyzeDeckMenu->setEnabled(newStatus);
} }
void TabDeckEditor::showSearchSyntaxHelp()
{
QFile file("theme:help/search.md");
if (!file.open(QFile::ReadOnly | QFile::Text)) {
return;
}
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("\\[([^\[]+)\\]\\(([^\\)]+)\\)", opts), "<a href=\'\\2\'>\\1</a>");
auto browser = new QTextBrowser;
browser->setParent(this, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint |
Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint);
browser->setWindowTitle("Search Help");
browser->setReadOnly(true);
browser->setMinimumSize({500, 600});
browser->setHtml(text);
connect(browser, &QTextBrowser::anchorClicked, [=](QUrl link) { searchEdit->setText(link.fragment()); });
browser->show();
}

View file

@ -13,7 +13,7 @@ class CardDatabaseModel;
class CardDatabaseDisplayModel; class CardDatabaseDisplayModel;
class DeckListModel; class DeckListModel;
class QTreeView; class QTreeView;
class QTableView;
class CardFrame; class CardFrame;
class QTextEdit; class QTextEdit;
class QLabel; class QLabel;
@ -33,10 +33,10 @@ private:
QTreeView *treeView; QTreeView *treeView;
protected: protected:
void keyPressEvent(QKeyEvent *event); void keyPressEvent(QKeyEvent *event) override;
public: public:
SearchLineEdit() : QLineEdit(), treeView(0) SearchLineEdit() : QLineEdit(), treeView(nullptr)
{ {
} }
void setTreeView(QTreeView *_treeView) void setTreeView(QTreeView *_treeView)
@ -90,12 +90,13 @@ private slots:
void freeDocksSize(); void freeDocksSize();
void refreshShortcuts(); void refreshShortcuts();
bool eventFilter(QObject *o, QEvent *e); bool eventFilter(QObject *o, QEvent *e) override;
void dockVisibleTriggered(); void dockVisibleTriggered();
void dockFloatingTriggered(); void dockFloatingTriggered();
void dockTopLevelChanged(bool topLevel); void dockTopLevelChanged(bool topLevel);
void saveDbHeaderState(); void saveDbHeaderState();
void setSaveStatus(bool newStatus); void setSaveStatus(bool newStatus);
void showSearchSyntaxHelp();
private: private:
CardInfoPtr currentCardInfo() const; CardInfoPtr currentCardInfo() const;
@ -146,10 +147,10 @@ private:
QWidget *centralWidget; QWidget *centralWidget;
public: public:
TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent = 0); explicit TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent = nullptr);
~TabDeckEditor(); ~TabDeckEditor() override;
void retranslateUi(); void retranslateUi() override;
QString getTabText() const; QString getTabText() const override;
void setDeck(DeckLoader *_deckLoader); void setDeck(DeckLoader *_deckLoader);
void setModified(bool _windowModified); void setModified(bool _windowModified);
bool confirmClose(); bool confirmClose();
@ -160,7 +161,7 @@ public:
void createCentralFrame(); void createCentralFrame();
public slots: public slots:
void closeRequest(); void closeRequest() override;
signals: signals:
void deckEditorClosing(TabDeckEditor *tab); void deckEditorClosing(TabDeckEditor *tab);
}; };

View file

@ -98,6 +98,11 @@ CardInfoPtr OracleImporter::addCard(QString name,
sortAndReduceColors(allColors); sortAndReduceColors(allColors);
properties.insert("colors", allColors); properties.insert("colors", allColors);
} }
QString allColorIdent = properties.value("colorIdenity").toString();
if (allColorIdent.size() > 1) {
sortAndReduceColors(allColorIdent);
properties.insert("coloridentity", allColorIdent);
}
// DETECT CARD POSITIONING INFO // DETECT CARD POSITIONING INFO
@ -178,7 +183,7 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList<QVaria
QMap<QString, SplitCardPart> splitCards; QMap<QString, SplitCardPart> splitCards;
QString ptSeparator("/"); QString ptSeparator("/");
QVariantMap card; QVariantMap card;
QString layout, name, text, colors, maintype, power, toughness; QString layout, name, text, colors, colorIdentity, maintype, power, toughness;
bool isToken; bool isToken;
QStringList additionalNames; QStringList additionalNames;
QVariantHash properties; QVariantHash properties;
@ -232,6 +237,11 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList<QVaria
if (!colors.isEmpty()) if (!colors.isEmpty())
properties.insert("colors", colors); properties.insert("colors", colors);
// special handling properties
colorIdentity = card.value("colorIdentity").toStringList().join("");
if (!colorIdentity.isEmpty())
properties.insert("coloridentity", colorIdentity);
maintype = card.value("types").toStringList().first(); maintype = card.value("types").toStringList().first();
if (!maintype.isEmpty()) if (!maintype.isEmpty())
properties.insert("maintype", maintype); properties.insert("maintype", maintype);
@ -242,6 +252,12 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList<QVaria
properties.insert("pt", power + ptSeparator + toughness); properties.insert("pt", power + ptSeparator + toughness);
additionalNames = card.value("names").toStringList(); additionalNames = card.value("names").toStringList();
auto legalities = card.value("legalities").toMap();
for (const QString &fmtName : legalities.keys()) {
properties.insert(QString("format-%1").arg(fmtName), legalities.value(fmtName).toString().toLower());
}
// split cards are considered a single card, enqueue for later merging // split cards are considered a single card, enqueue for later merging
if (layout == "split") { if (layout == "split") {
// get the position of this card part // get the position of this card part

View file

@ -1,6 +1,18 @@
ADD_DEFINITIONS("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"") ADD_DEFINITIONS("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"")
add_executable(carddatabase_test add_executable(carddatabase_test
carddatabase_test.cpp carddatabase_test.cpp
mocks.cpp
../../cockatrice/src/carddatabase.cpp
../../cockatrice/src/carddbparser/carddatabaseparser.cpp
../../cockatrice/src/carddbparser/cockatricexml3.cpp
../../cockatrice/src/carddbparser/cockatricexml4.cpp
)
add_executable(filter_string_test
filter_string_test.cpp
mocks.cpp
../../cockatrice/src/filter_string.cpp
../../cockatrice/src/cardfilter.cpp
../../cockatrice/src/filtertree.cpp
../../cockatrice/src/carddatabase.cpp ../../cockatrice/src/carddatabase.cpp
../../cockatrice/src/carddbparser/carddatabaseparser.cpp ../../cockatrice/src/carddbparser/carddatabaseparser.cpp
../../cockatrice/src/carddbparser/cockatricexml3.cpp ../../cockatrice/src/carddbparser/cockatricexml3.cpp
@ -8,10 +20,15 @@ add_executable(carddatabase_test
) )
if(NOT GTEST_FOUND) if(NOT GTEST_FOUND)
add_dependencies(carddatabase_test gtest) add_dependencies(carddatabase_test gtest)
add_dependencies(filter_string_test gtest)
endif() endif()
find_package(Qt5 COMPONENTS Concurrent Network Widgets REQUIRED) find_package(Qt5 COMPONENTS Concurrent Network Widgets Svg REQUIRED)
set(TEST_QT_MODULES Qt5::Concurrent Qt5::Network Qt5::Widgets) set(TEST_QT_MODULES Qt5::Concurrent Qt5::Network Qt5::Widgets Qt5::Svg)
target_link_libraries(carddatabase_test ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) target_link_libraries(carddatabase_test ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
add_test(NAME carddatabase_test COMMAND carddatabase_test) target_link_libraries(filter_string_test ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
add_test(NAME carddatabase_test COMMAND carddatabase_test)
add_test(NAME filter_string_test COMMAND filter_string_test)

View file

@ -1,57 +1,6 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "carddatabase_test.h" #include "mocks.h"
void CardDatabaseSettings::setSortKey(QString /* shortName */, unsigned int /* sortKey */){};
void CardDatabaseSettings::setEnabled(QString /* shortName */, bool /* enabled */){};
void CardDatabaseSettings::setIsKnown(QString /* shortName */, bool /* isknown */){};
unsigned int CardDatabaseSettings::getSortKey(QString /* shortName */)
{
return 0;
};
bool CardDatabaseSettings::isEnabled(QString /* shortName */)
{
return true;
};
bool CardDatabaseSettings::isKnown(QString /* shortName */)
{
return true;
};
SettingsCache::SettingsCache()
{
cardDatabaseSettings = new CardDatabaseSettings();
};
SettingsCache::~SettingsCache()
{
delete cardDatabaseSettings;
};
QString SettingsCache::getCustomCardDatabasePath() const
{
return QString("%1/customsets/").arg(CARDDB_DATADIR);
}
QString SettingsCache::getCardDatabasePath() const
{
return QString("%1/cards.xml").arg(CARDDB_DATADIR);
}
QString SettingsCache::getTokenDatabasePath() const
{
return QString("%1/tokens.xml").arg(CARDDB_DATADIR);
}
QString SettingsCache::getSpoilerCardDatabasePath() const
{
return QString("%1/spoiler.xml").arg(CARDDB_DATADIR);
}
CardDatabaseSettings &SettingsCache::cardDatabase() const
{
return *cardDatabaseSettings;
}
SettingsCache *settingsCache;
void PictureLoader::clearPixmapCache(CardInfoPtr /* card */)
{
}
namespace namespace
{ {

View file

@ -0,0 +1,66 @@
#include "gtest/gtest.h"
#include "../cockatrice/src/filter_string.h"
#include "mocks.h"
#include <cmath>
CardDatabase *db;
#define Query(name, card, query, match) \
TEST_F(CardQuery, name) {\
ASSERT_EQ(FilterString(query).check(card), match);\
}
namespace
{
class CardQuery : public ::testing::Test {
protected:
void SetUp() override {
cat = db->getCardBySimpleName("Cat");
}
// void TearDown() override {}
CardData cat;
};
Query(Empty, cat, "", true)
Query(Typing, cat, "t", true)
Query(NonMatchingType, cat, "t:kithkin", false)
Query(MatchingType, cat, "t:creature", true)
Query(Not1, cat, "not t:kithkin", true)
Query(Not2, cat, "not t:creature", false)
Query(Case, cat, "t:cReAtUrE", true)
Query(And, cat, "t:creature t:creature", true)
Query(And2, cat, "t:creature t:sorcery", false)
Query(Or, cat, "t:bat or t:creature", true)
Query(Cmc1, cat, "cmc=2", true)
Query(Cmc2, cat, "cmc>3", false)
Query(Cmc3, cat, "cmc>1", true)
Query(Quotes, cat, "t:\"creature\"", true);
Query(Field, cat, "pt:\"3/3\"", true)
Query(Color1, cat, "c:g", true);
Query(Color2, cat, "c:gw", true);
Query(Color3, cat, "c!g", true);
Query(Color4, cat, "c!gw", false);
} // namespace
int main(int argc, char **argv)
{
settingsCache = new SettingsCache;
db = new CardDatabase;
db->loadCardDatabases();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -0,0 +1,54 @@
#include "mocks.h"
void CardDatabaseSettings::setSortKey(QString /* shortName */, unsigned int /* sortKey */){};
void CardDatabaseSettings::setEnabled(QString /* shortName */, bool /* enabled */){};
void CardDatabaseSettings::setIsKnown(QString /* shortName */, bool /* isknown */){};
unsigned int CardDatabaseSettings::getSortKey(QString /* shortName */)
{
return 0;
};
bool CardDatabaseSettings::isEnabled(QString /* shortName */)
{
return true;
};
bool CardDatabaseSettings::isKnown(QString /* shortName */)
{
return true;
};
SettingsCache::SettingsCache()
{
cardDatabaseSettings = new CardDatabaseSettings();
};
SettingsCache::~SettingsCache()
{
delete cardDatabaseSettings;
};
QString SettingsCache::getCustomCardDatabasePath() const
{
return QString("%1/customsets/").arg(CARDDB_DATADIR);
}
QString SettingsCache::getCardDatabasePath() const
{
return QString("%1/cards.xml").arg(CARDDB_DATADIR);
}
QString SettingsCache::getTokenDatabasePath() const
{
return QString("%1/tokens.xml").arg(CARDDB_DATADIR);
}
QString SettingsCache::getSpoilerCardDatabasePath() const
{
return QString("%1/spoiler.xml").arg(CARDDB_DATADIR);
}
CardDatabaseSettings &SettingsCache::cardDatabase() const
{
return *cardDatabaseSettings;
}
void PictureLoader::clearPixmapCache(CardInfoPtr /* card */)
{
}
SettingsCache *settingsCache;

View file

@ -10,6 +10,7 @@
#define SETTINGSCACHE_H #define SETTINGSCACHE_H
class CardDatabaseSettings class CardDatabaseSettings
{ {
public: public:
@ -40,6 +41,8 @@ signals:
void cardDatabasePathChanged(); void cardDatabasePathChanged();
}; };
extern SettingsCache *settingsCache;
#define PICTURELOADER_H #define PICTURELOADER_H
class PictureLoader class PictureLoader