mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-11 20:43:25 -08:00
Merge upstream/master into scryfall-tagger-tags
Conflict resolution: keep both the tag database and the upstream database writer in the oracle test targets and both includes in the importer test; restore the tags.svg resource entry that the rerere replay dropped. Upstream's icon rules also landed mid-branch: use loadThemedIcon() for the tag quick-filter button and re-author tags.svg as a 24x24 currentColor icon so IconAssets.AssetsFollowTheRules passes.
This commit is contained in:
commit
f154335f5c
553 changed files with 21045 additions and 11980 deletions
|
|
@ -1,10 +1,10 @@
|
|||
set(HEADERS libcockatrice/filters/filter_card.h libcockatrice/filters/filter_string.h
|
||||
libcockatrice/filters/filter_tree.h
|
||||
set(HEADERS libcockatrice/filters/filter_card.h libcockatrice/filters/filter_mode.h
|
||||
libcockatrice/filters/filter_string.h libcockatrice/filters/filter_tree.h
|
||||
)
|
||||
|
||||
add_library(
|
||||
libcockatrice_filters STATIC ${HEADERS} libcockatrice/filters/filter_card.cpp libcockatrice/filters/filter_string.cpp
|
||||
libcockatrice/filters/filter_tree.cpp
|
||||
libcockatrice_filters STATIC ${HEADERS} libcockatrice/filters/filter_card.cpp libcockatrice/filters/filter_mode.cpp
|
||||
libcockatrice/filters/filter_string.cpp libcockatrice/filters/filter_tree.cpp
|
||||
)
|
||||
|
||||
add_dependencies(libcockatrice_filters libcockatrice_card)
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ const QString CardFilter::attrName(Attr a)
|
|||
return tr("Type");
|
||||
case AttrColor:
|
||||
return tr("Color");
|
||||
case AttrColorIdentity:
|
||||
return tr("Color Identity");
|
||||
case AttrText:
|
||||
return tr("Text");
|
||||
case AttrSet:
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public:
|
|||
{
|
||||
AttrCmc = 0,
|
||||
AttrColor,
|
||||
AttrColorIdentity,
|
||||
AttrLoyalty,
|
||||
AttrManaCost,
|
||||
AttrName,
|
||||
|
|
|
|||
77
libcockatrice_filters/libcockatrice/filters/filter_mode.cpp
Normal file
77
libcockatrice_filters/libcockatrice/filters/filter_mode.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @file filter_mode.cpp
|
||||
* @ingroup Filters
|
||||
*/
|
||||
|
||||
#include "libcockatrice/filters/filter_mode.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
FilterModeInfo getFilterModeInfo(FilterMode filterMode)
|
||||
{
|
||||
switch (filterMode) {
|
||||
case FilterMode::Includes:
|
||||
return {CardFilter::Type::TypeOr, CardFilter::Type::TypeEnd, CardFilter::Attr::AttrColor};
|
||||
case FilterMode::Excludes:
|
||||
return {CardFilter::Type::TypeAndNot, CardFilter::Type::TypeEnd, CardFilter::Attr::AttrColor};
|
||||
case FilterMode::IncludeExclude:
|
||||
return {CardFilter::Type::TypeOr, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor};
|
||||
case FilterMode::ExactMatch:
|
||||
return {CardFilter::Type::TypeAnd, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor};
|
||||
case FilterMode::ColorIdentity:
|
||||
return {CardFilter::Type::TypeEnd, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColorIdentity};
|
||||
case FilterMode::Custom:
|
||||
case FilterMode::End:
|
||||
break;
|
||||
}
|
||||
return {CardFilter::Type::TypeEnd, CardFilter::Type::TypeEnd, CardFilter::Attr::AttrEnd};
|
||||
}
|
||||
|
||||
FilterMode deriveFilterMode(const QList<const CardFilter *> &filters, FilterMode currentMode)
|
||||
{
|
||||
constexpr int modeCount = static_cast<int>(FilterMode::End);
|
||||
bool modeImpossible[modeCount] = {};
|
||||
const QString colors = "WUBRG";
|
||||
|
||||
for (int i = 0; i < modeCount; ++i) {
|
||||
const FilterModeInfo modeInfo = getFilterModeInfo(static_cast<FilterMode>(i));
|
||||
|
||||
// Remove the modes that cannot create one of the filters.
|
||||
for (const CardFilter *filter : filters) {
|
||||
if (filter->attr() != modeInfo.attr ||
|
||||
(filter->type() != modeInfo.includeType && filter->type() != modeInfo.excludeType)) {
|
||||
modeImpossible[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Modes that have both an include and an exclude filter should always have a filter for each color.
|
||||
if (modeInfo.includeType != CardFilter::Type::TypeEnd && modeInfo.excludeType != CardFilter::Type::TypeEnd &&
|
||||
!modeImpossible[i]) {
|
||||
for (const QChar &color : colors) {
|
||||
int count = 0;
|
||||
for (const CardFilter *filter : filters) {
|
||||
count += filter->term() == color;
|
||||
}
|
||||
if (count != 1) {
|
||||
modeImpossible[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the current mode if it can produce the filters, so that a live mode is never swapped for another.
|
||||
// Switching to and from the custom mode is always possible.
|
||||
if (!modeImpossible[static_cast<int>(currentMode)] && currentMode != FilterMode::Custom) {
|
||||
return currentMode;
|
||||
}
|
||||
|
||||
for (int i = 0; i < modeCount; ++i) {
|
||||
if (!modeImpossible[i] || i == static_cast<int>(FilterMode::Custom)) {
|
||||
return static_cast<FilterMode>(i);
|
||||
}
|
||||
}
|
||||
|
||||
return FilterMode::Custom;
|
||||
}
|
||||
62
libcockatrice_filters/libcockatrice/filters/filter_mode.h
Normal file
62
libcockatrice_filters/libcockatrice/filters/filter_mode.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* @file filter_mode.h
|
||||
* @ingroup Filters
|
||||
*/
|
||||
|
||||
#ifndef LIBCOCKATRICE_FILTERS_FILTER_MODE_H
|
||||
#define LIBCOCKATRICE_FILTERS_FILTER_MODE_H
|
||||
|
||||
#include "libcockatrice/filters/filter_card.h"
|
||||
|
||||
#include <QChar>
|
||||
#include <QList>
|
||||
#include <QMetaType>
|
||||
|
||||
/**
|
||||
* @brief The way selected and unselected colors are combined into card filters.
|
||||
*/
|
||||
enum class FilterMode
|
||||
{
|
||||
Includes = 0, // Include selected colors (OR condition).
|
||||
Excludes, // Exclude selected colors (AND NOT).
|
||||
IncludeExclude, // Include selected colors (OR) and exclude unselected colors (AND NOT).
|
||||
ExactMatch, // Only selected colors are included, all others are excluded.
|
||||
ColorIdentity, // Keep cards whose color identity is a subset of the selected colors (stored as AND NOT for each
|
||||
// unselected color).
|
||||
Custom, // The color filters do not match any of the above modes. Not included in the combo box.
|
||||
End
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(FilterMode)
|
||||
|
||||
/**
|
||||
* @brief The filters a single color contributes in a given mode.
|
||||
*/
|
||||
struct FilterModeInfo
|
||||
{
|
||||
// When a color symbol is enabled, add filters of this type. Skipped if equal to CardFilter::Type::TypeEnd.
|
||||
CardFilter::Type includeType;
|
||||
|
||||
// When a color symbol is disabled, add filters of this type. Skipped if equal to CardFilter::Type::TypeEnd.
|
||||
CardFilter::Type excludeType;
|
||||
|
||||
// The attribute to filter for when adding the filters.
|
||||
CardFilter::Attr attr;
|
||||
};
|
||||
|
||||
/** @brief Return the filters the given mode contributes for one color. */
|
||||
FilterModeInfo getFilterModeInfo(FilterMode filterMode);
|
||||
|
||||
/**
|
||||
* @brief Determine the mode that produced the given color filters.
|
||||
*
|
||||
* Returns @p currentMode unchanged when that mode can still produce @p filters, so that a live mode is never
|
||||
* replaced by an equally valid one. Otherwise returns the first mode that can produce them, falling back to
|
||||
* FilterMode::Custom when none can.
|
||||
*
|
||||
* @param filters The filters on CardFilter::Attr::AttrColor and CardFilter::Attr::AttrColorIdentity.
|
||||
* @param currentMode The mode currently selected in the UI.
|
||||
*/
|
||||
FilterMode deriveFilterMode(const QList<const CardFilter *> &filters, FilterMode currentMode);
|
||||
|
||||
#endif // LIBCOCKATRICE_FILTERS_FILTER_MODE_H
|
||||
|
|
@ -425,7 +425,7 @@ static void setupParserRules()
|
|||
const bool identity = sv.tokens[0].empty() || sv.tokens[0][0] != 'i';
|
||||
if (sv.tokens[1][0] == ':') {
|
||||
return [=](const CardData &x) {
|
||||
const QString match = identity ? x->getColors() : x->getProperty("coloridentity");
|
||||
const QString match = identity ? x->getColors() : x->getColorIdentity();
|
||||
if (parts.contains("m") && match.length() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -443,7 +443,7 @@ static void setupParserRules()
|
|||
}
|
||||
|
||||
return [=](const CardData &x) {
|
||||
const QString match = identity ? x->getColors() : x->getProperty("colorIdentity");
|
||||
const QString match = identity ? x->getColors() : x->getColorIdentity();
|
||||
if (parts.contains("m") && match.length() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -522,6 +522,14 @@ FilterString::FilterString()
|
|||
_error = "Not initialized";
|
||||
}
|
||||
|
||||
QString FilterString::error() const
|
||||
{
|
||||
if (_errorColumn == 0) {
|
||||
return _error;
|
||||
}
|
||||
return tr("Error at position %1: %2").arg(_errorColumn).arg(_error);
|
||||
}
|
||||
|
||||
FilterString::FilterString(const QString &expr, const CardSearchLanguage &searchLanguage)
|
||||
{
|
||||
QByteArray ba = expr.simplified().toUtf8();
|
||||
|
|
@ -538,7 +546,8 @@ FilterString::FilterString(const QString &expr, const CardSearchLanguage &search
|
|||
searchLanguageContext = searchLanguage;
|
||||
|
||||
search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) {
|
||||
_error = QString("Error at position %1: %2").arg(col).arg(QString::fromStdString(msg));
|
||||
_error = QString::fromStdString(msg);
|
||||
_errorColumn = col;
|
||||
});
|
||||
|
||||
if (!search.parse(ba.data(), result)) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef FILTER_STRING_H
|
||||
#define FILTER_STRING_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
|
|
@ -33,6 +34,8 @@ typedef AstBase<EmptyType> Ast;
|
|||
|
||||
class FilterString
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(FilterString)
|
||||
|
||||
public:
|
||||
FilterString();
|
||||
explicit FilterString(const QString &exp, const CardSearchLanguage &searchLanguage = {});
|
||||
|
|
@ -45,18 +48,18 @@ public:
|
|||
return result(card);
|
||||
}
|
||||
|
||||
bool valid()
|
||||
[[nodiscard]] bool valid() const
|
||||
{
|
||||
return _error.isEmpty();
|
||||
}
|
||||
|
||||
QString error()
|
||||
{
|
||||
return _error;
|
||||
}
|
||||
[[nodiscard]] QString error() const;
|
||||
|
||||
private:
|
||||
// Untranslated parser message; error() adds the translated prefix, logging uses it as-is.
|
||||
QString _error;
|
||||
// Column reported by the parser, or 0 when the message carries no position.
|
||||
size_t _errorColumn = 0;
|
||||
Filter result;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -177,23 +177,24 @@ FilterItem::FilterItem(QString trm, FilterItemList *parent) : p(parent), term(st
|
|||
lowercaseTerm = term.toLower();
|
||||
|
||||
switch (attr()) {
|
||||
case CardFilter::AttrColor: {
|
||||
QString converted = term.trimmed();
|
||||
case CardFilter::AttrColor:
|
||||
case CardFilter::AttrColorIdentity: {
|
||||
QString converted = term.trimmed().toLower();
|
||||
|
||||
converted.replace("green", "g", Qt::CaseInsensitive);
|
||||
converted.replace("grn", "g", Qt::CaseInsensitive);
|
||||
converted.replace("blue", "u", Qt::CaseInsensitive);
|
||||
converted.replace("blu", "u", Qt::CaseInsensitive);
|
||||
converted.replace("black", "b", Qt::CaseInsensitive);
|
||||
converted.replace("blk", "b", Qt::CaseInsensitive);
|
||||
converted.replace("red", "r", Qt::CaseInsensitive);
|
||||
converted.replace("white", "w", Qt::CaseInsensitive);
|
||||
converted.replace("wht", "w", Qt::CaseInsensitive);
|
||||
converted.replace("colorless", "c", Qt::CaseInsensitive);
|
||||
converted.replace("colourless", "c", Qt::CaseInsensitive);
|
||||
converted.replace("none", "c", Qt::CaseInsensitive);
|
||||
converted.replace("green", "g");
|
||||
converted.replace("grn", "g");
|
||||
converted.replace("blue", "u");
|
||||
converted.replace("blu", "u");
|
||||
converted.replace("black", "b");
|
||||
converted.replace("blk", "b");
|
||||
converted.replace("red", "r");
|
||||
converted.replace("white", "w");
|
||||
converted.replace("wht", "w");
|
||||
converted.replace("colorless", "c");
|
||||
converted.replace("colourless", "c");
|
||||
converted.replace("none", "c");
|
||||
|
||||
converted.replace(QString(" "), QString(""), Qt::CaseInsensitive);
|
||||
converted.replace(QString(" "), QString(""));
|
||||
|
||||
convertedColorTerm = converted;
|
||||
break;
|
||||
|
|
@ -359,12 +360,17 @@ bool FilterItem::acceptSubType(const CardInfoPtr &info) const
|
|||
return subType.contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptColor(const CardInfoPtr &info) const
|
||||
/**
|
||||
* @brief Return true when every letter of the given term appears in the given colors string.
|
||||
* If the term is equal to "c", then only return true if the color string is empty.
|
||||
*
|
||||
* @param term The letters which should be included, or "c"
|
||||
* @param colors The string to check for letters in
|
||||
*/
|
||||
static bool matchesColor(const QString &term, const QString &colors)
|
||||
{
|
||||
const QString colors = info->getColors();
|
||||
|
||||
// Colorless card filter
|
||||
if (convertedColorTerm.toLower() == "c" && colors.length() < 1) {
|
||||
if (term == "c" && colors.length() < 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -373,13 +379,23 @@ bool FilterItem::acceptColor(const CardInfoPtr &info) const
|
|||
* then we should match all of them to the card's colors
|
||||
*/
|
||||
int match_count = 0;
|
||||
for (const QChar &color : convertedColorTerm) {
|
||||
for (const QChar &color : term) {
|
||||
if (colors.contains(color, Qt::CaseInsensitive)) {
|
||||
match_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return match_count == convertedColorTerm.length();
|
||||
return match_count == term.length();
|
||||
}
|
||||
|
||||
bool FilterItem::acceptColor(const CardInfoPtr &info) const
|
||||
{
|
||||
return matchesColor(convertedColorTerm, info->getColors());
|
||||
}
|
||||
|
||||
bool FilterItem::acceptColorIdentity(const CardInfoPtr &info) const
|
||||
{
|
||||
return matchesColor(convertedColorTerm, info->getColorIdentity());
|
||||
}
|
||||
|
||||
bool FilterItem::acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const
|
||||
|
|
@ -551,6 +567,8 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr &info,
|
|||
return acceptType(info);
|
||||
case CardFilter::AttrColor:
|
||||
return acceptColor(info);
|
||||
case CardFilter::AttrColorIdentity:
|
||||
return acceptColorIdentity(info);
|
||||
case CardFilter::AttrText:
|
||||
return acceptText(info, searchLanguage);
|
||||
case CardFilter::AttrSet:
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ public:
|
|||
[[nodiscard]] bool acceptMainType(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptSubType(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptColor(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptColorIdentity(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptSet(const CardInfoPtr &info) const;
|
||||
[[nodiscard]] bool acceptManaCost(const CardInfoPtr &info) const;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue