mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-27 16:32:20 -07:00
[Card] Add a setting for the language used in card search (#7314)
* [Card] Add a setting for the language used in card search Localized card names and texts can now be searched too, controlled by a 'Language used in card search' toggle (English, selected card language, or both) on the general settings page. Untranslated cards always keep matching in English. Removed the redundant local copy of the URL templates list in the localized picture loader while here. * [Card] Bind search language per FilterString instance The peg parser rules are set up once per process, so the GenericQuery and OracleQuery rule actions could not capture per-instance state. Instead of storing the search language in a process-global that FilterString instance methods mutate, hand it to the rule actions through a thread-local parse context and copy it into the filter closures they produce. Card evaluation in FilterString::check no longer reads any process-global state, and each instance keeps the language it was built with; constructing one instance no longer changes what unrelated instances (deck filter, drop-to-hand, zone views) match against. The card database display model stores the raw query and rebuilds the FilterString when the search language changes, since the language is now bound at parse time. Add tests for the English/Selected/Both search modes, the English fallback for untranslated cards, and per-instance language independence. * [Card] Pass the card search language to deck and zone card searches Wire the two remaining FilterString consumers to the configured card search language so card-name matches respect it everywhere: - DeckFilterString now takes the search language and mode, exposes them to its [[card name]] rule action via a thread-local parse context (same pattern as FilterString), and the engine's card database uses them for content search. - ZoneViewZone reads the card language from CardsDisplaySettings when applying its search filter, and the reveal-zone widget re-applies the active search when the language setting changes. - The deck-storage search re-runs its filter against the current card language setting, including live re-application when the setting changes. Game-action targeting (DlgMoveTopCardsUntil) intentionally keeps evaluating against English card names. * [Card] Rename CardSearchLanguage to SearchLanguageMode * [Card] Restore displaced namespace doc in card_localization.h * [Filters] Pass CardSearchLanguage as a single struct * [CardSearchModel] Match English and localized names in Both mode Card names are stored in both English and localized forms, so search for matches in both during the 'Both' search mode instead of checking only the localized name. * [CreateTokenDialog] Fetch cardsDisplay settings inside the apply lambda Avoid capturing the raw settings pointer in the lambda: resolve the card language and card search language from the settings cache at call time so the values are always current when the search language is re-applied. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
12299abcc8
commit
ef68a7bdcc
27 changed files with 488 additions and 68 deletions
|
|
@ -74,6 +74,36 @@ NumericValue <- [0-9]+
|
|||
|
||||
static std::once_flag init;
|
||||
|
||||
// The peglib parser rules (and therefore their rule actions) are set up once per
|
||||
// process, so a rule action cannot capture per-instance state. The card language
|
||||
// plain-text name and text queries search in is therefore handed to the GenericQuery
|
||||
// and OracleQuery rule actions through this thread-local context, which is live only
|
||||
// while a FilterString is being parsed. The rule actions copy it into the filter
|
||||
// closures they produce, so card evaluation never reads process-global state.
|
||||
thread_local CardSearchLanguage searchLanguageContext;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool matchesInSearchLanguage(const QString &english,
|
||||
const QString &localized,
|
||||
const CardSearchLanguage &searchLanguage,
|
||||
const StringMatcher &matcher)
|
||||
{
|
||||
if (searchLanguage.mode == SearchLanguageMode::English) {
|
||||
return matcher(english);
|
||||
}
|
||||
|
||||
if (searchLanguage.mode == SearchLanguageMode::Both) {
|
||||
if (!searchLanguage.isEnglishOnly() && matcher(localized)) {
|
||||
return true;
|
||||
}
|
||||
return matcher(english);
|
||||
}
|
||||
|
||||
return searchLanguage.isEnglishOnly() ? matcher(english) : matcher(localized);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
static void setupParserRules()
|
||||
{
|
||||
auto passthru = [](const peg::SemanticValues &sv) -> Filter {
|
||||
|
|
@ -333,7 +363,11 @@ static void setupParserRules()
|
|||
|
||||
search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
||||
return [=](const CardData &x) { return matcher(x->getText()); };
|
||||
const CardSearchLanguage searchLanguage = searchLanguageContext;
|
||||
return [=](const CardData &x) {
|
||||
return matchesInSearchLanguage(x->getText(), x->getLocalizedText(searchLanguage.language), searchLanguage,
|
||||
matcher);
|
||||
};
|
||||
};
|
||||
|
||||
search["ColorQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
|
|
@ -410,7 +444,11 @@ static void setupParserRules()
|
|||
};
|
||||
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
||||
return [=](const CardData &x) { return matcher(x->getName()); };
|
||||
const CardSearchLanguage searchLanguage = searchLanguageContext;
|
||||
return [=](const CardData &x) {
|
||||
return matchesInSearchLanguage(x->getName(), x->getLocalizedName(searchLanguage.language), searchLanguage,
|
||||
matcher);
|
||||
};
|
||||
};
|
||||
|
||||
search["Color"] = [](const peg::SemanticValues &sv) -> char { return "WUBRGU"[sv.choice()]; };
|
||||
|
|
@ -425,7 +463,7 @@ FilterString::FilterString()
|
|||
_error = "Not initialized";
|
||||
}
|
||||
|
||||
FilterString::FilterString(const QString &expr)
|
||||
FilterString::FilterString(const QString &expr, const CardSearchLanguage &searchLanguage)
|
||||
{
|
||||
QByteArray ba = expr.simplified().toUtf8();
|
||||
|
||||
|
|
@ -438,6 +476,8 @@ FilterString::FilterString(const QString &expr)
|
|||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <QString>
|
||||
#include <functional>
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <utility>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(FilterStringLog, "filter_string");
|
||||
|
|
@ -35,7 +36,7 @@ class FilterString
|
|||
{
|
||||
public:
|
||||
FilterString();
|
||||
explicit FilterString(const QString &exp);
|
||||
explicit FilterString(const QString &exp, const CardSearchLanguage &searchLanguage = {});
|
||||
[[nodiscard]] bool check(const CardData &card) const
|
||||
{
|
||||
if (card.isNull()) {
|
||||
|
|
|
|||
|
|
@ -100,14 +100,16 @@ FilterTreeNode *FilterItemList::termNode(const QString &term)
|
|||
return childNodes.at(i);
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeAnd(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
bool FilterItemList::testTypeAnd(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
|
||||
if (!(*i)->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(*i)->acceptCardAttr(info, attr)) {
|
||||
if (!(*i)->acceptCardAttr(info, attr, searchLanguage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -115,13 +117,17 @@ bool FilterItemList::testTypeAnd(const CardInfoPtr info, CardFilter::Attr attr)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeAndNot(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
bool FilterItemList::testTypeAndNot(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
// if any one in the list is true, return false
|
||||
return !testTypeOr(info, attr);
|
||||
return !testTypeOr(info, attr, searchLanguage);
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
bool FilterItemList::testTypeOr(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
bool noChildEnabledChild = true;
|
||||
|
||||
|
|
@ -134,7 +140,7 @@ bool FilterItemList::testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) c
|
|||
noChildEnabledChild = false;
|
||||
}
|
||||
|
||||
if ((*i)->acceptCardAttr(info, attr)) {
|
||||
if ((*i)->acceptCardAttr(info, attr, searchLanguage)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -142,20 +148,58 @@ bool FilterItemList::testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) c
|
|||
return noChildEnabledChild;
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeOrNot(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
bool FilterItemList::testTypeOrNot(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
// if any one in the list is false, return true
|
||||
return !testTypeAnd(info, attr);
|
||||
return !testTypeAnd(info, attr, searchLanguage);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptName(const CardInfoPtr info) const
|
||||
bool FilterItem::acceptName(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
return info->getName().contains(term, Qt::CaseInsensitive);
|
||||
const QString &englishName = info->getName();
|
||||
const QString &localizedName = info->getLocalizedName(searchLanguage.language);
|
||||
|
||||
switch (searchLanguage.mode) {
|
||||
case SearchLanguageMode::English:
|
||||
return englishName.contains(term, Qt::CaseInsensitive);
|
||||
case SearchLanguageMode::Both:
|
||||
if (englishName.contains(term, Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
return !searchLanguage.isEnglishOnly() && localizedName.contains(term, Qt::CaseInsensitive);
|
||||
case SearchLanguageMode::Selected:
|
||||
if (searchLanguage.isEnglishOnly()) {
|
||||
return englishName.contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
return localizedName.contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptNameExact(const CardInfoPtr info) const
|
||||
bool FilterItem::acceptNameExact(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
return info->getName() == term;
|
||||
const QString &englishName = info->getName();
|
||||
const QString &localizedName = info->getLocalizedName(searchLanguage.language);
|
||||
|
||||
switch (searchLanguage.mode) {
|
||||
case SearchLanguageMode::English:
|
||||
return englishName == term;
|
||||
case SearchLanguageMode::Both:
|
||||
if (englishName == term) {
|
||||
return true;
|
||||
}
|
||||
return !searchLanguage.isEnglishOnly() && localizedName == term;
|
||||
case SearchLanguageMode::Selected:
|
||||
if (searchLanguage.isEnglishOnly()) {
|
||||
return englishName == term;
|
||||
}
|
||||
return localizedName == term;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptType(const CardInfoPtr info) const
|
||||
|
|
@ -213,9 +257,27 @@ bool FilterItem::acceptColor(const CardInfoPtr info) const
|
|||
return match_count == converted_term.length();
|
||||
}
|
||||
|
||||
bool FilterItem::acceptText(const CardInfoPtr info) const
|
||||
bool FilterItem::acceptText(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
return info->getText().contains(term, Qt::CaseInsensitive);
|
||||
const QString &englishText = info->getText();
|
||||
const QString &localizedText = info->getLocalizedText(searchLanguage.language);
|
||||
|
||||
switch (searchLanguage.mode) {
|
||||
case SearchLanguageMode::English:
|
||||
return englishText.contains(term, Qt::CaseInsensitive);
|
||||
case SearchLanguageMode::Both:
|
||||
if (englishText.contains(term, Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
return !searchLanguage.isEnglishOnly() && localizedText.contains(term, Qt::CaseInsensitive);
|
||||
case SearchLanguageMode::Selected:
|
||||
if (searchLanguage.isEnglishOnly()) {
|
||||
return englishText.contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
return localizedText.contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptSet(const CardInfoPtr info) const
|
||||
|
|
@ -402,19 +464,21 @@ bool FilterItem::relationCheck(int cardInfo) const
|
|||
return result;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
bool FilterItem::acceptCardAttr(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
switch (attr) {
|
||||
case CardFilter::AttrName:
|
||||
return acceptName(info);
|
||||
return acceptName(info, searchLanguage);
|
||||
case CardFilter::AttrNameExact:
|
||||
return acceptNameExact(info);
|
||||
return acceptNameExact(info, searchLanguage);
|
||||
case CardFilter::AttrType:
|
||||
return acceptType(info);
|
||||
case CardFilter::AttrColor:
|
||||
return acceptColor(info);
|
||||
case CardFilter::AttrText:
|
||||
return acceptText(info);
|
||||
return acceptText(info, searchLanguage);
|
||||
case CardFilter::AttrSet:
|
||||
return acceptSet(info);
|
||||
case CardFilter::AttrManaCost:
|
||||
|
|
@ -484,18 +548,18 @@ FilterTreeNode *FilterTree::termNode(const CardFilter *f)
|
|||
return termNode(f->attr(), f->type(), f->term());
|
||||
}
|
||||
|
||||
bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm) const
|
||||
bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
const FilterItemList *fil;
|
||||
bool status = true;
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeAnd);
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAnd(info, lm->attr)) {
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAnd(info, lm->attr, searchLanguage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeAndNot);
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAndNot(info, lm->attr)) {
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAndNot(info, lm->attr, searchLanguage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -504,23 +568,23 @@ bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm) const
|
|||
status = false;
|
||||
|
||||
// if this is true we can return because it is OR'd with the OrNot list
|
||||
if (fil->testTypeOr(info, lm->attr)) {
|
||||
if (fil->testTypeOr(info, lm->attr, searchLanguage)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeOrNot);
|
||||
if (fil && fil->isEnabled() && fil->testTypeOrNot(info, lm->attr)) {
|
||||
if (fil && fil->isEnabled() && fil->testTypeOrNot(info, lm->attr, searchLanguage)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool FilterTree::acceptsCard(const CardInfoPtr info) const
|
||||
bool FilterTree::acceptsCard(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
|
||||
{
|
||||
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
|
||||
if ((*i)->isEnabled() && !testAttr(info, *i)) {
|
||||
if ((*i)->isEnabled() && !testAttr(info, *i, searchLanguage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -167,10 +168,14 @@ public:
|
|||
return CardFilter::typeName(type);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool testTypeAnd(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
[[nodiscard]] bool testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
[[nodiscard]] bool testTypeOr(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
[[nodiscard]] bool testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
[[nodiscard]] bool
|
||||
testTypeAnd(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool
|
||||
testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool
|
||||
testTypeOr(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool
|
||||
testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
|
||||
};
|
||||
|
||||
class FilterItem : public FilterTreeNode
|
||||
|
|
@ -207,20 +212,21 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool acceptName(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptNameExact(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptName(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptNameExact(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptType(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptMainType(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptSubType(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptColor(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptText(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptText(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptSet(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptManaCost(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptCmc(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
[[nodiscard]] bool acceptLoyalty(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptRarity(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
[[nodiscard]] bool
|
||||
acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptFormat(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool relationCheck(int cardInfo) const;
|
||||
};
|
||||
|
|
@ -240,7 +246,7 @@ private:
|
|||
LogicMap *attrLogicMap(CardFilter::Attr attr);
|
||||
FilterItemList *attrTypeList(CardFilter::Attr attr, CardFilter::Type type);
|
||||
|
||||
bool testAttr(CardInfoPtr info, const LogicMap *lm) const;
|
||||
bool testAttr(CardInfoPtr info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const;
|
||||
|
||||
void nodeChanged() const override
|
||||
{
|
||||
|
|
@ -279,7 +285,7 @@ public:
|
|||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool acceptsCard(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptsCard(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
|
||||
void removeFiltersByAttr(CardFilter::Attr filterType);
|
||||
void removeFilter(const CardFilter *toRemove);
|
||||
void clear();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue