From e029173e29991b45fd7db4589fb27b1c9eccbf92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 18 Sep 2026 11:38:45 +0200 Subject: [PATCH] [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. --- .../dialogs/dlg_create_token.cpp | 11 ++ .../card_picture_to_load.cpp | 3 +- .../deck_editor_database_display_widget.cpp | 11 ++ .../settings_page/general_settings_page.cpp | 26 +++- .../settings_page/general_settings_page.h | 4 + .../widgets/utility/completer_utils.cpp | 20 ++++ .../visual_database_display_widget.cpp | 9 ++ .../libcockatrice/card/card_localization.h | 10 ++ .../libcockatrice/filters/filter_string.cpp | 48 +++++++- .../libcockatrice/filters/filter_string.h | 12 ++ .../libcockatrice/filters/filter_tree.cpp | 112 ++++++++++++++---- .../libcockatrice/filters/filter_tree.h | 43 +++++-- ...nterface_cards_display_settings_provider.h | 1 + .../database/card/card_search_model.cpp | 18 ++- .../models/database/card/card_search_model.h | 11 ++ .../database/card_database_display_model.cpp | 31 ++++- .../database/card_database_display_model.h | 11 +- .../settings/cards_display_settings.cpp | 14 +++ .../settings/cards_display_settings.h | 4 + tests/settings/settings_defaults_test.cpp | 13 ++ 20 files changed, 360 insertions(+), 52 deletions(-) diff --git a/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp b/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp index b311d2ebd..47985cf29 100644 --- a/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp @@ -16,11 +16,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -88,6 +90,15 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa cardDatabaseDisplayModel = new TokenDisplayModel(this); cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel); + CardsDisplaySettings *cardsDisplay = &SettingsCache::instance().cardsDisplay(); + const auto applyCardSearchLanguage = [this, cardsDisplay]() { + cardDatabaseDisplayModel->setSearchLanguage( + cardsDisplay->getCardLang(), static_cast(cardsDisplay->getCardSearchLanguage())); + }; + applyCardSearchLanguage(); + connect(cardsDisplay, &CardsDisplaySettings::cardLangChanged, this, applyCardSearchLanguage); + connect(cardsDisplay, &CardsDisplaySettings::cardSearchLanguageChanged, this, applyCardSearchLanguage); + chooseTokenFromAllRadioButton = new QRadioButton(tr("Show &all tokens")); connect(chooseTokenFromAllRadioButton, &QRadioButton::toggled, this, &DlgCreateToken::actChooseTokenFromAll); chooseTokenFromDeckRadioButton = new QRadioButton(tr("Show tokens from this &deck")); diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp index 7cb502e92..4f933207e 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp @@ -94,8 +94,7 @@ void CardPictureToLoad::populateSetUrls() } } - const QStringList orderedTemplates = urlTemplates; - for (const QString &urlTemplate : orderedTemplates) { + for (const QString &urlTemplate : urlTemplates) { QString transformedUrl = transformUrl(urlTemplate); if (!transformedUrl.isEmpty()) { diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp index c8bdacf0d..29c18c821 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp @@ -11,8 +11,10 @@ #include #include #include +#include #include #include +#include DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent, CardDatabaseModel *databaseModel) : QWidget(parent) @@ -40,6 +42,15 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent databaseDisplayModel->setSourceModel(databaseModel); databaseDisplayModel->setFilterKeyColumn(0); + CardsDisplaySettings *cardsDisplay = &SettingsCache::instance().cardsDisplay(); + const auto applyCardSearchLanguage = [this, cardsDisplay]() { + databaseDisplayModel->setSearchLanguage(cardsDisplay->getCardLang(), + static_cast(cardsDisplay->getCardSearchLanguage())); + }; + applyCardSearchLanguage(); + connect(cardsDisplay, &CardsDisplaySettings::cardLangChanged, this, applyCardSearchLanguage); + connect(cardsDisplay, &CardsDisplaySettings::cardSearchLanguageChanged, this, applyCardSearchLanguage); + databaseView = new CardDatabaseView(this, databaseDisplayModel); databaseView->setObjectName("databaseView"); databaseView->setFocusProxy(searchEdit); diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp index 36436f8a3..40f84215f 100644 --- a/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp @@ -63,13 +63,26 @@ GeneralSettingsPage::GeneralSettingsPage() connect(&cardLanguageBox, qOverload(&QComboBox::currentIndexChanged), this, &GeneralSettingsPage::cardLanguageBoxChanged); + // card search language, independent of the card display language + cardSearchLanguageBox.addItem(""); // texts set in retranslateUi + cardSearchLanguageBox.addItem(""); + cardSearchLanguageBox.addItem(""); + const int cardSearchLanguageIndex = SettingsCache::instance().cardsDisplay().getCardSearchLanguage(); + cardSearchLanguageBox.setCurrentIndex(cardSearchLanguageIndex < 0 ? static_cast(CardSearchLanguage::English) + : cardSearchLanguageIndex); + + connect(&cardSearchLanguageBox, qOverload(&QComboBox::currentIndexChanged), this, + &GeneralSettingsPage::cardSearchLanguageBoxChanged); + auto *languageGrid = new QGridLayout; languageGrid->addWidget(&languageLabel, 0, 0); languageGrid->addWidget(&languageBox, 0, 1); languageGrid->addWidget(&cardLanguageLabel, 1, 0); languageGrid->addWidget(&cardLanguageBox, 1, 1); languageGrid->addWidget(&cardLanguageNoteLabel, 2, 1); - languageGrid->addWidget(&advertiseTranslationPageLabel, 3, 1, Qt::AlignRight); + languageGrid->addWidget(&cardSearchLanguageLabel, 3, 0); + languageGrid->addWidget(&cardSearchLanguageBox, 3, 1); + languageGrid->addWidget(&advertiseTranslationPageLabel, 4, 1, Qt::AlignRight); cardLanguageNoteLabel.setWordWrap(true); cardLanguageNoteLabel.setAlignment(Qt::AlignLeft | Qt::AlignVCenter); @@ -481,6 +494,11 @@ void GeneralSettingsPage::cardLanguageBoxChanged(int index) } } +void GeneralSettingsPage::cardSearchLanguageBoxChanged(int index) +{ + SettingsCache::instance().cardsDisplay().setCardSearchLanguage(index); +} + void GeneralSettingsPage::updateStartupServerControlsVisibility() { const int index = startupTabSelector.currentIndex(); @@ -502,6 +520,12 @@ void GeneralSettingsPage::retranslateUi() cardLanguageLabel.setText(tr("Card text & images language:")); cardLanguageNoteLabel.setText( tr("Foreign card names, text and art apply after you update the card database (Oracle).")); + cardSearchLanguageLabel.setText(tr("Language used in card search:")); + cardSearchLanguageBox.setItemText(static_cast(CardSearchLanguage::English), tr("English")); + cardSearchLanguageBox.setItemText(static_cast(CardSearchLanguage::Selected), + tr("Selected card language (untranslated cards still match in English)")); + cardSearchLanguageBox.setItemText(static_cast(CardSearchLanguage::Both), + tr("English and selected card language")); advertiseTranslationPageLabel.setText( QString("%2").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations"))); diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h index 7afba158f..07e0ba3a9 100644 --- a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h @@ -38,6 +38,7 @@ private slots: void resetAllPathsClicked(); void languageBoxChanged(int index); void cardLanguageBoxChanged(int index); + void cardSearchLanguageBoxChanged(int index); void updateStartupServerControlsVisibility(); private: @@ -55,6 +56,9 @@ private: QComboBox cardLanguageBox; QLabel cardLanguageNoteLabel; + QLabel cardSearchLanguageLabel; + QComboBox cardSearchLanguageBox; + QLabel updateReleaseChannelLabel; QComboBox updateReleaseChannelBox; QCheckBox startupUpdateCheckCheckBox; diff --git a/cockatrice/src/interface/widgets/utility/completer_utils.cpp b/cockatrice/src/interface/widgets/utility/completer_utils.cpp index 16d5cfd13..1f7111960 100644 --- a/cockatrice/src/interface/widgets/utility/completer_utils.cpp +++ b/cockatrice/src/interface/widgets/utility/completer_utils.cpp @@ -1,5 +1,6 @@ #include "completer_utils.h" +#include "../../../client/settings/cache_settings.h" #include "card_completer_styler.h" #include @@ -7,13 +8,26 @@ #include #include #include +#include #include #include #include +#include + +namespace +{ +void applyCardSearchLanguage(CardSearchModel *searchModel) +{ + const auto &cardsDisplay = SettingsCache::instance().cardsDisplay(); + searchModel->setSearchLanguage(cardsDisplay.getCardLang(), + static_cast(cardsDisplay.getCardSearchLanguage())); +} +} // namespace CardCompleterSetup createCardCompleter(CardDatabaseDisplayModel *displayModel, QObject *parent, int maxVisibleItems) { auto *searchModel = new CardSearchModel(displayModel, parent); + applyCardSearchLanguage(searchModel); auto *proxyModel = new CardCompleterProxyModel(parent); proxyModel->setSourceModel(searchModel); @@ -27,6 +41,12 @@ CardCompleterSetup createCardCompleter(CardDatabaseDisplayModel *displayModel, Q completer->setMaxVisibleItems(maxVisibleItems); CardCompleterStyler::apply(completer); + auto *cardsDisplay = &SettingsCache::instance().cardsDisplay(); + QObject::connect(cardsDisplay, &CardsDisplaySettings::cardLangChanged, searchModel, + [searchModel] { applyCardSearchLanguage(searchModel); }); + QObject::connect(cardsDisplay, &CardsDisplaySettings::cardSearchLanguageChanged, searchModel, + [searchModel] { applyCardSearchLanguage(searchModel); }); + return {searchModel, proxyModel, completer}; } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp index f4530a12b..fb43dddcd 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp @@ -41,6 +41,15 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, databaseDisplayModel->setSourceModel(database_model); databaseDisplayModel->setFilterKeyColumn(0); + CardsDisplaySettings *cardsDisplay = &SettingsCache::instance().cardsDisplay(); + const auto applyCardSearchLanguage = [this, cardsDisplay]() { + databaseDisplayModel->setSearchLanguage(cardsDisplay->getCardLang(), + static_cast(cardsDisplay->getCardSearchLanguage())); + }; + applyCardSearchLanguage(); + connect(cardsDisplay, &CardsDisplaySettings::cardLangChanged, this, applyCardSearchLanguage); + connect(cardsDisplay, &CardsDisplaySettings::cardSearchLanguageChanged, this, applyCardSearchLanguage); + cards = new QList; connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this, &VisualDatabaseDisplayWidget::modelDirty); diff --git a/libcockatrice_card/libcockatrice/card/card_localization.h b/libcockatrice_card/libcockatrice/card/card_localization.h index a9c8d28ea..450ee1805 100644 --- a/libcockatrice_card/libcockatrice/card/card_localization.h +++ b/libcockatrice_card/libcockatrice/card/card_localization.h @@ -15,6 +15,16 @@ * imports the selected language's card data) and the client settings UI (which * offers the language choice). */ +/** + * @brief The card languages card search should run against. + */ +enum class CardSearchLanguage +{ + English, ///< Only search the English card names and texts. + Selected, ///< Search the selected card language (untranslated cards still match in English). + Both ///< Search both the English and the selected card language names and texts. +}; + namespace CardLocalization { /** diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp index aaf391c03..bb9f00cc2 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp @@ -74,6 +74,38 @@ NumericValue <- [0-9]+ static std::once_flag init; +// The peg parser rules are set up once per process, which means the GenericQuery/ +// OracleQuery lambdas cannot capture per-instance state. The card language the +// plain-text name and text queries search in is therefore kept here and applied +// by those lambdas; every FilterString shares it because it reflects a single +// global user setting. +static QString globalSearchLanguage; +static CardSearchLanguage globalSearchLanguageMode = CardSearchLanguage::English; + +namespace +{ +bool matchesInSearchLanguage(const QString &english, + const QString &localized, + const QString &searchLanguage, + CardSearchLanguage searchLanguageMode, + const StringMatcher &matcher) +{ + if (searchLanguageMode == CardSearchLanguage::English) { + return matcher(english); + } + + if (searchLanguageMode == CardSearchLanguage::Both && matcher(english)) { + return true; + } + + if (searchLanguage.isEmpty() || searchLanguage == "en") { + return matcher(english); + } + + return matcher(localized); +} +} // namespace + static void setupParserRules() { auto passthru = [](const peg::SemanticValues &sv) -> Filter { @@ -333,7 +365,10 @@ static void setupParserRules() search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter { const auto matcher = std::any_cast(sv[0]); - return [=](const CardData &x) { return matcher(x->getText()); }; + return [=](const CardData &x) { + return matchesInSearchLanguage(x->getText(), x->getLocalizedText(globalSearchLanguage), + globalSearchLanguage, globalSearchLanguageMode, matcher); + }; }; search["ColorQuery"] = [](const peg::SemanticValues &sv) -> Filter { @@ -410,7 +445,10 @@ static void setupParserRules() }; search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter { const auto matcher = std::any_cast(sv[0]); - return [=](const CardData &x) { return matcher(x->getName()); }; + return [=](const CardData &x) { + return matchesInSearchLanguage(x->getName(), x->getLocalizedName(globalSearchLanguage), + globalSearchLanguage, globalSearchLanguageMode, matcher); + }; }; search["Color"] = [](const peg::SemanticValues &sv) -> char { return "WUBRGU"[sv.choice()]; }; @@ -447,3 +485,9 @@ FilterString::FilterString(const QString &expr) result = [](const CardData &) -> bool { return false; }; } } + +void FilterString::setSearchLanguage(const QString &searchLanguage, CardSearchLanguage searchLanguageMode) +{ + globalSearchLanguage = searchLanguage; + globalSearchLanguageMode = searchLanguageMode; +} diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.h b/libcockatrice_filters/libcockatrice/filters/filter_string.h index a058f7d07..7bcab9c2e 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.h @@ -14,6 +14,7 @@ #include #include #include +#include #include inline Q_LOGGING_CATEGORY(FilterStringLog, "filter_string"); @@ -55,6 +56,17 @@ public: return _error; } + /** + * @brief Sets the card language plain-text name and text queries run against. + * + * The peg parser rules are set up once per process, so this propagates to + * every FilterString instance created through the shared parser. + * + * @param searchLanguage Empty string for English only, otherwise the card language code. + * @param searchLanguageMode The search language mode from CardSearchLanguage. + */ + void setSearchLanguage(const QString &searchLanguage, CardSearchLanguage searchLanguageMode); + private: QString _error; Filter result; diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp index 8502db50b..1731f834d 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp @@ -100,14 +100,17 @@ 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 QString &searchLanguage, + CardSearchLanguage searchLanguageMode) 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, searchLanguageMode)) { return false; } } @@ -115,13 +118,19 @@ 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 QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const { // if any one in the list is true, return false - return !testTypeOr(info, attr); + return !testTypeOr(info, attr, searchLanguage, searchLanguageMode); } -bool FilterItemList::testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) const +bool FilterItemList::testTypeOr(const CardInfoPtr info, + CardFilter::Attr attr, + const QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const { bool noChildEnabledChild = true; @@ -134,7 +143,7 @@ bool FilterItemList::testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) c noChildEnabledChild = false; } - if ((*i)->acceptCardAttr(info, attr)) { + if ((*i)->acceptCardAttr(info, attr, searchLanguage, searchLanguageMode)) { return true; } } @@ -142,20 +151,51 @@ 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 QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const { // if any one in the list is false, return true - return !testTypeAnd(info, attr); + return !testTypeAnd(info, attr, searchLanguage, searchLanguageMode); } -bool FilterItem::acceptName(const CardInfoPtr info) const +bool FilterItem::acceptName(const CardInfoPtr info, + const QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const { - return info->getName().contains(term, Qt::CaseInsensitive); + if (searchLanguageMode == CardSearchLanguage::English) { + return info->getName().contains(term, Qt::CaseInsensitive); + } + + if (searchLanguageMode == CardSearchLanguage::Both && info->getName().contains(term, Qt::CaseInsensitive)) { + return true; + } + + if (searchLanguage.isEmpty() || searchLanguage == "en") { + return info->getName().contains(term, Qt::CaseInsensitive); + } + + return info->getLocalizedName(searchLanguage).contains(term, Qt::CaseInsensitive); } -bool FilterItem::acceptNameExact(const CardInfoPtr info) const +bool FilterItem::acceptNameExact(const CardInfoPtr info, + const QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const { - return info->getName() == term; + if (searchLanguageMode == CardSearchLanguage::English) { + return info->getName() == term; + } + + if (searchLanguageMode == CardSearchLanguage::Both && info->getName() == term) { + return true; + } + + if (searchLanguage.isEmpty() || searchLanguage == "en") { + return info->getName() == term; + } + + return info->getLocalizedName(searchLanguage) == term; } bool FilterItem::acceptType(const CardInfoPtr info) const @@ -213,9 +253,23 @@ 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 QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const { - return info->getText().contains(term, Qt::CaseInsensitive); + if (searchLanguageMode == CardSearchLanguage::English) { + return info->getText().contains(term, Qt::CaseInsensitive); + } + + if (searchLanguageMode == CardSearchLanguage::Both && info->getText().contains(term, Qt::CaseInsensitive)) { + return true; + } + + if (searchLanguage.isEmpty() || searchLanguage == "en") { + return info->getText().contains(term, Qt::CaseInsensitive); + } + + return info->getLocalizedText(searchLanguage).contains(term, Qt::CaseInsensitive); } bool FilterItem::acceptSet(const CardInfoPtr info) const @@ -402,19 +456,22 @@ 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 QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const { switch (attr) { case CardFilter::AttrName: - return acceptName(info); + return acceptName(info, searchLanguage, searchLanguageMode); case CardFilter::AttrNameExact: - return acceptNameExact(info); + return acceptNameExact(info, searchLanguage, searchLanguageMode); case CardFilter::AttrType: return acceptType(info); case CardFilter::AttrColor: return acceptColor(info); case CardFilter::AttrText: - return acceptText(info); + return acceptText(info, searchLanguage, searchLanguageMode); case CardFilter::AttrSet: return acceptSet(info); case CardFilter::AttrManaCost: @@ -484,18 +541,21 @@ 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 QString &searchLanguage, + CardSearchLanguage searchLanguageMode) 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, searchLanguageMode)) { 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, searchLanguageMode)) { return false; } @@ -504,23 +564,25 @@ 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, searchLanguageMode)) { 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, searchLanguageMode)) { return true; } return status; } -bool FilterTree::acceptsCard(const CardInfoPtr info) const +bool FilterTree::acceptsCard(const CardInfoPtr info, + const QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const { for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) { - if ((*i)->isEnabled() && !testAttr(info, *i)) { + if ((*i)->isEnabled() && !testAttr(info, *i, searchLanguage, searchLanguageMode)) { return false; } } diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index aac1777e0..e98106b53 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -167,10 +168,22 @@ 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 QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const; + [[nodiscard]] bool testTypeAndNot(CardInfoPtr info, + CardFilter::Attr attr, + const QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const; + [[nodiscard]] bool testTypeOr(CardInfoPtr info, + CardFilter::Attr attr, + const QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const; + [[nodiscard]] bool testTypeOrNot(CardInfoPtr info, + CardFilter::Attr attr, + const QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const; }; class FilterItem : public FilterTreeNode @@ -207,20 +220,26 @@ public: return true; } - [[nodiscard]] bool acceptName(CardInfoPtr info) const; - [[nodiscard]] bool acceptNameExact(CardInfoPtr info) const; + [[nodiscard]] bool + acceptName(CardInfoPtr info, const QString &searchLanguage, CardSearchLanguage searchLanguageMode) const; + [[nodiscard]] bool + acceptNameExact(CardInfoPtr info, const QString &searchLanguage, CardSearchLanguage searchLanguageMode) 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 QString &searchLanguage, CardSearchLanguage searchLanguageMode) 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 QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const; [[nodiscard]] bool acceptFormat(CardInfoPtr info) const; [[nodiscard]] bool relationCheck(int cardInfo) const; }; @@ -240,7 +259,10 @@ 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 QString &searchLanguage, + CardSearchLanguage searchLanguageMode) const; void nodeChanged() const override { @@ -279,7 +301,8 @@ public: return 0; } - [[nodiscard]] bool acceptsCard(CardInfoPtr info) const; + [[nodiscard]] bool + acceptsCard(CardInfoPtr info, const QString &searchLanguage, CardSearchLanguage searchLanguageMode) const; void removeFiltersByAttr(CardFilter::Attr filterType); void removeFilter(const CardFilter *toRemove); void clear(); diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h index 304ab3cb7..900f51f2b 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h @@ -29,6 +29,7 @@ public: [[nodiscard]] virtual int getArchidektPreviewSize() const = 0; [[nodiscard]] virtual int getSampleHandSize() const = 0; [[nodiscard]] virtual QString getCardLang() const = 0; + [[nodiscard]] virtual int getCardSearchLanguage() const = 0; }; #endif // COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp index 621f28983..274719b3a 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp @@ -65,7 +65,13 @@ void CardSearchModel::updateSearchResults(const QString &query) continue; } - const QString lowerName = card->getName().toLower(); + // The completer suggestions match against the same languages the card + // search uses, so typing a localized name finds the card. + QString matchName = card->getName(); + if (searchLanguageMode != CardSearchLanguage::English && !searchLanguage.isEmpty() && searchLanguage != "en") { + matchName = card->getLocalizedName(searchLanguage); + } + const QString lowerName = matchName.toLower(); if (!lowerName.contains(lowerQuery)) { continue; } @@ -79,11 +85,17 @@ void CardSearchModel::updateSearchResults(const QString &query) } } - auto sortByDistanceThenLength = [](const SearchResult &a, const SearchResult &b) { + auto sortByDistanceThenLength = [this](const SearchResult &a, const SearchResult &b) { + QString nameA = a.card->getName(); + QString nameB = b.card->getName(); + if (searchLanguageMode != CardSearchLanguage::English && !searchLanguage.isEmpty() && searchLanguage != "en") { + nameA = a.card->getLocalizedName(searchLanguage); + nameB = b.card->getLocalizedName(searchLanguage); + } if (a.distance != b.distance) { return a.distance < b.distance; } - return a.card->getName().size() < b.card->getName().size(); + return nameA.size() < nameB.size(); }; std::sort(prefixMatches.begin(), prefixMatches.end(), sortByDistanceThenLength); diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h index 646bf7e61..dcd61e37c 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h @@ -27,6 +27,15 @@ public: void updateSearchResults(const QString &query); // Update results based on input + void setSearchLanguage(const QString &searchLang, CardSearchLanguage mode) + { + if (searchLanguage == searchLang && searchLanguageMode == mode) { + return; + } + searchLanguage = searchLang; + searchLanguageMode = mode; + } + private: struct SearchResult { @@ -36,6 +45,8 @@ private: CardDatabaseDisplayModel *sourceModel; QList searchResults; + QString searchLanguage; + CardSearchLanguage searchLanguageMode = CardSearchLanguage::English; }; #endif // CARD_SEARCH_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp index 724ee61f2..acef20440 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp @@ -179,7 +179,7 @@ bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex } if (filterString != nullptr) { - if (filterTree != nullptr && !filterTree->acceptsCard(info)) { + if (filterTree != nullptr && !filterTree->acceptsCard(info, searchLanguage, searchLanguageMode)) { return false; } return filterString->check(info); @@ -190,7 +190,9 @@ bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const { - if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive)) { + if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive) && + searchLanguageMode != CardSearchLanguage::English && !searchLanguage.isEmpty() && searchLanguage != "en" && + !info->getLocalizedName(searchLanguage).contains(cardName, Qt::CaseInsensitive)) { return false; } @@ -199,7 +201,7 @@ bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const } if (filterTree != nullptr) { - return filterTree->acceptsCard(info); + return filterTree->acceptsCard(info, searchLanguage, searchLanguageMode); } return true; @@ -235,6 +237,29 @@ void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree) invalidate(); } +void CardDatabaseDisplayModel::setStringFilter(const QString &_src) +{ + delete filterString; + filterString = new FilterString(_src); + filterString->setSearchLanguage(searchLanguage, searchLanguageMode); + dirty(); +} + +void CardDatabaseDisplayModel::setSearchLanguage(const QString &searchLang, CardSearchLanguage mode) +{ + if (searchLanguage == searchLang && searchLanguageMode == mode) { + return; + } + + searchLanguage = searchLang; + searchLanguageMode = mode; + + if (filterString != nullptr) { + filterString->setSearchLanguage(searchLanguage, searchLanguageMode); + } + dirty(); +} + void CardDatabaseDisplayModel::filterTreeChanged() { invalidate(); diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h index c3145c356..5eba2ae0a 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h @@ -10,6 +10,7 @@ #include #include +#include #include class FilterTree; @@ -32,6 +33,8 @@ private: FilterString *filterString; int loadedRowCount; QTimer dirtyTimer; + QString searchLanguage; + CardSearchLanguage searchLanguageMode = CardSearchLanguage::English; /** The translation table that will be used for sanitizeCardName. */ static QMap characterTranslation; @@ -55,17 +58,13 @@ public: cardName = sanitizeCardName(_cardName, characterTranslation); dirty(); } - void setStringFilter(const QString &_src) - { - delete filterString; - filterString = new FilterString(_src); - dirty(); - } + void setStringFilter(const QString &_src); void setCardNameSet(const QSet &_cardNameSet) { cardNameSet = _cardNameSet; dirty(); } + void setSearchLanguage(const QString &searchLang, CardSearchLanguage mode); void dirty() { diff --git a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp index b8eafca6c..0996dc92e 100644 --- a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp @@ -110,6 +110,11 @@ QString CardsDisplaySettings::getCardLang() const return getValue("cardLang", QString(), QString(), "en").toString(); } +int CardsDisplaySettings::getCardSearchLanguage() const +{ + return getValue("cardSearchLanguage", QString(), QString(), static_cast(CardSearchLanguage::English)).toInt(); +} + void CardsDisplaySettings::setDisplayCardNames(bool _displayCardNames) { setValue(_displayCardNames, "displayCardNames"); @@ -242,3 +247,12 @@ void CardsDisplaySettings::setCardLang(const QString &_cardLang) sync(); emit cardLangChanged(_cardLang); } + +void CardsDisplaySettings::setCardSearchLanguage(int _cardSearchLanguage) +{ + if (_cardSearchLanguage == getCardSearchLanguage()) { + return; + } + setValue(_cardSearchLanguage, "cardSearchLanguage"); + emit cardSearchLanguageChanged(_cardSearchLanguage); +} diff --git a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h index 0b47ce490..85eb5adbd 100644 --- a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h @@ -3,6 +3,7 @@ #include "settings_manager.h" +#include #include class CardsDisplaySettings : public SettingsManager, public ICardsDisplaySettingsProvider @@ -32,6 +33,7 @@ public: [[nodiscard]] int getArchidektPreviewSize() const override; [[nodiscard]] int getSampleHandSize() const override; [[nodiscard]] QString getCardLang() const override; + [[nodiscard]] int getCardSearchLanguage() const override; void setDisplayCardNames(bool _displayCardNames); void setRoundCardCorners(bool _roundCardCorners); @@ -54,6 +56,7 @@ public: void setArchidektPreviewCardSize(int _archidektPreviewCardSize); void setSampleHandSize(int _sampleHandSize); void setCardLang(const QString &_cardLang); + void setCardSearchLanguage(int _cardSearchLanguage); signals: void displayCardNamesChanged(); @@ -71,6 +74,7 @@ signals: void archidektPreviewSizeChanged(); void sampleHandSizeChanged(int amount); void cardLangChanged(const QString &lang); + void cardSearchLanguageChanged(int cardSearchLanguage); public: explicit CardsDisplaySettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index 340859323..fdf1c3fa2 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -562,6 +562,19 @@ TEST_F(SettingsDefaultsTest, CardsDisplay_CardLang_SetAndGet) ASSERT_EQ(s.getCardLang(), QString("de")); } +TEST_F(SettingsDefaultsTest, CardsDisplay_CardSearchLanguage_Default) +{ + CardsDisplaySettings s(settingsPath, nullptr); + ASSERT_EQ(s.getCardSearchLanguage(), static_cast(CardSearchLanguage::English)); +} + +TEST_F(SettingsDefaultsTest, CardsDisplay_CardSearchLanguage_SetAndGet) +{ + CardsDisplaySettings s(settingsPath, nullptr); + s.setCardSearchLanguage(static_cast(CardSearchLanguage::Selected)); + ASSERT_EQ(s.getCardSearchLanguage(), static_cast(CardSearchLanguage::Selected)); +} + // --- VisualDeckStorageSettings --- TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)