mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-09 19:43:26 -08:00
[Client] Added an exclude and color identity filter for the visual database display (#7587)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Client] Add exclude and color identity filters to the visual database display * [Filters] Extract the color filter mode derivation into libcockatrice_filters The mode table and the 'which mode produced this filter set' derivation are filter semantics, not widget code, so they belong next to CardFilter rather than in an anonymous namespace in the client. Moving them also makes them testable: filter_mode_test pins every mode, the Custom fallback and the 'keep the live mode when it still fits' rule. * [Tests] Cover the negated color identity query ci! read the camelCase colorIdentity property instead of the lowercase coloridentity key, so it matched the empty string and rejected every card. The new case uses a card whose colors and color identity differ, so it fails on the old lookup and passes on the fixed one. --------- Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
This commit is contained in:
parent
64c7809332
commit
74686d1736
17 changed files with 703 additions and 141 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
|
||||
|
|
@ -409,7 +409,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;
|
||||
}
|
||||
|
|
@ -427,7 +427,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,23 +176,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;
|
||||
|
|
@ -358,12 +359,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;
|
||||
}
|
||||
|
||||
|
|
@ -372,13 +378,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
|
||||
|
|
@ -540,6 +556,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