mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-27 00:53:55 -07:00
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
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue