mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 00:55:09 -07:00
Compare commits
2 commits
8a97d23e20
...
1b7f9c0de4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b7f9c0de4 | ||
|
|
6d06ae2bcf |
31 changed files with 75 additions and 801 deletions
|
|
@ -43,12 +43,6 @@ 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
|
||||
|
|
@ -122,7 +116,7 @@ static void setupParserRules()
|
|||
|
||||
// actual functionality
|
||||
search["DeckContentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
|
||||
auto cardFilter = FilterString(std::any_cast<QString>(sv[0]), deckSearchLanguageContext);
|
||||
auto cardFilter = FilterString(std::any_cast<QString>(sv[0]));
|
||||
auto numberMatcher = sv.size() > 1 ? std::any_cast<NumberMatcher>(sv[1]) : [](int count) { return count > 0; };
|
||||
|
||||
return [=](const DeckSearchData &data) -> bool {
|
||||
|
|
@ -192,7 +186,7 @@ DeckFilterString::DeckFilterString()
|
|||
_error = "Not initialized";
|
||||
}
|
||||
|
||||
DeckFilterString::DeckFilterString(const QString &expr, const CardSearchLanguage &searchLanguage)
|
||||
DeckFilterString::DeckFilterString(const QString &expr)
|
||||
{
|
||||
QByteArray ba = expr.simplified().toUtf8();
|
||||
|
||||
|
|
@ -205,8 +199,6 @@ DeckFilterString::DeckFilterString(const QString &expr, const CardSearchLanguage
|
|||
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));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string");
|
||||
|
||||
|
|
@ -36,7 +35,7 @@ class DeckFilterString
|
|||
{
|
||||
public:
|
||||
DeckFilterString();
|
||||
explicit DeckFilterString(const QString &expr, const CardSearchLanguage &searchLanguage = {});
|
||||
explicit DeckFilterString(const QString &expr);
|
||||
bool check(const DeckSearchData &data) const
|
||||
{
|
||||
return filter(data);
|
||||
|
|
|
|||
|
|
@ -16,13 +16,11 @@
|
|||
#include <QLineEdit>
|
||||
#include <QRadioButton>
|
||||
#include <QTreeView>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/models/database/card_database_model.h>
|
||||
#include <libcockatrice/models/database/token/token_display_model.h>
|
||||
#include <libcockatrice/settings/card_override_settings.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
#include <libcockatrice/settings/interface_settings.h>
|
||||
#include <libcockatrice/settings/layouts_settings.h>
|
||||
#include <libcockatrice/utility/string_limits.h>
|
||||
|
|
@ -90,17 +88,6 @@ 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<SearchLanguageMode>(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"));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#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"
|
||||
|
|
@ -12,13 +11,11 @@
|
|||
#include <QGraphicsSceneWheelEvent>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/protocol/pb/command_dump_zone.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_dump_zone.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
|
||||
/**
|
||||
* @param parent the parent QGraphicsWidget containing the reveal zone
|
||||
|
|
@ -256,10 +253,7 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca
|
|||
|
||||
void ZoneViewZone::setFilterString(const QString &_filterString)
|
||||
{
|
||||
const CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay();
|
||||
filterString = FilterString(
|
||||
_filterString, CardSearchLanguage{cardsDisplay.getCardLang(),
|
||||
static_cast<SearchLanguageMode>(cardsDisplay.getCardSearchLanguage())});
|
||||
filterString = FilterString(_filterString);
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
#include <QStyle>
|
||||
#include <QStyleOption>
|
||||
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
#include <libcockatrice/settings/interface_settings.h>
|
||||
|
||||
namespace
|
||||
|
|
@ -169,12 +168,6 @@ 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);
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ void CardPictureToLoad::populateSetUrls()
|
|||
}
|
||||
}
|
||||
|
||||
for (const QString &urlTemplate : urlTemplates) {
|
||||
const QStringList orderedTemplates = urlTemplates;
|
||||
for (const QString &urlTemplate : orderedTemplates) {
|
||||
QString transformedUrl = transformUrl(urlTemplate);
|
||||
|
||||
if (!transformedUrl.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@
|
|||
#include <QHeaderView>
|
||||
#include <QToolButton>
|
||||
#include <QTreeView>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/card/relation/card_relation.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
|
||||
DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent, CardDatabaseModel *databaseModel)
|
||||
: QWidget(parent)
|
||||
|
|
@ -42,17 +40,6 @@ 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<SearchLanguageMode>(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);
|
||||
|
|
|
|||
|
|
@ -63,26 +63,13 @@ GeneralSettingsPage::GeneralSettingsPage()
|
|||
connect(&cardLanguageBox, qOverload<int>(&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<int>(SearchLanguageMode::English)
|
||||
: cardSearchLanguageIndex);
|
||||
|
||||
connect(&cardSearchLanguageBox, qOverload<int>(&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(&cardSearchLanguageLabel, 3, 0);
|
||||
languageGrid->addWidget(&cardSearchLanguageBox, 3, 1);
|
||||
languageGrid->addWidget(&advertiseTranslationPageLabel, 4, 1, Qt::AlignRight);
|
||||
languageGrid->addWidget(&advertiseTranslationPageLabel, 3, 1, Qt::AlignRight);
|
||||
|
||||
cardLanguageNoteLabel.setWordWrap(true);
|
||||
cardLanguageNoteLabel.setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
|
|
@ -494,11 +481,6 @@ void GeneralSettingsPage::cardLanguageBoxChanged(int index)
|
|||
}
|
||||
}
|
||||
|
||||
void GeneralSettingsPage::cardSearchLanguageBoxChanged(int index)
|
||||
{
|
||||
SettingsCache::instance().cardsDisplay().setCardSearchLanguage(index);
|
||||
}
|
||||
|
||||
void GeneralSettingsPage::updateStartupServerControlsVisibility()
|
||||
{
|
||||
const int index = startupTabSelector.currentIndex();
|
||||
|
|
@ -520,12 +502,6 @@ 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<int>(SearchLanguageMode::English), tr("English"));
|
||||
cardSearchLanguageBox.setItemText(static_cast<int>(SearchLanguageMode::Selected),
|
||||
tr("Selected card language (untranslated cards still match in English)"));
|
||||
cardSearchLanguageBox.setItemText(static_cast<int>(SearchLanguageMode::Both),
|
||||
tr("English and selected card language"));
|
||||
advertiseTranslationPageLabel.setText(
|
||||
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ private slots:
|
|||
void resetAllPathsClicked();
|
||||
void languageBoxChanged(int index);
|
||||
void cardLanguageBoxChanged(int index);
|
||||
void cardSearchLanguageBoxChanged(int index);
|
||||
void updateStartupServerControlsVisibility();
|
||||
|
||||
private:
|
||||
|
|
@ -56,9 +55,6 @@ private:
|
|||
QComboBox cardLanguageBox;
|
||||
QLabel cardLanguageNoteLabel;
|
||||
|
||||
QLabel cardSearchLanguageLabel;
|
||||
QComboBox cardSearchLanguageBox;
|
||||
|
||||
QLabel updateReleaseChannelLabel;
|
||||
QComboBox updateReleaseChannelBox;
|
||||
QCheckBox startupUpdateCheckCheckBox;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#include "completer_utils.h"
|
||||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "card_completer_styler.h"
|
||||
|
||||
#include <QCompleter>
|
||||
|
|
@ -8,26 +7,13 @@
|
|||
#include <QObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringListModel>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
|
||||
#include <libcockatrice/models/database/card/card_search_model.h>
|
||||
#include <libcockatrice/models/database/card_database_display_model.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
void applyCardSearchLanguage(CardSearchModel *searchModel)
|
||||
{
|
||||
const CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay();
|
||||
searchModel->setSearchLanguage(CardSearchLanguage{
|
||||
cardsDisplay.getCardLang(), static_cast<SearchLanguageMode>(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);
|
||||
|
|
@ -41,12 +27,6 @@ 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};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,17 +41,6 @@ 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<SearchLanguageMode>(cardsDisplay.getCardSearchLanguage())});
|
||||
};
|
||||
applyCardSearchLanguage();
|
||||
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
|
||||
applyCardSearchLanguage);
|
||||
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardSearchLanguageChanged, this,
|
||||
applyCardSearchLanguage);
|
||||
|
||||
cards = new QList<ExactCard>;
|
||||
connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this,
|
||||
&VisualDatabaseDisplayWidget::modelDirty);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
#include "visual_deck_storage_sort_filter_proxy_model.h"
|
||||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../../filters/deck_filter_string.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <algorithm>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
|
||||
VisualDeckStorageSortFilterProxyModel::VisualDeckStorageSortFilterProxyModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
|
|
@ -218,10 +215,7 @@ void VisualDeckStorageSortFilterProxyModel::updateSearchMatches()
|
|||
return;
|
||||
}
|
||||
|
||||
const auto &cardsDisplay = SettingsCache::instance().cardsDisplay();
|
||||
DeckFilterString filterString(
|
||||
searchText, CardSearchLanguage{cardsDisplay.getCardLang(),
|
||||
static_cast<SearchLanguageMode>(cardsDisplay.getCardSearchLanguage())});
|
||||
DeckFilterString filterString(searchText);
|
||||
for (int row = 0; row < count; ++row) {
|
||||
const DeckPreviewData &data = source->dataForRow(row);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
#include <libcockatrice/settings/paths_settings.h>
|
||||
#include <libcockatrice/settings/visual_deck_storage_settings.h>
|
||||
|
||||
|
|
@ -119,13 +118,6 @@ 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -33,178 +33,20 @@ issue a load request, which will first look for local images on-disk and then co
|
|||
found, use the stored binary data from the network cache to populate the in-memory pixmap cache under the card's cache
|
||||
key. If it is not found, it will then proceed with issuing a network request.
|
||||
|
||||
The size of both of these caches can be configured by the user on the "Storage" settings page.
|
||||
The size of both of these caches can be configured by the user in the "Card Sources" settings page.
|
||||
|
||||
# PixmapCacheKeys and ProviderIDs
|
||||
|
||||
Every card picture that is loaded ends up in the QPixmapCache under a key that identifies the exact printing it belongs
|
||||
to. The key is produced by ExactCard::getPixmapCacheKey() and has the following shape:
|
||||
|
||||
```text
|
||||
card_<card name>_<provider ID>
|
||||
```
|
||||
|
||||
For example, the _Example Card_ printing with provider ID `0b23cdc8-d413-4fb1-8470-474221b10fe2` is stored
|
||||
under `card_Example Card_0b23cdc8-d413-4fb1-8470-474221b10fe2`. If the printing has no provider ID, the key
|
||||
drops the suffix and falls back to `card_<card name>`.
|
||||
|
||||
The **provider ID** is the Scryfall UUID of the printing. Oracle maps the `scryfallId` of every printing to the `uuid`
|
||||
property when building the card database, and deck files persist it as the `uuid` attribute of each card entry. Because
|
||||
the provider ID is part of the pixmap cache key, two different printings of the same card never share a cache entry.
|
||||
This is exactly what allows the printing selector and exact-card lookups to display the picture of the precise printing
|
||||
a card was added as.
|
||||
|
||||
The base key holds the full-size image. When a widget asks for a scaled version, the scaled pixmap is stored under an
|
||||
additional key of the form `<key>_<width>x<height>`, with the size adjusted for the device pixel ratio of the screen,
|
||||
so each widget size is only ever scaled once.
|
||||
|
||||
The cache key is also used for bookkeeping outside of the pixmap cache itself:
|
||||
|
||||
- CardPictureLoaderWorker keeps a set of keys that are currently being loaded so the same card is never queued twice.
|
||||
- CardPictureLoader tracks, per key, the last time loading failed. A failed load stores a NULL pixmap under the key; as
|
||||
long as that marker is present, subsequent requests for the card show the "failed" card back and are only re-queued
|
||||
after the retry interval of 300 seconds has passed.
|
||||
- When the CardInfo of a loaded card is destroyed, its cache entries and failure markers are removed.
|
||||
TODO
|
||||
|
||||
# The Redirect Cache
|
||||
|
||||
Many picture URLs - in particular the Gatherer and Scryfall URLs from the default set of templates - redirect to a CDN
|
||||
or to a different host. To avoid following the same redirect for every single card, CardPictureLoaderWorker remembers
|
||||
redirects and applies them without an extra network round trip.
|
||||
|
||||
The redirect cache is a hash map from original URL to redirect URL plus timestamp. It is persisted to a `cache.ini`
|
||||
file (Qt's INI format, under the `redirects` array) inside the redirect cache directory
|
||||
(`SettingsCache::instance().paths().getRedirectCachePath()`, i.e. `<cache directory>/redirects/`). The cache is loaded when the worker
|
||||
starts, pruned of entries older than the configured TTL, and written back to disk when the application quits.
|
||||
|
||||
Entries are added whenever a network reply reports a redirection (see below) and are consulted before any request is
|
||||
made: both CardPictureLoaderWorker::queueRequest() and CardPictureLoaderWorker::makeRequest() check for a cached
|
||||
redirect first and jump straight to the final URL.
|
||||
|
||||
The TTL is the "Redirect Cache TTL" setting on the "Storage" settings page and defaults to 30 days. Lowering it makes
|
||||
Cockatrice re-resolve redirects sooner, which can help when a download URL changed its redirect target.
|
||||
|
||||
Because Cockatrice tracks redirects itself, the QNetworkAccessManager is configured with Qt's `ManualRedirectPolicy`.
|
||||
Redirects found in a reply are handled manually:
|
||||
|
||||
- A recursive redirect (a URL redirecting to itself) is treated as a failed load.
|
||||
- Otherwise the redirect is recorded in the redirect cache and the request is re-issued against the target URL.
|
||||
- A successful reply with one of the redirect status codes 301, 302, 303, 305, 307 or 308 is handled the same way.
|
||||
|
||||
Clearing the network cache (CardPictureLoader::clearNetworkCache()) also clears the redirect cache.
|
||||
TODO
|
||||
|
||||
# Local Image Loading
|
||||
|
||||
Before any network request is issued, CardPictureLoaderWorker hands the ExactCard to CardPictureLoaderLocal, which
|
||||
tries to find a matching picture on disk. If a local picture is found, it is used and no network request is made.
|
||||
|
||||
CardPictureLoaderLocal searches three locations:
|
||||
|
||||
- The **CUSTOM folder** (`<pictures directory>/CUSTOM/`). Every file in it is indexed recursively by its base name
|
||||
(both `baseName` and `completeBaseName`, so a file named `ExampleCard.jpg` is indexed as `ExampleCard`). The index is rebuilt
|
||||
every 10 seconds, so new files are picked up without restarting the
|
||||
client (changing the configured pictures directory only reassigns the search paths; the next timer tick rebuilds the index).
|
||||
- The **set-named subfolders** of the pictures directory: `<pictures directory>/<set code>/` and
|
||||
`<pictures directory>/downloadedPics/<set code>/`.
|
||||
- The **root of the `downloadedPics` folder** (`<pictures directory>/downloadedPics/`). The export naming schemes without a
|
||||
set-folder part write their files straight into `downloadedPics/`, so this is where flat-scheme downloads and the
|
||||
local overrides described below are matched.
|
||||
|
||||
For each candidate folder, the loader generates file-name variants from the card's corrected name, set code, collector
|
||||
number and provider ID using the import naming schemes (Card Name + Provider ID, Card Name + Set + Collector,
|
||||
Set + Collector + Card Name, Card Name + Set, Card Name), each tried with both `_` and `-` as separator. A file is
|
||||
accepted when its name without the extension *equals* the variant exactly - the extension itself is free - and the
|
||||
first variant that yields a readable image wins. For example, the file `Example Card_EXM_43.png` in the `EXM` set
|
||||
folder matches the card with corrected name `Example Card`, set code `EXM` and collector number `43`.
|
||||
|
||||
\attention The file-name variants use the *corrected* card name, so split cards are stored under their joined name: the
|
||||
"Example // Card" card is matched by a file named `ExampleCard.*`.
|
||||
|
||||
The naming schemes are also documented in the user-facing page @ref custom_card_pictures, which additionally covers
|
||||
the CUSTOM folder workflow, `picurl` and download URL templates.
|
||||
|
||||
When the filesystem cache method is selected on the "Storage" settings page, downloaded images are additionally written
|
||||
into `<pictures directory>/downloadedPics/` using the configured export naming scheme (as `.png` files). The two export
|
||||
schemes with a set-folder part (`Set Folder / Name + Provider ID` and `Set Folder / Name + Set Name + Collector`) write
|
||||
into `downloadedPics/<set code>/`; the three flat schemes write directly into `downloadedPics/`. Automatic cache writes
|
||||
never overwrite an existing file, so a provider outage can permanently leave an outdated image in that folder until it
|
||||
is deleted manually - the user-facing troubleshooting guide @ref fixing_card_pictures covers how to do this. Explicit
|
||||
image overrides (see below) are the exception and always overwrite.
|
||||
|
||||
# Local Image Overrides
|
||||
|
||||
Beyond the generic on-disk lookup above, individual printings can be given explicit artwork that wins over every other
|
||||
source without touching the CUSTOM folder or any download URL. This is the "Image Overrides" submenu of the context menu
|
||||
that opens when you right-click a card in the deck editor's printing selector.
|
||||
|
||||
- **Load Custom Image...** asks for a picture file and installs it for the card through
|
||||
CardPictureLoader::saveCardImageToLocalStorage() with `allowOverwrite == true`.
|
||||
- **One entry per alternate printing** (labeled `<set> <collector number>`): selecting one hands the card to
|
||||
CardPictureLoader::installPrintingOverride(), which resolves that printing's artwork - enqueueing a load and waiting
|
||||
for the `CardInfo::pixmapUpdated` signal if it is not cached yet - and persists it for the card.
|
||||
- **Clear Custom Image** calls CardPictureLoader::deleteAllLocalOverrides() to remove every stored override image of the
|
||||
card, after which normal resolution resumes. The entry is only enabled while CardPictureLoader::hasLocalOverrides()
|
||||
reports at least one stored file.
|
||||
|
||||
Overrides are stored as `.png` files in `downloadedPics/` under the export naming scheme configured on the "Storage"
|
||||
settings page - which is exactly why the local matcher also looks into the `downloadedPics/` root (see above). They are
|
||||
written with `allowOverwrite == true`, so an override always replaces whatever the filesystem cache previously saved for
|
||||
that spelling; only *automatic* cache writes are prevented from clobbering it. Overriding a card with its own current
|
||||
printing is a no-op (the UI omits it from the menu), and an override whose artwork fails to resolve surfaces the
|
||||
"failed" card back instead of a silent no-op while any override already on disk is left in place and re-displayed.
|
||||
TODO
|
||||
|
||||
# URL Generation and Resolution
|
||||
|
||||
When no local image is available and downloading is enabled, the network loader starts working through a list of
|
||||
candidate URLs. This list is managed by CardPictureToLoad and is built in two steps.
|
||||
|
||||
First, CardPictureToLoad::extractSetsSorted() collects all sets the card has printings in and sorts them by set
|
||||
priority. Unless the user disabled per-printing art ("Override all card art with personal set preference (Pre-ProviderID
|
||||
change behavior)"), the set that
|
||||
matches the requested printing's provider ID is moved to the front, so the exact printing is always attempted first.
|
||||
|
||||
For each set, CardPictureToLoad::populateSetUrls() builds an ordered URL list:
|
||||
|
||||
1. A custom URL defined for that printing via the `picurl` property in the card database, if present.
|
||||
2. The configured download URL templates, in priority order (Deck Editor → "URL Download Priority").
|
||||
|
||||
URL templates are transformed into concrete URLs by CardPictureToLoad::transformUrl(), which substitutes reference
|
||||
points. `!name!`, `!setcode!` and friends substitute card and printing data, while the `!set:<property>!` and
|
||||
`!prop:<property>!` reference points resolve a property of the printing or of the card respectively. The canonical list
|
||||
of all reference points with examples, including the `_fill_with_` and `_substr_` modifiers, lives in
|
||||
@ref custom_card_pictures.
|
||||
|
||||
The `!set:...!` and `!prop:...!` reference points also support two modifiers:
|
||||
|
||||
- `_fill_with_<text>` pads the value with the given text, right-aligned, e.g. `!set:num_fill_with_000!` turns collector
|
||||
number `1` into `001`. If the value is longer than the fill text, the template is invalidated.
|
||||
- `_substr_<start>_<length>` extracts a substring, e.g. `!set:num_substr_2_2!` takes two characters starting at the
|
||||
third. If the substring would extend past the end of the value, the template is invalidated.
|
||||
|
||||
Substituted values are percent-encoded. If a template asks for a property the card or printing does not have (or one of
|
||||
the modifiers invalidates it), the template yields no URL and is skipped; the next template is tried instead.
|
||||
|
||||
\attention Custom URLs should start with `http://` or `https://`. The scheme is not validated before the URL is handed
|
||||
to QNetworkAccessManager, so a template without an absolute scheme may silently fail to download; prefer HTTPS where the
|
||||
provider allows it.
|
||||
|
||||
The resolution order is: for the current set, try each URL in the list; when all URLs for a set are exhausted, move to
|
||||
the next set; when every set is exhausted, the load fails. A failed load is reported through the NULL-pixmap mechanism
|
||||
described in the PixmapCacheKeys and ProviderIDs section above.
|
||||
|
||||
Several mechanisms influence the resolution process:
|
||||
|
||||
- **Rate limiting.** The worker allows roughly 10 requests per second globally. A server that answers with HTTP 429
|
||||
gets its per-host allowance halved; the first 429 for a host is waited out (honoring the `Retry-After` header if
|
||||
present) and the same URL retried, while a second 429 makes the loader fall through to the other configured sources.
|
||||
When all sources are exhausted the request is deferred with some random jitter and retried once the back-off expires.
|
||||
- **Redirects.** Replies with a redirect status (301, 302, 303, 305, 307, 308) are followed and recorded in the
|
||||
redirect cache as described in the Redirect Cache section above.
|
||||
- **Blacklisted images.** Gatherer returns the card back image for cards it does not know. A few known MD5 hashes of
|
||||
that image are blacklisted, so such a "successful" download is treated as not found instead of being shown.
|
||||
- **WebP.** Images detected as WebP (RIFF/WEBP header) are decoded through QMovie instead of QImageReader.
|
||||
- **Downloads disabled.** When "Download card pictures on the fly" is disabled and the network cache method is active,
|
||||
requests use Qt's `AlwaysCache` policy so that only previously cached images are served.
|
||||
|
||||
A user-facing reference for writing download URL templates, including more worked examples, is available at
|
||||
@ref custom_card_pictures.
|
||||
TODO
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
@page custom_card_pictures Custom Card Pictures
|
||||
|
||||
There are four ways to make Cockatrice use custom artwork for your cards:
|
||||
|
||||
- Placing image files in the **CUSTOM pictures folder**.
|
||||
- Providing a **custom card database** that points each printing at a picture URL via the `picurl` property.
|
||||
- Writing your **own download URL templates**.
|
||||
- Setting an **image override** for a single card from inside the deck editor.
|
||||
|
||||
Each of these is described below. If pictures are missing or wrong, see @ref fixing_card_pictures instead.
|
||||
|
||||
# Custom Pictures Folder (CUSTOM)
|
||||
|
||||
Any image file placed in the CUSTOM folder is used as the card picture, and no download is attempted for cards that
|
||||
match a file there.
|
||||
|
||||
- The folder is `<pictures directory>/CUSTOM/`. The pictures directory is configured on the 'General' settings tab,
|
||||
under 'Directories' → 'Pictures directory'.
|
||||
- Any image format Qt can decode is accepted (PNG, JPG/JPEG, WebP, GIF, BMP, ...); the file extension is not filtered,
|
||||
so even an extension-less file is picked up if the decoder recognizes its content.
|
||||
- Files are indexed by their name, so you can organize them into subfolders freely.
|
||||
- New or changed files are picked up automatically within a few seconds — no client restart is required.
|
||||
|
||||
The file name must match the card using one of the naming schemes below. Both `_` and `-` are accepted as separators,
|
||||
and the file extension is ignored when matching:
|
||||
|
||||
| Scheme | Example file name |
|
||||
| --------------------------- | ------------------------------------------------------- |
|
||||
| Card Name | `Example Card.png` |
|
||||
| Card Name + Set | `Example Card_DDL.png` |
|
||||
| Card Name + Set + Collector | `Example Card_DDL_43.png` |
|
||||
| Set + Collector + Card Name | `DDL_43_Example Card.png` |
|
||||
| Card Name + Provider ID | `Example Card_0b23cdc8-d413-4fb1-8470-474221b10fe2.png` |
|
||||
|
||||
The name used for matching is the *corrected* card name. Correction removes the split-card separator ` // ` and the
|
||||
characters reserved in Windows file names (`* < > : " \ ?` and control characters), and turns `/` into a space, so the
|
||||
"Example // Card" card is matched by a file named `ExampleCard.png`, not `Example // Card.png`. Most other punctuation
|
||||
(commas, apostrophes, `!`, ...) is left untouched.
|
||||
|
||||
\attention A file in the CUSTOM folder always wins over downloaded pictures, even if it is the wrong image. Delete the
|
||||
file if you want to see the downloaded artwork again.
|
||||
|
||||
The naming conventions are the same as those recognized in the set-named subfolders and in `downloadedPics`, and are
|
||||
documented for developers in @ref loading_card_pictures.
|
||||
|
||||
# Custom Card Database (picurl)
|
||||
|
||||
If you maintain your own card database (see the
|
||||
[Custom Cards & Sets](https://github.com/Cockatrice/Cockatrice/wiki/Custom-Cards-&-Sets) wiki), each printing's `<set>`
|
||||
tag can carry a `picurl` attribute containing a full URL for that printing's picture:
|
||||
|
||||
```xml
|
||||
<set picurl="https://example.com/cards/example-card.jpg" ...>
|
||||
```
|
||||
|
||||
Cockatrice tries this URL **before** the configured download URL templates, so it is the most direct way to provide
|
||||
custom artwork for a specific printing.
|
||||
|
||||
- The URL should start with `http://` or `https://`; the scheme is not validated, so make sure it is absolute or the
|
||||
download may silently fail.
|
||||
- When you change a `picurl` for a card whose picture was already downloaded and cached, delete the stored images
|
||||
(Storage tab → 'Delete Saved Images' / 'Delete Cached Images') so Cockatrice fetches the new URL.
|
||||
|
||||
# Custom Download URL Templates
|
||||
|
||||
The built-in download URLs are templates: Cockatrice replaces reference points in the URL with information about the
|
||||
card and its printing. You can write your own templates in 'Cockatrice → Settings' (Ctrl + Shift + P by default), on
|
||||
the 'Deck Editor' tab, in the 'URL Download Priority' section.
|
||||
|
||||
The following reference points are available:
|
||||
|
||||
| Reference point | Description | Example |
|
||||
| ------------------------------- | ------------------------------- | -------------- |
|
||||
| `!name!` | Card name | `Example Card` |
|
||||
| `!name_lower!` | Card name, lower case | `example card` |
|
||||
| `!corrected_name!` | Corrected card name | `ExampleCard` (instead of "Example // Card") |
|
||||
| `!corrected_name_lower!` | Corrected card name, lower case | `examplecard` |
|
||||
| `!sflang!` | Scryfall language code for the current client language; defaults to English when the language has no localized images | `en`, `zhs` |
|
||||
| `!setcode!` / `!setcode_lower!` | Set code | `EXM` / `exm` |
|
||||
| `!setname!` / `!setname_lower!` | Full set name | `Exemplary Set` / `exemplary set` |
|
||||
| `!set:<property>!` | A property of this printing, e.g. `muid` (Gatherer multiverse ID), `uuid` (Scryfall UUID), `num` (collector number), `rarity` | `373549` |
|
||||
| `!prop:<property>!` | A property of the card, e.g. `side` (front/back), `colors`, `cmc`, `coloridentity`, `type`, `pt`, and the format legality statuses | `front` |
|
||||
|
||||
The `!set:...!` and `!prop:...!` reference points support two modifiers:
|
||||
|
||||
- `_fill_with_<text>` pads the value with the given text, right-aligned, e.g. `!set:num_fill_with_000!` turns collector
|
||||
number `1` into `001`. If the value is longer than the fill text, the template is skipped.
|
||||
- `_substr_<start>_<length>` extracts a substring, e.g. `!set:num_substr_2_2!` takes two characters starting at the
|
||||
third. If the substring would extend past the end of the value, the template is skipped.
|
||||
|
||||
Substituted values are URL-encoded. A template that asks for a property the card or printing does not have is skipped,
|
||||
and the next template in the list is tried instead.
|
||||
|
||||
\attention Custom URLs should start with `http://` or `https://`. As with `picurl`, the scheme is not validated before
|
||||
the URL is handed to QNetworkAccessManager, so use an absolute URL or the download may silently fail.
|
||||
|
||||
Some working examples:
|
||||
|
||||
```text
|
||||
https://cards.scryfall.io/large/!prop:side!/!set:uuid_substr_0_1!/!set:uuid_substr_1_1!/!set:uuid!.jpg
|
||||
https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!
|
||||
https://api.scryfall.com/cards/multiverse/!set:muid!?format=image
|
||||
https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card
|
||||
https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card
|
||||
```
|
||||
|
||||
See the [Custom Picture Download URLs](https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs)
|
||||
wiki for more examples and ideas.
|
||||
|
||||
\attention Keep in mind that templates using `!name!` or `!set:muid!` resolve by name or multiverse ID, not by the
|
||||
exact printing. Only the Scryfall `!set:uuid!` templates always return the exact printing requested. See
|
||||
@ref fixing_card_pictures for more on this.
|
||||
|
||||
# Image Overrides
|
||||
|
||||
The quickest way to give one card custom art is an image override: right-click the card in the deck editor's printing
|
||||
selector and open the **Image Overrides** submenu of the context menu.
|
||||
|
||||
- **Load Custom Image...** — choose a picture file (the dialog suggests PNG, JPG/JPEG and WebP); it becomes that card's
|
||||
artwork immediately.
|
||||
- **One entry per alternate printing** of the card, labeled `<set> <collector number>` (hovering an entry previews that
|
||||
printing's artwork). Selecting one makes the card use that exact printing's picture, so e.g. a basic land can be shown
|
||||
with any of its artworks.
|
||||
- **Clear Custom Image** — removes the stored override and returns the card to normal resolution. It is only available
|
||||
while the card has a stored override.
|
||||
|
||||
Overrides are stored as `.png` files in `<pictures directory>/downloadedPics/`, under the "Naming scheme" configured on
|
||||
the Storage settings page, and are matched the same way as downloaded images. Because local files are checked before any
|
||||
URL is requested, an override always wins over downloaded artwork and `picurl` for that card. The override exists only on
|
||||
the machine it was created on - it is not part of the deck file - so a card with a stored override shows normally on
|
||||
another computer.
|
||||
|
||||
\attention If you also keep a matching file in the CUSTOM folder, that file is matched before the override. When you
|
||||
change an override, use **Clear Custom Image** so the stored `.png` is replaced; manually deleting the file in
|
||||
`downloadedPics/` has the same effect.
|
||||
|
|
@ -11,10 +11,6 @@
|
|||
|
||||
- @subpage beta_release
|
||||
|
||||
## Card Pictures
|
||||
|
||||
- @subpage custom_card_pictures
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- @subpage fixing_card_pictures
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ valid URLs. If you suspect the list has been modified or corrupted, press 'Reset
|
|||
defaults.
|
||||
|
||||
For information on how to add your own custom URL templates, see the 'How to add a custom URL' link in the same
|
||||
settings section, or @ref custom_card_pictures for a full reference of the URL reference points, the CUSTOM
|
||||
pictures folder, and custom card databases.
|
||||
settings section.
|
||||
|
||||
# Check Your Local Picture Folder
|
||||
|
||||
|
|
@ -42,12 +41,8 @@ Cockatrice checks the following locations, in order:
|
|||
- The custom pictures folder (recursively indexed by file name).
|
||||
- `<pictures directory>/<set code>/<card file name>`
|
||||
- `<pictures directory>/downloadedPics/<set code>/<card file name>`
|
||||
- `<pictures directory>/downloadedPics/<card file name>` (for export naming schemes without a set folder)
|
||||
|
||||
A file only matches when its name without the extension equals one of the recognized scheme patterns exactly.
|
||||
|
||||
The following import naming schemes are recognized (using both `_` and `-` as separators). The canonical table with
|
||||
concrete example file names is on @ref custom_card_pictures:
|
||||
The following import naming schemes are recognized (using both `_` and `-` as separators):
|
||||
|
||||
| Scheme | Pattern |
|
||||
| --------------------------- | -------------------------- |
|
||||
|
|
@ -61,10 +56,6 @@ If a picture you downloaded or placed manually is wrong, stale, or corrupted, de
|
|||
attention to the `downloadedPics` subfolder: this is where the filesystem caching method writes downloaded images, and
|
||||
after a provider outage it can permanently contain the wrong printing until you delete it manually.
|
||||
|
||||
If a card persistently shows artwork you assigned yourself, you may have an **image override** set for it. Right-click
|
||||
the card in the deck editor's printing selector and use 'Image Overrides' → 'Clear Custom Image' to remove it (or
|
||||
delete the stored `.png` in `downloadedPics/`). See @ref custom_card_pictures for details.
|
||||
|
||||
See @ref loading_card_pictures for details on how local images are loaded.
|
||||
|
||||
# Clear Caches
|
||||
|
|
|
|||
|
|
@ -4,42 +4,6 @@
|
|||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
|
|
|||
|
|
@ -74,36 +74,6 @@ 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 {
|
||||
|
|
@ -363,11 +333,7 @@ static void setupParserRules()
|
|||
|
||||
search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
||||
const CardSearchLanguage searchLanguage = searchLanguageContext;
|
||||
return [=](const CardData &x) {
|
||||
return matchesInSearchLanguage(x->getText(), x->getLocalizedText(searchLanguage.language), searchLanguage,
|
||||
matcher);
|
||||
};
|
||||
return [=](const CardData &x) { return matcher(x->getText()); };
|
||||
};
|
||||
|
||||
search["ColorQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
|
|
@ -444,11 +410,7 @@ static void setupParserRules()
|
|||
};
|
||||
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
||||
const CardSearchLanguage searchLanguage = searchLanguageContext;
|
||||
return [=](const CardData &x) {
|
||||
return matchesInSearchLanguage(x->getName(), x->getLocalizedName(searchLanguage.language), searchLanguage,
|
||||
matcher);
|
||||
};
|
||||
return [=](const CardData &x) { return matcher(x->getName()); };
|
||||
};
|
||||
|
||||
search["Color"] = [](const peg::SemanticValues &sv) -> char { return "WUBRGU"[sv.choice()]; };
|
||||
|
|
@ -463,7 +425,7 @@ FilterString::FilterString()
|
|||
_error = "Not initialized";
|
||||
}
|
||||
|
||||
FilterString::FilterString(const QString &expr, const CardSearchLanguage &searchLanguage)
|
||||
FilterString::FilterString(const QString &expr)
|
||||
{
|
||||
QByteArray ba = expr.simplified().toUtf8();
|
||||
|
||||
|
|
@ -476,8 +438,6 @@ FilterString::FilterString(const QString &expr, const CardSearchLanguage &search
|
|||
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,7 +14,6 @@
|
|||
#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");
|
||||
|
|
@ -36,7 +35,7 @@ class FilterString
|
|||
{
|
||||
public:
|
||||
FilterString();
|
||||
explicit FilterString(const QString &exp, const CardSearchLanguage &searchLanguage = {});
|
||||
explicit FilterString(const QString &exp);
|
||||
[[nodiscard]] bool check(const CardData &card) const
|
||||
{
|
||||
if (card.isNull()) {
|
||||
|
|
|
|||
|
|
@ -100,16 +100,14 @@ FilterTreeNode *FilterItemList::termNode(const QString &term)
|
|||
return childNodes.at(i);
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeAnd(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
bool FilterItemList::testTypeAnd(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
|
||||
if (!(*i)->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(*i)->acceptCardAttr(info, attr, searchLanguage)) {
|
||||
if (!(*i)->acceptCardAttr(info, attr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -117,17 +115,13 @@ bool FilterItemList::testTypeAnd(const CardInfoPtr info,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeAndNot(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
bool FilterItemList::testTypeAndNot(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
// if any one in the list is true, return false
|
||||
return !testTypeOr(info, attr, searchLanguage);
|
||||
return !testTypeOr(info, attr);
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeOr(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
bool FilterItemList::testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
bool noChildEnabledChild = true;
|
||||
|
||||
|
|
@ -140,7 +134,7 @@ bool FilterItemList::testTypeOr(const CardInfoPtr info,
|
|||
noChildEnabledChild = false;
|
||||
}
|
||||
|
||||
if ((*i)->acceptCardAttr(info, attr, searchLanguage)) {
|
||||
if ((*i)->acceptCardAttr(info, attr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -148,58 +142,20 @@ bool FilterItemList::testTypeOr(const CardInfoPtr info,
|
|||
return noChildEnabledChild;
|
||||
}
|
||||
|
||||
bool FilterItemList::testTypeOrNot(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
bool FilterItemList::testTypeOrNot(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
// if any one in the list is false, return true
|
||||
return !testTypeAnd(info, attr, searchLanguage);
|
||||
return !testTypeAnd(info, attr);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptName(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
|
||||
bool FilterItem::acceptName(const CardInfoPtr info) const
|
||||
{
|
||||
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;
|
||||
return info->getName().contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptNameExact(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
|
||||
bool FilterItem::acceptNameExact(const CardInfoPtr info) const
|
||||
{
|
||||
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;
|
||||
return info->getName() == term;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptType(const CardInfoPtr info) const
|
||||
|
|
@ -257,27 +213,9 @@ bool FilterItem::acceptColor(const CardInfoPtr info) const
|
|||
return match_count == converted_term.length();
|
||||
}
|
||||
|
||||
bool FilterItem::acceptText(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
|
||||
bool FilterItem::acceptText(const CardInfoPtr info) const
|
||||
{
|
||||
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;
|
||||
return info->getText().contains(term, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool FilterItem::acceptSet(const CardInfoPtr info) const
|
||||
|
|
@ -464,21 +402,19 @@ bool FilterItem::relationCheck(int cardInfo) const
|
|||
return result;
|
||||
}
|
||||
|
||||
bool FilterItem::acceptCardAttr(const CardInfoPtr info,
|
||||
CardFilter::Attr attr,
|
||||
const CardSearchLanguage &searchLanguage) const
|
||||
bool FilterItem::acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) const
|
||||
{
|
||||
switch (attr) {
|
||||
case CardFilter::AttrName:
|
||||
return acceptName(info, searchLanguage);
|
||||
return acceptName(info);
|
||||
case CardFilter::AttrNameExact:
|
||||
return acceptNameExact(info, searchLanguage);
|
||||
return acceptNameExact(info);
|
||||
case CardFilter::AttrType:
|
||||
return acceptType(info);
|
||||
case CardFilter::AttrColor:
|
||||
return acceptColor(info);
|
||||
case CardFilter::AttrText:
|
||||
return acceptText(info, searchLanguage);
|
||||
return acceptText(info);
|
||||
case CardFilter::AttrSet:
|
||||
return acceptSet(info);
|
||||
case CardFilter::AttrManaCost:
|
||||
|
|
@ -548,18 +484,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 CardSearchLanguage &searchLanguage) const
|
||||
bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm) const
|
||||
{
|
||||
const FilterItemList *fil;
|
||||
bool status = true;
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeAnd);
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAnd(info, lm->attr, searchLanguage)) {
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAnd(info, lm->attr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeAndNot);
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAndNot(info, lm->attr, searchLanguage)) {
|
||||
if (fil && fil->isEnabled() && !fil->testTypeAndNot(info, lm->attr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -568,23 +504,23 @@ bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm, const Card
|
|||
status = false;
|
||||
|
||||
// if this is true we can return because it is OR'd with the OrNot list
|
||||
if (fil->testTypeOr(info, lm->attr, searchLanguage)) {
|
||||
if (fil->testTypeOr(info, lm->attr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
fil = lm->findTypeList(CardFilter::TypeOrNot);
|
||||
if (fil && fil->isEnabled() && fil->testTypeOrNot(info, lm->attr, searchLanguage)) {
|
||||
if (fil && fil->isEnabled() && fil->testTypeOrNot(info, lm->attr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool FilterTree::acceptsCard(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const
|
||||
bool FilterTree::acceptsCard(const CardInfoPtr info) const
|
||||
{
|
||||
for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) {
|
||||
if ((*i)->isEnabled() && !testAttr(info, *i, searchLanguage)) {
|
||||
if ((*i)->isEnabled() && !testAttr(info, *i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -168,14 +167,10 @@ public:
|
|||
return CardFilter::typeName(type);
|
||||
}
|
||||
|
||||
[[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;
|
||||
[[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;
|
||||
};
|
||||
|
||||
class FilterItem : public FilterTreeNode
|
||||
|
|
@ -212,21 +207,20 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool acceptName(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptNameExact(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptName(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool acceptNameExact(CardInfoPtr info) 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 CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptText(CardInfoPtr info) 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 CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const;
|
||||
[[nodiscard]] bool acceptFormat(CardInfoPtr info) const;
|
||||
[[nodiscard]] bool relationCheck(int cardInfo) const;
|
||||
};
|
||||
|
|
@ -246,7 +240,7 @@ private:
|
|||
LogicMap *attrLogicMap(CardFilter::Attr attr);
|
||||
FilterItemList *attrTypeList(CardFilter::Attr attr, CardFilter::Type type);
|
||||
|
||||
bool testAttr(CardInfoPtr info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const;
|
||||
bool testAttr(CardInfoPtr info, const LogicMap *lm) const;
|
||||
|
||||
void nodeChanged() const override
|
||||
{
|
||||
|
|
@ -285,7 +279,7 @@ public:
|
|||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool acceptsCard(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const;
|
||||
[[nodiscard]] bool acceptsCard(CardInfoPtr info) const;
|
||||
void removeFiltersByAttr(CardFilter::Attr filterType);
|
||||
void removeFilter(const CardFilter *toRemove);
|
||||
void clear();
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ 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
|
||||
|
|
|
|||
|
|
@ -65,31 +65,25 @@ void CardSearchModel::updateSearchResults(const QString &query)
|
|||
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 QString lowerName = card->getName().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});
|
||||
}
|
||||
break;
|
||||
if (lowerName.startsWith(lowerQuery)) {
|
||||
prefixMatches.append({card, distance});
|
||||
} else {
|
||||
containsMatches.append({card, distance});
|
||||
}
|
||||
}
|
||||
|
||||
auto sortByDistanceThenLength = [this](const SearchResult &a, const SearchResult &b) {
|
||||
auto sortByDistanceThenLength = [](const SearchResult &a, const SearchResult &b) {
|
||||
if (a.distance != b.distance) {
|
||||
return a.distance < b.distance;
|
||||
}
|
||||
return sortableName(a.card).size() < sortableName(b.card).size();
|
||||
return a.card->getName().size() < b.card->getName().size();
|
||||
};
|
||||
|
||||
std::sort(prefixMatches.begin(), prefixMatches.end(), sortByDistanceThenLength);
|
||||
|
|
@ -107,25 +101,3 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,14 +27,6 @@ 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
|
||||
{
|
||||
|
|
@ -42,15 +34,8 @@ 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<SearchResult> searchResults;
|
||||
CardSearchLanguage searchLanguage;
|
||||
};
|
||||
|
||||
#endif // CARD_SEARCH_MODEL_H
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex
|
|||
}
|
||||
|
||||
if (filterString != nullptr) {
|
||||
if (filterTree != nullptr && !filterTree->acceptsCard(info, searchLanguage)) {
|
||||
if (filterTree != nullptr && !filterTree->acceptsCard(info)) {
|
||||
return false;
|
||||
}
|
||||
return filterString->check(info);
|
||||
|
|
@ -190,14 +190,8 @@ bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex
|
|||
|
||||
bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const
|
||||
{
|
||||
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 (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cardNameSet.isEmpty() && !cardNameSet.contains(info->getName())) {
|
||||
|
|
@ -205,7 +199,7 @@ bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const
|
|||
}
|
||||
|
||||
if (filterTree != nullptr) {
|
||||
return filterTree->acceptsCard(info, searchLanguage);
|
||||
return filterTree->acceptsCard(info);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -241,28 +235,6 @@ 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();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTimer>
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/filters/filter_string.h>
|
||||
|
||||
class FilterTree;
|
||||
|
|
@ -33,8 +32,6 @@ private:
|
|||
FilterString *filterString;
|
||||
int loadedRowCount;
|
||||
QTimer dirtyTimer;
|
||||
CardSearchLanguage searchLanguage;
|
||||
QString searchText;
|
||||
|
||||
/** The translation table that will be used for sanitizeCardName. */
|
||||
static QMap<wchar_t, wchar_t> characterTranslation;
|
||||
|
|
@ -58,13 +55,17 @@ public:
|
|||
cardName = sanitizeCardName(_cardName, characterTranslation);
|
||||
dirty();
|
||||
}
|
||||
void setStringFilter(const QString &_src);
|
||||
void setStringFilter(const QString &_src)
|
||||
{
|
||||
delete filterString;
|
||||
filterString = new FilterString(_src);
|
||||
dirty();
|
||||
}
|
||||
void setCardNameSet(const QSet<QString> &_cardNameSet)
|
||||
{
|
||||
cardNameSet = _cardNameSet;
|
||||
dirty();
|
||||
}
|
||||
void setSearchLanguage(const CardSearchLanguage &searchLang);
|
||||
|
||||
void dirty()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -110,11 +110,6 @@ QString CardsDisplaySettings::getCardLang() const
|
|||
return getValue("cardLang", QString(), QString(), "en").toString();
|
||||
}
|
||||
|
||||
int CardsDisplaySettings::getCardSearchLanguage() const
|
||||
{
|
||||
return getValue("cardSearchLanguage", QString(), QString(), static_cast<int>(SearchLanguageMode::English)).toInt();
|
||||
}
|
||||
|
||||
void CardsDisplaySettings::setDisplayCardNames(bool _displayCardNames)
|
||||
{
|
||||
setValue(_displayCardNames, "displayCardNames");
|
||||
|
|
@ -247,12 +242,3 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "settings_manager.h"
|
||||
|
||||
#include <libcockatrice/card/card_localization.h>
|
||||
#include <libcockatrice/interfaces/interface_cards_display_settings_provider.h>
|
||||
|
||||
class CardsDisplaySettings : public SettingsManager, public ICardsDisplaySettingsProvider
|
||||
|
|
@ -33,7 +32,6 @@ 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);
|
||||
|
|
@ -56,7 +54,6 @@ public:
|
|||
void setArchidektPreviewCardSize(int _archidektPreviewCardSize);
|
||||
void setSampleHandSize(int _sampleHandSize);
|
||||
void setCardLang(const QString &_cardLang);
|
||||
void setCardSearchLanguage(int _cardSearchLanguage);
|
||||
|
||||
signals:
|
||||
void displayCardNamesChanged();
|
||||
|
|
@ -74,7 +71,6 @@ 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);
|
||||
|
|
|
|||
|
|
@ -73,57 +73,6 @@ 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)
|
||||
|
|
|
|||
|
|
@ -575,19 +575,6 @@ 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<int>(SearchLanguageMode::English));
|
||||
}
|
||||
|
||||
TEST_F(SettingsDefaultsTest, CardsDisplay_CardSearchLanguage_SetAndGet)
|
||||
{
|
||||
CardsDisplaySettings s(settingsPath, nullptr);
|
||||
s.setCardSearchLanguage(static_cast<int>(SearchLanguageMode::Selected));
|
||||
ASSERT_EQ(s.getCardSearchLanguage(), static_cast<int>(SearchLanguageMode::Selected));
|
||||
}
|
||||
|
||||
// --- VisualDeckStorageSettings ---
|
||||
|
||||
TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue