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:
parent
4427ad1451
commit
eb60fec8e2
24 changed files with 780 additions and 122 deletions
|
@ -123,6 +123,7 @@ SET(cockatrice_SOURCES
|
|||
src/carddbparser/carddatabaseparser.cpp
|
||||
src/carddbparser/cockatricexml3.cpp
|
||||
src/carddbparser/cockatricexml4.cpp
|
||||
src/filter_string.cpp
|
||||
${VERSION_STRING_CPP}
|
||||
)
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<file>resources/icons/delete.svg</file>
|
||||
<file>resources/icons/forgot_password.svg</file>
|
||||
<file>resources/icons/increment.svg</file>
|
||||
<file>resources/icons/info.svg</file>
|
||||
<file>resources/icons/lock.svg</file>
|
||||
<file>resources/icons/not_ready_start.svg</file>
|
||||
<file>resources/icons/pencil.svg</file>
|
||||
|
@ -360,5 +361,6 @@
|
|||
<file>resources/tips/images/themes.png</file>
|
||||
<file>resources/tips/images/tip_of_the_day.png</file>
|
||||
|
||||
<file>resources/help/search.md</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
60
cockatrice/resources/help/search.md
Normal file
60
cockatrice/resources/help/search.md
Normal 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>
|
43
cockatrice/resources/icons/info.svg
Normal file
43
cockatrice/resources/icons/info.svg
Normal 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 |
|
@ -21,7 +21,6 @@ class CardRelation;
|
|||
class ICardDatabaseParser;
|
||||
|
||||
typedef QMap<QString, QString> QStringMap;
|
||||
typedef QMap<QString, int> MuidMap;
|
||||
typedef QSharedPointer<CardInfo> CardInfoPtr;
|
||||
typedef QSharedPointer<CardSet> CardSetPtr;
|
||||
typedef QMap<QString, CardInfoPerSet> CardInfoPerSetMap;
|
||||
|
@ -248,6 +247,10 @@ public:
|
|||
properties.insert(_name, _value);
|
||||
emit cardInfoChanged(smartThis);
|
||||
}
|
||||
bool hasProperty(const QString &propertyName) const
|
||||
{
|
||||
return properties.contains(propertyName);
|
||||
}
|
||||
const CardInfoPerSetMap &getSets() const
|
||||
{
|
||||
return sets;
|
||||
|
|
|
@ -143,12 +143,16 @@ void CardDatabaseModel::cardRemoved(CardInfoPtr card)
|
|||
endRemoveRows();
|
||||
}
|
||||
|
||||
CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent) : QSortFilterProxyModel(parent), isToken(ShowAll)
|
||||
CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent), isToken(ShowAll), filterString(nullptr)
|
||||
{
|
||||
filterTree = nullptr;
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
|
||||
dirtyTimer.setSingleShot(true);
|
||||
connect(&dirtyTimer, &QTimer::timeout, this, &CardDatabaseDisplayModel::invalidate);
|
||||
|
||||
loadedRowCount = 0;
|
||||
}
|
||||
|
||||
|
@ -285,6 +289,13 @@ bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex
|
|||
if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken()))
|
||||
return false;
|
||||
|
||||
if (filterString != nullptr) {
|
||||
if (filterTree != nullptr && !filterTree->acceptsCard(info)) {
|
||||
return false;
|
||||
}
|
||||
return filterString->check(info);
|
||||
}
|
||||
|
||||
return rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
#define CARDDATABASEMODEL_H
|
||||
|
||||
#include "carddatabase.h"
|
||||
#include "filter_string.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTimer>
|
||||
|
||||
class FilterTree;
|
||||
|
||||
|
@ -67,11 +69,12 @@ public:
|
|||
|
||||
private:
|
||||
FilterBool isToken;
|
||||
QString cardNameBeginning, cardName, cardText;
|
||||
QString searchTerm;
|
||||
QString cardName, cardText;
|
||||
QSet<QString> cardNameSet, cardTypes, cardColors;
|
||||
FilterTree *filterTree;
|
||||
FilterString *filterString;
|
||||
int loadedRowCount;
|
||||
QTimer dirtyTimer;
|
||||
|
||||
/** The translation table that will be used for sanitizeCardName. */
|
||||
static QMap<wchar_t, wchar_t> characterTranslation;
|
||||
|
@ -82,41 +85,33 @@ public:
|
|||
void setIsToken(FilterBool _isToken)
|
||||
{
|
||||
isToken = _isToken;
|
||||
invalidate();
|
||||
}
|
||||
void setCardNameBeginning(const QString &_beginning)
|
||||
{
|
||||
cardNameBeginning = _beginning;
|
||||
invalidate();
|
||||
dirty();
|
||||
}
|
||||
|
||||
void setCardName(const QString &_cardName)
|
||||
{
|
||||
if (filterString != nullptr) {
|
||||
delete filterString;
|
||||
filterString = nullptr;
|
||||
}
|
||||
cardName = sanitizeCardName(_cardName, characterTranslation);
|
||||
invalidate();
|
||||
dirty();
|
||||
}
|
||||
void setStringFilter(const QString &_src)
|
||||
{
|
||||
delete filterString;
|
||||
filterString = new FilterString(_src);
|
||||
dirty();
|
||||
}
|
||||
void setCardNameSet(const QSet<QString> &_cardNameSet)
|
||||
{
|
||||
cardNameSet = _cardNameSet;
|
||||
invalidate();
|
||||
dirty();
|
||||
}
|
||||
void setSearchTerm(const QString &_searchTerm)
|
||||
|
||||
void dirty()
|
||||
{
|
||||
searchTerm = _searchTerm;
|
||||
}
|
||||
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();
|
||||
dirtyTimer.start(20);
|
||||
}
|
||||
void clearFilterAll();
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
|
|
@ -41,6 +41,8 @@ const QString CardFilter::attrName(Attr a)
|
|||
return tr("Toughness");
|
||||
case AttrLoyalty:
|
||||
return tr("Loyalty");
|
||||
case AttrFormat:
|
||||
return tr("Format");
|
||||
default:
|
||||
return QString("");
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <utility>
|
||||
|
||||
class CardFilter : public QObject
|
||||
{
|
||||
|
@ -18,7 +19,7 @@ public:
|
|||
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 */
|
||||
enum Attr
|
||||
{
|
||||
|
@ -33,6 +34,7 @@ public:
|
|||
AttrText,
|
||||
AttrTough,
|
||||
AttrType,
|
||||
AttrFormat,
|
||||
AttrEnd
|
||||
};
|
||||
|
||||
|
@ -42,7 +44,7 @@ private:
|
|||
enum Attr a;
|
||||
|
||||
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
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ CardInfoText::CardInfoText(QWidget *parent) : QFrame(parent), info(nullptr)
|
|||
textLabel = new QTextEdit();
|
||||
textLabel->setReadOnly(true);
|
||||
|
||||
QGridLayout *grid = new QGridLayout(this);
|
||||
auto *grid = new QGridLayout(this);
|
||||
grid->addWidget(nameLabel, 0, 0);
|
||||
grid->addWidget(textLabel, 1, 0, -1, 2);
|
||||
grid->setRowStretch(1, 1);
|
||||
|
@ -39,6 +39,8 @@ void CardInfoText::setCard(CardInfoPtr card)
|
|||
|
||||
QStringList cardProps = card->getProperties();
|
||||
foreach (QString key, cardProps) {
|
||||
if (key.contains("-"))
|
||||
continue;
|
||||
QString keyText = Mtg::getNicePropertyName(key).toHtmlEscaped() + ":";
|
||||
text +=
|
||||
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 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:"));
|
||||
|
||||
for (int i = 0; i < relatedCards.size(); ++i) {
|
||||
QString tmp = relatedCards.at(i)->getName().toHtmlEscaped();
|
||||
for (auto *relatedCard : relatedCards) {
|
||||
QString tmp = relatedCard->getName().toHtmlEscaped();
|
||||
text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>";
|
||||
}
|
||||
|
||||
for (int i = 0; i < reverserelatedCards2Me.size(); ++i) {
|
||||
QString tmp = reverserelatedCards2Me.at(i)->getName().toHtmlEscaped();
|
||||
for (auto *i : reverserelatedCards2Me) {
|
||||
QString tmp = i->getName().toHtmlEscaped();
|
||||
text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>";
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ private:
|
|||
CardInfoPtr info;
|
||||
|
||||
public:
|
||||
CardInfoText(QWidget *parent = 0);
|
||||
explicit CardInfoText(QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
void setInvalidCardName(const QString &cardName);
|
||||
|
||||
|
|
352
cockatrice/src/filter_string.cpp
Normal file
352
cockatrice/src/filter_string.cpp
Normal 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; };
|
||||
}
|
||||
}
|
48
cockatrice/src/filter_string.h
Normal file
48
cockatrice/src/filter_string.h
Normal 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
|
|
@ -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
|
||||
{
|
||||
if (info->getLoyalty().isEmpty()) {
|
||||
|
@ -400,6 +405,8 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) c
|
|||
return acceptPowerToughness(info, attr);
|
||||
case CardFilter::AttrLoyalty:
|
||||
return acceptLoyalty(info);
|
||||
case CardFilter::AttrFormat:
|
||||
return acceptFormat(info);
|
||||
default:
|
||||
return true; /* ignore this attribute */
|
||||
}
|
||||
|
@ -439,16 +446,6 @@ FilterItemList *FilterTree::attrTypeList(CardFilter::Attr attr, CardFilter::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)
|
||||
{
|
||||
return attrTypeList(attr, type)->termNode(term);
|
||||
|
@ -459,11 +456,6 @@ FilterTreeNode *FilterTree::termNode(const CardFilter *f)
|
|||
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
|
||||
{
|
||||
const FilterItemList *fil;
|
||||
|
|
|
@ -208,6 +208,7 @@ public:
|
|||
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;
|
||||
};
|
||||
|
||||
|
@ -252,11 +253,10 @@ private:
|
|||
public:
|
||||
FilterTree();
|
||||
~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(const CardFilter *f);
|
||||
FilterTreeNode *attrTypeNode(CardFilter::Attr attr, CardFilter::Type type);
|
||||
|
||||
const QString text() const override
|
||||
{
|
||||
return QString("root");
|
||||
|
|
|
@ -21,6 +21,7 @@ QString const ManaCost("manacost");
|
|||
QString const PowTough("pt");
|
||||
QString const Side("side");
|
||||
QString const Layout("layout");
|
||||
QString const ColorIdentity("coloridentity");
|
||||
|
||||
inline static const QString getNicePropertyName(QString key)
|
||||
{
|
||||
|
@ -42,6 +43,8 @@ inline static const QString getNicePropertyName(QString key)
|
|||
return QCoreApplication::translate("Mtg", "Side");
|
||||
if (key == Layout)
|
||||
return QCoreApplication::translate("Mtg", "Layout");
|
||||
if (key == ColorIdentity)
|
||||
return QCoreApplication::translate("Mtg", "Color Identity");
|
||||
return key;
|
||||
}
|
||||
}; // namespace Mtg
|
||||
|
|
|
@ -33,8 +33,10 @@
|
|||
#include <QPrintPreviewDialog>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QSignalMapper>
|
||||
#include <QSplitter>
|
||||
#include <QTextBrowser>
|
||||
#include <QTextEdit>
|
||||
#include <QTextStream>
|
||||
#include <QTimer>
|
||||
|
@ -349,6 +351,7 @@ void TabDeckEditor::createCentralFrame()
|
|||
searchEdit->setPlaceholderText(tr("Search by card name"));
|
||||
searchEdit->setClearButtonEnabled(true);
|
||||
searchEdit->addAction(QPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
|
||||
auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
|
||||
searchEdit->installEventFilter(&searchKeySignals);
|
||||
|
||||
setFocusProxy(searchEdit);
|
||||
|
@ -363,6 +366,7 @@ void TabDeckEditor::createCentralFrame()
|
|||
connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard()));
|
||||
connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard()));
|
||||
connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard()));
|
||||
connect(help, &QAction::triggered, this, &TabDeckEditor::showSearchSyntaxHelp);
|
||||
|
||||
databaseModel = new CardDatabaseModel(db, true, this);
|
||||
databaseModel->setObjectName("databaseModel");
|
||||
|
@ -700,7 +704,7 @@ void TabDeckEditor::updateCardInfoRight(const QModelIndex ¤t, const QModel
|
|||
|
||||
void TabDeckEditor::updateSearch(const QString &search)
|
||||
{
|
||||
databaseDisplayModel->setCardName(search);
|
||||
databaseDisplayModel->setStringFilter(search);
|
||||
QModelIndexList sel = databaseView->selectionModel()->selectedRows();
|
||||
if (sel.isEmpty() && databaseDisplayModel->rowCount())
|
||||
databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0),
|
||||
|
@ -1212,3 +1216,35 @@ void TabDeckEditor::setSaveStatus(bool newStatus)
|
|||
aPrintDeck->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();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ class CardDatabaseModel;
|
|||
class CardDatabaseDisplayModel;
|
||||
class DeckListModel;
|
||||
class QTreeView;
|
||||
class QTableView;
|
||||
|
||||
class CardFrame;
|
||||
class QTextEdit;
|
||||
class QLabel;
|
||||
|
@ -33,10 +33,10 @@ private:
|
|||
QTreeView *treeView;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
public:
|
||||
SearchLineEdit() : QLineEdit(), treeView(0)
|
||||
SearchLineEdit() : QLineEdit(), treeView(nullptr)
|
||||
{
|
||||
}
|
||||
void setTreeView(QTreeView *_treeView)
|
||||
|
@ -90,12 +90,13 @@ private slots:
|
|||
void freeDocksSize();
|
||||
void refreshShortcuts();
|
||||
|
||||
bool eventFilter(QObject *o, QEvent *e);
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
void dockVisibleTriggered();
|
||||
void dockFloatingTriggered();
|
||||
void dockTopLevelChanged(bool topLevel);
|
||||
void saveDbHeaderState();
|
||||
void setSaveStatus(bool newStatus);
|
||||
void showSearchSyntaxHelp();
|
||||
|
||||
private:
|
||||
CardInfoPtr currentCardInfo() const;
|
||||
|
@ -146,10 +147,10 @@ private:
|
|||
QWidget *centralWidget;
|
||||
|
||||
public:
|
||||
TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent = 0);
|
||||
~TabDeckEditor();
|
||||
void retranslateUi();
|
||||
QString getTabText() const;
|
||||
explicit TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent = nullptr);
|
||||
~TabDeckEditor() override;
|
||||
void retranslateUi() override;
|
||||
QString getTabText() const override;
|
||||
void setDeck(DeckLoader *_deckLoader);
|
||||
void setModified(bool _windowModified);
|
||||
bool confirmClose();
|
||||
|
@ -160,7 +161,7 @@ public:
|
|||
void createCentralFrame();
|
||||
|
||||
public slots:
|
||||
void closeRequest();
|
||||
void closeRequest() override;
|
||||
signals:
|
||||
void deckEditorClosing(TabDeckEditor *tab);
|
||||
};
|
||||
|
|
|
@ -98,6 +98,11 @@ CardInfoPtr OracleImporter::addCard(QString name,
|
|||
sortAndReduceColors(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
|
||||
|
||||
|
@ -178,7 +183,7 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList<QVaria
|
|||
QMap<QString, SplitCardPart> splitCards;
|
||||
QString ptSeparator("/");
|
||||
QVariantMap card;
|
||||
QString layout, name, text, colors, maintype, power, toughness;
|
||||
QString layout, name, text, colors, colorIdentity, maintype, power, toughness;
|
||||
bool isToken;
|
||||
QStringList additionalNames;
|
||||
QVariantHash properties;
|
||||
|
@ -232,6 +237,11 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList<QVaria
|
|||
if (!colors.isEmpty())
|
||||
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();
|
||||
if (!maintype.isEmpty())
|
||||
properties.insert("maintype", maintype);
|
||||
|
@ -242,6 +252,12 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList<QVaria
|
|||
properties.insert("pt", power + ptSeparator + toughness);
|
||||
|
||||
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
|
||||
if (layout == "split") {
|
||||
// get the position of this card part
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
ADD_DEFINITIONS("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"")
|
||||
add_executable(carddatabase_test
|
||||
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/carddbparser/carddatabaseparser.cpp
|
||||
../../cockatrice/src/carddbparser/cockatricexml3.cpp
|
||||
|
@ -8,10 +20,15 @@ add_executable(carddatabase_test
|
|||
)
|
||||
if(NOT GTEST_FOUND)
|
||||
add_dependencies(carddatabase_test gtest)
|
||||
add_dependencies(filter_string_test gtest)
|
||||
endif()
|
||||
|
||||
find_package(Qt5 COMPONENTS Concurrent Network Widgets REQUIRED)
|
||||
set(TEST_QT_MODULES Qt5::Concurrent Qt5::Network Qt5::Widgets)
|
||||
find_package(Qt5 COMPONENTS Concurrent Network Widgets Svg REQUIRED)
|
||||
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(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)
|
|
@ -1,57 +1,6 @@
|
|||
#include "gtest/gtest.h"
|
||||
|
||||
#include "carddatabase_test.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 */)
|
||||
{
|
||||
}
|
||||
#include "mocks.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
|
66
tests/carddatabase/filter_string_test.cpp
Normal file
66
tests/carddatabase/filter_string_test.cpp
Normal 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();
|
||||
}
|
54
tests/carddatabase/mocks.cpp
Normal file
54
tests/carddatabase/mocks.cpp
Normal 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;
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#define SETTINGSCACHE_H
|
||||
|
||||
|
||||
class CardDatabaseSettings
|
||||
{
|
||||
public:
|
||||
|
@ -40,6 +41,8 @@ signals:
|
|||
void cardDatabasePathChanged();
|
||||
};
|
||||
|
||||
extern SettingsCache *settingsCache;
|
||||
|
||||
#define PICTURELOADER_H
|
||||
|
||||
class PictureLoader
|
Loading…
Reference in a new issue