Cockatrice/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp
Lukas Brübach e029173e29 [Card] Add a setting for the language used in card search
Localized card names and texts can now be searched too, controlled by a
'Language used in card search' toggle (English, selected card language, or
both) on the general settings page. Untranslated cards always keep matching
in English.

Removed the redundant local copy of the URL templates list in the localized
picture loader while here.
2026-09-18 12:09:20 +02:00

115 lines
3.5 KiB
C++

#include "card_search_model.h"
#include "../card_database_display_model.h"
#include "../card_database_model.h"
#include <algorithm>
#include <libcockatrice/utility/levenshtein.h>
CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent)
: QAbstractListModel(parent), sourceModel(sourceModel)
{
}
int CardSearchModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return searchResults.size();
}
QVariant CardSearchModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= searchResults.size()) {
return QVariant();
}
const SearchResult &result = searchResults.at(index.row());
if (role == Qt::DisplayRole) {
return result.card->getName();
}
if (role == CardInfoRole) {
return QVariant::fromValue(result.card);
}
return QVariant();
}
void CardSearchModel::updateSearchResults(const QString &query)
{
beginResetModel();
searchResults.clear();
if (query.isEmpty() || !sourceModel) {
endResetModel();
return;
}
CardDatabaseModel *sourceDbModel = qobject_cast<CardDatabaseModel *>(sourceModel->sourceModel());
if (!sourceDbModel) {
endResetModel();
return;
}
const QString lowerQuery = query.toLower();
QList<SearchResult> prefixMatches;
QList<SearchResult> containsMatches;
// Iterate the raw database model directly so results are always complete and fresh
const int rowCount = sourceDbModel->rowCount();
for (int i = 0; i < rowCount; ++i) {
CardInfoPtr card = sourceDbModel->getCard(i);
if (!card) {
continue;
}
// The completer suggestions match against the same languages the card
// search uses, so typing a localized name finds the card.
QString matchName = card->getName();
if (searchLanguageMode != CardSearchLanguage::English && !searchLanguage.isEmpty() && searchLanguage != "en") {
matchName = card->getLocalizedName(searchLanguage);
}
const QString lowerName = matchName.toLower();
if (!lowerName.contains(lowerQuery)) {
continue;
}
const int distance = levenshteinDistance(lowerQuery, lowerName);
if (lowerName.startsWith(lowerQuery)) {
prefixMatches.append({card, distance});
} else {
containsMatches.append({card, distance});
}
}
auto sortByDistanceThenLength = [this](const SearchResult &a, const SearchResult &b) {
QString nameA = a.card->getName();
QString nameB = b.card->getName();
if (searchLanguageMode != CardSearchLanguage::English && !searchLanguage.isEmpty() && searchLanguage != "en") {
nameA = a.card->getLocalizedName(searchLanguage);
nameB = b.card->getLocalizedName(searchLanguage);
}
if (a.distance != b.distance) {
return a.distance < b.distance;
}
return nameA.size() < nameB.size();
};
std::sort(prefixMatches.begin(), prefixMatches.end(), sortByDistanceThenLength);
std::sort(containsMatches.begin(), containsMatches.end(), sortByDistanceThenLength);
// Prefix matches always come first, then contains-only matches
searchResults.reserve(prefixMatches.size() + containsMatches.size());
searchResults.append(prefixMatches);
searchResults.append(containsMatches);
// Keep only the top 10 results
if (searchResults.size() > 10) {
searchResults = searchResults.mid(0, 10);
}
endResetModel();
}