diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index 4abb8210c..a2b7519a8 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -43,6 +43,12 @@ NumericValue <- [0-9]+ static std::once_flag init; +// The peglib parser is a single permanent object, so the rule actions below cannot see +// per-instance state. The card language that the nested [[card name]] search matches +// against is passed through this thread-local context, which is live only while a +// DeckFilterString is being parsed, and copied into the nested FilterString closures. +thread_local CardSearchLanguage deckSearchLanguageContext; + static void setupParserRules() { // plumbing @@ -116,7 +122,7 @@ static void setupParserRules() // actual functionality search["DeckContentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { - auto cardFilter = FilterString(std::any_cast(sv[0])); + auto cardFilter = FilterString(std::any_cast(sv[0]), deckSearchLanguageContext); auto numberMatcher = sv.size() > 1 ? std::any_cast(sv[1]) : [](int count) { return count > 0; }; return [=](const DeckSearchData &data) -> bool { @@ -186,7 +192,7 @@ DeckFilterString::DeckFilterString() _error = "Not initialized"; } -DeckFilterString::DeckFilterString(const QString &expr) +DeckFilterString::DeckFilterString(const QString &expr, const CardSearchLanguage &searchLanguage) { QByteArray ba = expr.simplified().toUtf8(); @@ -199,6 +205,8 @@ DeckFilterString::DeckFilterString(const QString &expr) return; } + deckSearchLanguageContext = 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)); }); diff --git a/cockatrice/src/filters/deck_filter_string.h b/cockatrice/src/filters/deck_filter_string.h index 90a6a17eb..5b1419004 100644 --- a/cockatrice/src/filters/deck_filter_string.h +++ b/cockatrice/src/filters/deck_filter_string.h @@ -12,6 +12,7 @@ #include #include #include +#include inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string"); @@ -35,7 +36,7 @@ class DeckFilterString { public: DeckFilterString(); - explicit DeckFilterString(const QString &expr); + explicit DeckFilterString(const QString &expr, const CardSearchLanguage &searchLanguage = {}); bool check(const DeckSearchData &data) const { return filter(data); diff --git a/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp b/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp index b311d2ebd..6d8ad0534 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,17 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa cardDatabaseDisplayModel = new TokenDisplayModel(this); cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel); + const auto applyCardSearchLanguage = [this]() { + const CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay(); + cardDatabaseDisplayModel->setSearchLanguage(CardSearchLanguage{ + cardsDisplay.getCardLang(), static_cast(cardsDisplay.getCardSearchLanguage())}); + }; + applyCardSearchLanguage(); + connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, + applyCardSearchLanguage); + connect(&SettingsCache::instance().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/game_graphics/zones/view_zone.cpp b/cockatrice/src/game_graphics/zones/view_zone.cpp index baf7b8b30..5bd5d262f 100644 --- a/cockatrice/src/game_graphics/zones/view_zone.cpp +++ b/cockatrice/src/game_graphics/zones/view_zone.cpp @@ -1,5 +1,6 @@ #include "view_zone.h" +#include "../../client/settings/cache_settings.h" #include "../../game/player/player_actions.h" #include "../../game/player/player_logic.h" #include "../../game/zones/view_zone_logic.h" @@ -11,11 +12,13 @@ #include #include #include +#include #include #include #include #include #include +#include /** * @param parent the parent QGraphicsWidget containing the reveal zone @@ -253,7 +256,10 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca void ZoneViewZone::setFilterString(const QString &_filterString) { - filterString = FilterString(_filterString); + const CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay(); + filterString = FilterString( + _filterString, CardSearchLanguage{cardsDisplay.getCardLang(), + static_cast(cardsDisplay.getCardSearchLanguage())}); reorganizeCards(); } diff --git a/cockatrice/src/game_graphics/zones/view_zone_widget.cpp b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp index c13b79fea..fa6733413 100644 --- a/cockatrice/src/game_graphics/zones/view_zone_widget.cpp +++ b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace @@ -168,6 +169,12 @@ ZoneViewWidget::ZoneViewWidget(PlayerLogic *_player, } connect(&searchEdit, &QLineEdit::textChanged, zone, &ZoneViewZone::setFilterString); + + const auto applyCardSearchLanguage = [this] { zone->setFilterString(searchEdit.text()); }; + connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, + applyCardSearchLanguage); + connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardSearchLanguageChanged, this, + applyCardSearchLanguage); } setLayout(vbox); 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..a83e25f5f 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,17 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent databaseDisplayModel->setSourceModel(databaseModel); databaseDisplayModel->setFilterKeyColumn(0); + const auto applyCardSearchLanguage = [this]() { + const CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay(); + databaseDisplayModel->setSearchLanguage(CardSearchLanguage{ + cardsDisplay.getCardLang(), static_cast(cardsDisplay.getCardSearchLanguage())}); + }; + applyCardSearchLanguage(); + connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, + applyCardSearchLanguage); + connect(&SettingsCache::instance().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..b0fd0e018 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(SearchLanguageMode::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(SearchLanguageMode::English), tr("English")); + cardSearchLanguageBox.setItemText(static_cast(SearchLanguageMode::Selected), + tr("Selected card language (untranslated cards still match in English)")); + cardSearchLanguageBox.setItemText(static_cast(SearchLanguageMode::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..23ddb30b0 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 CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay(); + searchModel->setSearchLanguage(CardSearchLanguage{ + 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..a20d56f63 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,17 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, databaseDisplayModel->setSourceModel(database_model); databaseDisplayModel->setFilterKeyColumn(0); + const auto applyCardSearchLanguage = [this]() { + const CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay(); + databaseDisplayModel->setSearchLanguage(CardSearchLanguage{ + cardsDisplay.getCardLang(), static_cast(cardsDisplay.getCardSearchLanguage())}); + }; + applyCardSearchLanguage(); + connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, + applyCardSearchLanguage); + connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardSearchLanguageChanged, this, + applyCardSearchLanguage); + cards = new QList; connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this, &VisualDatabaseDisplayWidget::modelDirty); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp index 4b3a1ac29..0ad6ebcb2 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp @@ -1,9 +1,12 @@ #include "visual_deck_storage_sort_filter_proxy_model.h" +#include "../../../client/settings/cache_settings.h" #include "../../filters/deck_filter_string.h" #include #include +#include +#include VisualDeckStorageSortFilterProxyModel::VisualDeckStorageSortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) @@ -215,7 +218,10 @@ void VisualDeckStorageSortFilterProxyModel::updateSearchMatches() return; } - DeckFilterString filterString(searchText); + const auto &cardsDisplay = SettingsCache::instance().cardsDisplay(); + DeckFilterString filterString( + searchText, CardSearchLanguage{cardsDisplay.getCardLang(), + static_cast(cardsDisplay.getCardSearchLanguage())}); for (int row = 0; row < count; ++row) { const DeckPreviewData &data = source->dataForRow(row); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp index bd167ec5b..c7218f195 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -118,6 +119,13 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare connect(searchWidget, &VisualDeckStorageSearchWidget::searchTextChanged, this, &VisualDeckStorageWidget::updateSearchFilter); + // The deck content search matches card names in the configured card language; + // re-run it whenever that setting changes so active searches follow immediately. + CardsDisplaySettings *cardsDisplay = &SettingsCache::instance().cardsDisplay(); + const auto reapplySearchForLanguage = [this] { storageProxyModel->reapplyFilters(); }; + connect(cardsDisplay, &CardsDisplaySettings::cardLangChanged, this, reapplySearchForLanguage); + connect(cardsDisplay, &CardsDisplaySettings::cardSearchLanguageChanged, this, reapplySearchForLanguage); + connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this, &VisualDeckStorageWidget::createRootFolderWidget); diff --git a/libcockatrice_card/libcockatrice/card/card_localization.h b/libcockatrice_card/libcockatrice/card/card_localization.h index a9c8d28ea..1bde506cb 100644 --- a/libcockatrice_card/libcockatrice/card/card_localization.h +++ b/libcockatrice_card/libcockatrice/card/card_localization.h @@ -4,6 +4,42 @@ #include #include +/** + * @brief The card languages card search should run against. + */ +enum class SearchLanguageMode +{ + 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. +}; + +/** + * @brief The card language and matching mode searches run against. + * + * Bundles the card language code configured in the settings with the + * SearchLanguageMode, so entry points take one value instead of two related + * parameters. + */ +struct CardSearchLanguage +{ + QString language; ///< Card language code (e.g. "de"); empty means the English fallback. + SearchLanguageMode mode = SearchLanguageMode::English; ///< How the language participates in the search. + + /** + * @brief Whether only the English card data is searched. + * + * @return True when no card language is selected or English itself is selected. + */ + [[nodiscard]] bool isEnglishOnly() const + { + return language.isEmpty() || language == QLatin1String("en"); + } + + bool operator==(const CardSearchLanguage &) const = default; + bool operator!=(const CardSearchLanguage &) const = default; +}; + /** * @namespace CardLocalization * @ingroup Cards diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp index aaf391c03..1b63ca122 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp @@ -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(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(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)); }); diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.h b/libcockatrice_filters/libcockatrice/filters/filter_string.h index a058f7d07..015df0cf4 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"); @@ -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()) { diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp index 8502db50b..a5d91d9d3 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp @@ -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; } } diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index aac1777e0..dd47a1ebc 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,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(); 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..3a734ed37 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp @@ -65,25 +65,31 @@ void CardSearchModel::updateSearchResults(const QString &query) continue; } - const QString lowerName = card->getName().toLower(); - if (!lowerName.contains(lowerQuery)) { - continue; - } + // The completer suggestions match against the same languages the card + // search uses, so typing a localized name finds the card. In Both mode + // either language can match. + for (const QString &matchName : searchableNames(card)) { + const QString lowerName = matchName.toLower(); + if (!lowerName.contains(lowerQuery)) { + continue; + } - const int distance = levenshteinDistance(lowerQuery, lowerName); + const int distance = levenshteinDistance(lowerQuery, lowerName); - if (lowerName.startsWith(lowerQuery)) { - prefixMatches.append({card, distance}); - } else { - containsMatches.append({card, distance}); + if (lowerName.startsWith(lowerQuery)) { + prefixMatches.append({card, distance}); + } else { + containsMatches.append({card, distance}); + } + break; } } - auto sortByDistanceThenLength = [](const SearchResult &a, const SearchResult &b) { + auto sortByDistanceThenLength = [this](const SearchResult &a, const SearchResult &b) { if (a.distance != b.distance) { return a.distance < b.distance; } - return a.card->getName().size() < b.card->getName().size(); + return sortableName(a.card).size() < sortableName(b.card).size(); }; std::sort(prefixMatches.begin(), prefixMatches.end(), sortByDistanceThenLength); @@ -101,3 +107,25 @@ void CardSearchModel::updateSearchResults(const QString &query) endResetModel(); } + +QStringList CardSearchModel::searchableNames(const CardInfoPtr &card) const +{ + if (searchLanguage.isEnglishOnly()) { + return {card->getName()}; + } + + const QString localizedName = card->getLocalizedName(searchLanguage.language); + if (searchLanguage.mode == SearchLanguageMode::Selected) { + return {localizedName}; + } + + return {card->getName(), localizedName}; +} + +QString CardSearchModel::sortableName(const CardInfoPtr &card) const +{ + if (searchLanguage.isEnglishOnly()) { + return card->getName(); + } + return card->getLocalizedName(searchLanguage.language); +} 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..b877a9385 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,14 @@ public: void updateSearchResults(const QString &query); // Update results based on input + void setSearchLanguage(const CardSearchLanguage &searchLang) + { + if (searchLanguage == searchLang) { + return; + } + searchLanguage = searchLang; + } + private: struct SearchResult { @@ -34,8 +42,15 @@ private: int distance; }; + /** @brief The names a card is searched by with the current search language. */ + [[nodiscard]] QStringList searchableNames(const CardInfoPtr &card) const; + + /** @brief The name used to break distance ties when sorting suggestions. */ + [[nodiscard]] QString sortableName(const CardInfoPtr &card) const; + CardDatabaseDisplayModel *sourceModel; QList searchResults; + CardSearchLanguage searchLanguage; }; #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..7f89677c8 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)) { return false; } return filterString->check(info); @@ -190,8 +190,14 @@ bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const { - if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive)) { - return false; + if (!cardName.isEmpty()) { + const bool matchesEnglish = info->getName().contains(cardName, Qt::CaseInsensitive); + const bool matchesLocalized = + !searchLanguage.isEnglishOnly() && + info->getLocalizedName(searchLanguage.language).contains(cardName, Qt::CaseInsensitive); + if (!matchesEnglish && !matchesLocalized) { + return false; + } } if (!cardNameSet.isEmpty() && !cardNameSet.contains(info->getName())) { @@ -199,7 +205,7 @@ bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const } if (filterTree != nullptr) { - return filterTree->acceptsCard(info); + return filterTree->acceptsCard(info, searchLanguage); } return true; @@ -235,6 +241,28 @@ void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree) invalidate(); } +void CardDatabaseDisplayModel::setStringFilter(const QString &_src) +{ + searchText = _src; + delete filterString; + filterString = new FilterString(_src, searchLanguage); + dirty(); +} + +void CardDatabaseDisplayModel::setSearchLanguage(const CardSearchLanguage &searchLang) +{ + if (searchLanguage == searchLang) { + return; + } + + searchLanguage = searchLang; + + if (filterString != nullptr) { + setStringFilter(searchText); + } + 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..e6aadd8c6 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; + CardSearchLanguage searchLanguage; + QString searchText; /** 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 CardSearchLanguage &searchLang); void dirty() { diff --git a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp index b8eafca6c..94e888504 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(SearchLanguageMode::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/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp index c6d68be1f..2b92c65df 100644 --- a/tests/carddatabase/filter_string_test.cpp +++ b/tests/carddatabase/filter_string_test.cpp @@ -73,6 +73,57 @@ QUERY(Color4, cat, "c!gw", false) QUERY(BracketNextToUnquotedString, cat, "(o:woof OR o:meow)", true) +CardInfoPtr localizedCat() +{ + CardInfoPtr localized = CardInfo::newInstance("Cat", "Meow!", false, {}, {}, {}, {}, {}); + localized->setLocalizedName("de", "Kater"); + localized->setLocalizedText("de", "miaut"); + return localized; +} + +TEST_F(CardQuery, SearchLanguageEnglishMatchesOnlyEnglish) +{ + const CardData localized = localizedCat(); + ASSERT_TRUE(FilterString("Cat", CardSearchLanguage{"de", SearchLanguageMode::English}).check(localized)); + ASSERT_FALSE(FilterString("Kater", CardSearchLanguage{"de", SearchLanguageMode::English}).check(localized)); +} + +TEST_F(CardQuery, SearchLanguageSelectedMatchesLocalizedNameAndText) +{ + const CardData localized = localizedCat(); + ASSERT_TRUE(FilterString("Kater", CardSearchLanguage{"de", SearchLanguageMode::Selected}).check(localized)); + ASSERT_TRUE(FilterString("o:miaut", CardSearchLanguage{"de", SearchLanguageMode::Selected}).check(localized)); + ASSERT_FALSE(FilterString("Cat", CardSearchLanguage{"de", SearchLanguageMode::Selected}).check(localized)); +} + +TEST_F(CardQuery, SearchLanguageSelectedFallsBackToEnglishForUntranslatedCards) +{ + const CardData localized = localizedCat(); + ASSERT_TRUE(FilterString("Cat", CardSearchLanguage{"fr", SearchLanguageMode::Selected}).check(localized)); + ASSERT_FALSE(FilterString("Kater", CardSearchLanguage{"fr", SearchLanguageMode::Selected}).check(localized)); +} + +TEST_F(CardQuery, SearchLanguageBothMatchesEitherLanguage) +{ + const CardData localized = localizedCat(); + ASSERT_TRUE(FilterString("Cat", CardSearchLanguage{"de", SearchLanguageMode::Both}).check(localized)); + ASSERT_TRUE(FilterString("Kater", CardSearchLanguage{"de", SearchLanguageMode::Both}).check(localized)); +} + +TEST_F(CardQuery, SearchLanguageIsBoundPerInstance) +{ + const CardData localized = localizedCat(); + + FilterString germanQuery("Kater", CardSearchLanguage{"de", SearchLanguageMode::Selected}); + ASSERT_TRUE(germanQuery.check(localized)); + + // Constructing an English-bound instance afterwards must not change the + // language the earlier instance searches in. + FilterString englishQuery("Kater", CardSearchLanguage{"", SearchLanguageMode::English}); + ASSERT_FALSE(englishQuery.check(localized)); + ASSERT_TRUE(germanQuery.check(localized)); +} + } // namespace int main(int argc, char **argv) diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index 0341b2b99..6e674cb86 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -575,6 +575,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(SearchLanguageMode::English)); +} + +TEST_F(SettingsDefaultsTest, CardsDisplay_CardSearchLanguage_SetAndGet) +{ + CardsDisplaySettings s(settingsPath, nullptr); + s.setCardSearchLanguage(static_cast(SearchLanguageMode::Selected)); + ASSERT_EQ(s.getCardSearchLanguage(), static_cast(SearchLanguageMode::Selected)); +} + // --- VisualDeckStorageSettings --- TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)