From 6f631e0954709bfbc198ffa7c28d5f5104979bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Mon, 17 Aug 2026 10:42:27 +0200 Subject: [PATCH] Address comments --- .../visual_deck_storage_model.cpp | 219 ++++++++++-------- .../visual_deck_storage_model.h | 52 ++--- ...l_deck_storage_sort_filter_proxy_model.cpp | 141 +++++------ ...ual_deck_storage_sort_filter_proxy_model.h | 22 +- 4 files changed, 216 insertions(+), 218 deletions(-) diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp index 4478a90fe..bf2c49604 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp @@ -20,8 +20,8 @@ namespace */ struct DeckScanResult { - QList decks; - QStringList folderPaths; + QList decks; ///< Deck rows in scan order. + QStringList folderPaths; ///< Sorted list of subfolder paths. }; /** @@ -30,8 +30,8 @@ struct DeckScanResult */ struct DeckLoadResult { - LoadedDeck deck; - QDateTime lastModified; + LoadedDeck deck; ///< The parsed deck. + QDateTime lastModified; ///< File modification time at load. }; /** @@ -49,6 +49,63 @@ QString relativePathFromDeckRoot(const QString &path, const QString &deckPath) } return relativePath; } + +/** + * @brief The path of \a filePath relative to \a deckPath, or the bare file name + * if \a filePath is not below \a deckPath. + */ +QString relativeFilePathFor(const QString &filePath, const QString &deckPath) +{ + if (filePath.startsWith(deckPath)) { + return filePath.mid(deckPath.length()); + } + + return QFileInfo(filePath).fileName(); +} + +/** + * @brief The directory of \a filePath relative to \a deckPath, or empty if the + * file sits directly in the deck root. + */ +QString folderPathFor(const QString &filePath, const QString &deckPath) +{ + return relativePathFromDeckRoot(QFileInfo(filePath).absolutePath(), deckPath); +} + +/** + * @brief Scans a deck directory on a worker thread, returning discovered deck + * files and subfolder paths. + */ +DeckScanResult scanDeckDirectory(const QString &deckPath) +{ + DeckScanResult result; + + QDirIterator fileIt(deckPath, DeckLoader::ACCEPTED_FILE_EXTENSIONS, QDir::Files, + QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + while (fileIt.hasNext()) { + const QString filePath = fileIt.next(); + DeckPreviewData data; + data.filePath = filePath; + data.relativeFilePath = relativeFilePathFor(filePath, deckPath); + data.folderPath = folderPathFor(filePath, deckPath); + data.lastModified = QFileInfo(filePath).lastModified(); + result.decks.append(std::move(data)); + } + + QSet seenFolders; + QDirIterator folderIt(deckPath, QDir::Dirs | QDir::NoDotAndDotDot, + QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + while (folderIt.hasNext()) { + const QString folderPath = relativePathFromDeckRoot(folderIt.next(), deckPath); + if (!folderPath.isEmpty() && !seenFolders.contains(folderPath)) { + seenFolders.insert(folderPath); + result.folderPaths.append(folderPath); + } + } + result.folderPaths.sort(); + + return result; +} } // namespace VisualDeckStorageModel::VisualDeckStorageModel(QObject *parent) : QAbstractListModel(parent) @@ -57,16 +114,16 @@ VisualDeckStorageModel::VisualDeckStorageModel(QObject *parent) : QAbstractListM int VisualDeckStorageModel::rowCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : decks_.size(); + return parent.isValid() ? 0 : decks.size(); } QVariant VisualDeckStorageModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 || index.row() >= decks_.size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= decks.size()) { return {}; } - const DeckPreviewData &data = decks_.at(index.row()); + const DeckPreviewData &data = decks.at(index.row()); switch (role) { case Qt::DisplayRole: case VisualDeckStorageRoles::DisplayNameRole: @@ -86,21 +143,21 @@ QVariant VisualDeckStorageModel::data(const QModelIndex &index, int role) const case VisualDeckStorageRoles::LastLoadedRole: return data.lastLoaded; case VisualDeckStorageRoles::BannerCardNameRole: - return data.bannerCardName; + return data.bannerCard.name; case VisualDeckStorageRoles::BannerCardProviderIdRole: - return data.bannerCardProviderId; + return data.bannerCard.providerId; default: return {}; } } -void VisualDeckStorageModel::setDeckPath(const QString &deckPath) +void VisualDeckStorageModel::setDeckPath(const QString &path) { - QString cleanedPath = QDir::cleanPath(deckPath); + QString cleanedPath = QDir::cleanPath(path); if (cleanedPath == ".") { cleanedPath.clear(); } - deckPath_ = cleanedPath; + deckPath = cleanedPath; startScan(); } @@ -112,10 +169,10 @@ void VisualDeckStorageModel::refresh() const DeckPreviewData &VisualDeckStorageModel::dataForRow(int row) const { static const DeckPreviewData emptyData; - if (row < 0 || row >= decks_.size()) { + if (row < 0 || row >= decks.size()) { return emptyData; } - return decks_.at(row); + return decks.at(row); } const LoadedDeck &VisualDeckStorageModel::deckForRow(int row) const @@ -125,8 +182,8 @@ const LoadedDeck &VisualDeckStorageModel::deckForRow(int row) const int VisualDeckStorageModel::rowForFilePath(const QString &filePath) const { - for (int i = 0; i < decks_.size(); ++i) { - if (decks_.at(i).filePath == filePath) { + for (int i = 0; i < decks.size(); ++i) { + if (decks.at(i).filePath == filePath) { return i; } } @@ -135,18 +192,18 @@ int VisualDeckStorageModel::rowForFilePath(const QString &filePath) const void VisualDeckStorageModel::startScan() { - ++scanGeneration_; + ++scanGeneration; beginResetModel(); - decks_.clear(); - folderPaths_.clear(); + decks.clear(); + folderPaths.clear(); endResetModel(); - if (deckPath_.isEmpty()) { + if (deckPath.isEmpty()) { return; } - const QString deckPath = deckPath_; - const int generation = scanGeneration_; + const QString currentDeckPath = deckPath; + const int generation = scanGeneration; // The scan (directory walk + one stat per file) runs on a worker thread so that // constructing the widget never stalls the UI thread on a large deck folder. @@ -154,74 +211,49 @@ void VisualDeckStorageModel::startScan() connect(watcher, &QFutureWatcher::finished, this, [this, watcher, generation] { watcher->deleteLater(); - if (generation != scanGeneration_) { + if (generation != scanGeneration) { return; // A newer scan started while this one was running; drop the stale result. } const DeckScanResult result = watcher->result(); - folderPaths_ = result.folderPaths; + folderPaths = result.folderPaths; if (result.decks.isEmpty()) { return; } beginInsertRows(QModelIndex(), 0, result.decks.size() - 1); - decks_ = result.decks; + decks = result.decks; endInsertRows(); - for (int row = 0; row < decks_.size(); ++row) { + for (int row = 0; row < decks.size(); ++row) { beginLoad(row); } }); - watcher->setFuture(QtConcurrent::run([deckPath]() -> DeckScanResult { - DeckScanResult result; - - QDirIterator fileIt(deckPath, DeckLoader::ACCEPTED_FILE_EXTENSIONS, QDir::Files, - QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); - while (fileIt.hasNext()) { - const QString filePath = fileIt.next(); - DeckPreviewData data; - data.filePath = filePath; - data.relativeFilePath = VisualDeckStorageModel::relativeFilePathFor(filePath, deckPath); - data.folderPath = VisualDeckStorageModel::folderPathFor(filePath, deckPath); - data.lastModified = QFileInfo(filePath).lastModified(); - result.decks.append(std::move(data)); - } - - QDirIterator folderIt(deckPath, QDir::Dirs | QDir::NoDotAndDotDot, - QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); - while (folderIt.hasNext()) { - const QString folderPath = relativePathFromDeckRoot(folderIt.next(), deckPath); - if (!folderPath.isEmpty() && !result.folderPaths.contains(folderPath)) { - result.folderPaths.append(folderPath); - } - } - result.folderPaths.sort(); - - return result; - })); + watcher->setFuture( + QtConcurrent::run([currentDeckPath]() -> DeckScanResult { return scanDeckDirectory(currentDeckPath); })); } void VisualDeckStorageModel::beginLoad(int row) { - if (row < 0 || row >= decks_.size() || decks_.at(row).loadInProgress) { + if (row < 0 || row >= decks.size() || decks.at(row).loadInProgress) { return; } - DeckPreviewData &data = decks_[row]; + DeckPreviewData &data = decks[row]; data.loadInProgress = true; const QString filePath = data.filePath; const DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(filePath); - const int generation = scanGeneration_; + const int generation = scanGeneration; auto *watcher = new QFutureWatcher>(this); connect(watcher, &QFutureWatcher>::finished, this, [this, watcher, filePath, generation] { watcher->deleteLater(); - if (generation != scanGeneration_) { + if (generation != scanGeneration) { return; // The deck list was re-scanned while this load was running; drop the stale result. } @@ -230,7 +262,7 @@ void VisualDeckStorageModel::beginLoad(int row) return; } - DeckPreviewData &data = decks_[row]; + DeckPreviewData &data = decks[row]; data.loadInProgress = false; std::optional result = watcher->result(); @@ -241,7 +273,7 @@ void VisualDeckStorageModel::beginLoad(int row) data.deck = std::move(result->deck); data.loadSucceeded = true; data.lastModified = result->lastModified; - computeDeckMetadata(data); + recomputeDeckMetadata(data); emit dataChanged(index(row), index(row)); emit deckLoaded(row); @@ -293,7 +325,7 @@ static QString computeColorIdentity(const LoadedDeck &deck) /** * @brief Recomputes all derived metadata of a row from its loaded deck. */ -void VisualDeckStorageModel::computeDeckMetadata(DeckPreviewData &data) const +void VisualDeckStorageModel::recomputeDeckMetadata(DeckPreviewData &data) { const DeckList &deckList = data.deck.deckList; @@ -301,64 +333,46 @@ void VisualDeckStorageModel::computeDeckMetadata(DeckPreviewData &data) const data.displayName = !data.deckName.isEmpty() ? data.deckName : QFileInfo(data.deck.lastLoadInfo.fileName).fileName(); data.tags = deckList.getTags(); data.lastLoaded = QDateTime::fromString(deckList.getLastLoadedTimestamp()); - - const CardRef bannerCard = deckList.getBannerCard(); - data.bannerCardName = bannerCard.name; - data.bannerCardProviderId = bannerCard.providerId; - + data.bannerCard = deckList.getBannerCard(); data.colorIdentity = computeColorIdentity(data.deck); } void VisualDeckStorageModel::setFilePathForRow(int row, const QString &newFilePath) { - if (row < 0 || row >= decks_.size()) { + if (row < 0 || row >= decks.size()) { return; } - DeckPreviewData &data = decks_[row]; + DeckPreviewData &data = decks[row]; data.filePath = newFilePath; - data.relativeFilePath = relativeFilePathFor(newFilePath, deckPath_); - data.folderPath = folderPathFor(newFilePath, deckPath_); -} - -QString VisualDeckStorageModel::relativeFilePathFor(const QString &filePath, const QString &deckPath) -{ - if (filePath.startsWith(deckPath)) { - return filePath.mid(deckPath.length()); - } - - return QFileInfo(filePath).fileName(); -} - -QString VisualDeckStorageModel::folderPathFor(const QString &filePath, const QString &deckPath) -{ - return relativePathFromDeckRoot(QFileInfo(filePath).absolutePath(), deckPath); + data.relativeFilePath = relativeFilePathFor(newFilePath, deckPath); + data.folderPath = folderPathFor(newFilePath, deckPath); } bool VisualDeckStorageModel::renameDeck(int row, const QString &newName) { - if (row < 0 || row >= decks_.size() || decks_.at(row).deck.isEmpty()) { + if (row < 0 || row >= decks.size() || decks.at(row).deck.isEmpty()) { return false; } - DeckPreviewData &data = decks_[row]; + DeckPreviewData &data = decks[row]; data.deck.deckList.setName(newName); if (!DeckLoader::saveToFile(data.deck)) { return false; } - computeDeckMetadata(data); + recomputeDeckMetadata(data); emit dataChanged(index(row), index(row), {VisualDeckStorageRoles::DisplayNameRole}); return true; } bool VisualDeckStorageModel::renameFile(int row, const QString &newBaseName) { - if (row < 0 || row >= decks_.size() || newBaseName.isEmpty()) { + if (row < 0 || row >= decks.size() || newBaseName.isEmpty()) { return false; } - DeckPreviewData &data = decks_[row]; + DeckPreviewData &data = decks[row]; const QFileInfo info(data.filePath); if (newBaseName == info.baseName()) { return false; @@ -386,28 +400,28 @@ bool VisualDeckStorageModel::renameFile(int row, const QString &newBaseName) bool VisualDeckStorageModel::deleteFile(int row) { - if (row < 0 || row >= decks_.size()) { + if (row < 0 || row >= decks.size()) { return false; } - const QString filePath = decks_.at(row).filePath; + const QString filePath = decks.at(row).filePath; if (!QFile::remove(QFileInfo(filePath).filePath())) { return false; } beginRemoveRows(QModelIndex(), row, row); - decks_.removeAt(row); + decks.removeAt(row); endRemoveRows(); return true; } bool VisualDeckStorageModel::setTags(int row, const QStringList &tags) { - if (row < 0 || row >= decks_.size() || decks_.at(row).deck.isEmpty()) { + if (row < 0 || row >= decks.size() || decks.at(row).deck.isEmpty()) { return false; } - DeckPreviewData &data = decks_[row]; + DeckPreviewData &data = decks[row]; data.deck.deckList.setTags(tags); if (!DeckLoader::saveToFile(data.deck)) { return false; @@ -420,18 +434,17 @@ bool VisualDeckStorageModel::setTags(int row, const QStringList &tags) bool VisualDeckStorageModel::setBannerCard(int row, const CardRef &cardRef) { - if (row < 0 || row >= decks_.size() || decks_.at(row).deck.isEmpty()) { + if (row < 0 || row >= decks.size() || decks.at(row).deck.isEmpty()) { return false; } - DeckPreviewData &data = decks_[row]; + DeckPreviewData &data = decks[row]; data.deck.deckList.setBannerCard(cardRef); if (!DeckLoader::saveToFile(data.deck)) { return false; } - data.bannerCardName = cardRef.name; - data.bannerCardProviderId = cardRef.providerId; + data.bannerCard = cardRef; emit dataChanged(index(row), index(row), {VisualDeckStorageRoles::BannerCardNameRole, VisualDeckStorageRoles::BannerCardProviderIdRole}); return true; @@ -439,11 +452,11 @@ bool VisualDeckStorageModel::setBannerCard(int row, const CardRef &cardRef) bool VisualDeckStorageModel::convertToCockatriceFormat(int row) { - if (row < 0 || row >= decks_.size() || decks_.at(row).deck.isEmpty()) { + if (row < 0 || row >= decks.size() || decks.at(row).deck.isEmpty()) { return false; } - DeckPreviewData &data = decks_[row]; + DeckPreviewData &data = decks[row]; const QString oldFilePath = data.filePath; if (!DeckLoader::convertToCockatriceFormat(data.deck)) { return false; @@ -451,7 +464,7 @@ bool VisualDeckStorageModel::convertToCockatriceFormat(int row) setFilePathForRow(row, data.deck.lastLoadInfo.fileName); data.lastModified = QFileInfo(data.filePath).lastModified(); - computeDeckMetadata(data); + recomputeDeckMetadata(data); emit dataChanged(index(row), index(row)); if (oldFilePath != data.filePath) { @@ -462,11 +475,11 @@ bool VisualDeckStorageModel::convertToCockatriceFormat(int row) bool VisualDeckStorageModel::reloadIfModified(int row) { - if (row < 0 || row >= decks_.size()) { + if (row < 0 || row >= decks.size()) { return false; } - DeckPreviewData &data = decks_[row]; + DeckPreviewData &data = decks[row]; QFileInfo fileInfo(data.filePath); const QDateTime newLastModified = fileInfo.lastModified(); if (!newLastModified.isValid() || newLastModified <= data.lastModified) { @@ -482,7 +495,7 @@ bool VisualDeckStorageModel::reloadIfModified(int row) data.deck = *result; data.loadSucceeded = true; data.lastModified = fileInfo.lastModified(); - computeDeckMetadata(data); + recomputeDeckMetadata(data); emit dataChanged(index(row), index(row)); emit deckLoaded(row); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.h index daec92e3d..a44e7412d 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.h @@ -50,20 +50,19 @@ enum */ struct DeckPreviewData { - QString filePath; ///< Absolute file path. - QString relativeFilePath; ///< File path relative to the deck folder. - QString folderPath; ///< Directory relative to the deck folder ("" for the deck folder itself). - QString deckName; ///< The deck name as stored in the file (may be empty). - QString displayName; ///< Deck name, or the file name if the deck has no name. - QStringList tags; ///< The deck's tags. - QString colorIdentity; ///< The deck's color identity in WUBRG order. - QDateTime lastModified; ///< File modification time at last check. - QDateTime lastLoaded; ///< When the deck was last loaded from the file. - QString bannerCardName; ///< Name of the deck's banner card. - QString bannerCardProviderId; ///< Provider id of the deck's banner card. - LoadedDeck deck; ///< The parsed deck; empty until the file has been loaded. - bool loadSucceeded = false; ///< Whether the deck file finished loading successfully. - bool loadInProgress = false; ///< Whether the deck file is currently being loaded. + QString filePath; ///< Absolute file path. + QString relativeFilePath; ///< File path relative to the deck folder. + QString folderPath; ///< Directory relative to the deck folder ("" for the deck folder itself). + QString deckName; ///< The deck name as stored in the file (may be empty). + QString displayName; ///< Deck name, or the file name if the deck has no name. + QStringList tags; ///< The deck's tags. + QString colorIdentity; ///< The deck's color identity in WUBRG order. + QDateTime lastModified; ///< File modification time at last check. + QDateTime lastLoaded; ///< When the deck was last loaded from the file. + CardRef bannerCard; ///< The deck's banner card (name + provider id). + LoadedDeck deck; ///< The parsed deck; empty until the file has been loaded. + bool loadSucceeded = false; ///< Whether the deck file finished loading successfully. + bool loadInProgress = false; ///< Whether the deck file is currently being loaded. }; /** @@ -86,27 +85,28 @@ public: /** * @brief Sets the folder to scan for deck files and starts (re)loading. - * Emits modelReset once the scan finishes and deck loads begin. + * Clears the model immediately (modelReset), then populates it asynchronously + * as the background scan discovers deck files. */ - void setDeckPath(const QString &deckPath); + void setDeckPath(const QString &path); /** * @brief Re-scans the current deck folder, reloading every deck file. */ void refresh(); - [[nodiscard]] QString deckPath() const + [[nodiscard]] QString getDeckPath() const { - return deckPath_; + return deckPath; } /** * @brief The relative paths of all subdirectories of the deck folder, one level at a time. * Used by the view to build the folder tree. Sorted for deterministic order. */ - [[nodiscard]] QStringList folderPaths() const + [[nodiscard]] QStringList getFolderPaths() const { - return folderPaths_; + return folderPaths; } /// @name Data accessors @@ -144,15 +144,13 @@ signals: private: void startScan(); void beginLoad(int row); - void computeDeckMetadata(DeckPreviewData &data) const; + static void recomputeDeckMetadata(DeckPreviewData &data); void setFilePathForRow(int row, const QString &newFilePath); - [[nodiscard]] static QString relativeFilePathFor(const QString &filePath, const QString &deckPath); - [[nodiscard]] static QString folderPathFor(const QString &filePath, const QString &deckPath); - QString deckPath_; - QList decks_; - QStringList folderPaths_; ///< All subdirectories of the deck folder, sorted. - int scanGeneration_ = 0; ///< Bumped on every scan so stale results are ignored. + QString deckPath; + QList decks; + QStringList folderPaths; ///< All subdirectories of the deck folder, sorted. + int scanGeneration = 0; ///< Bumped on every scan so stale results are ignored. }; #endif // VISUAL_DECK_STORAGE_MODEL_H 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 63801fcc1..8968f6cb3 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 @@ -3,6 +3,7 @@ #include "../../filters/deck_filter_string.h" #include +#include VisualDeckStorageSortFilterProxyModel::VisualDeckStorageSortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) @@ -36,36 +37,36 @@ void VisualDeckStorageSortFilterProxyModel::setSourceModel(QAbstractItemModel *m void VisualDeckStorageSortFilterProxyModel::setSearchText(const QString &text) { - if (searchText_ == text) { + if (searchText == text) { return; } - searchText_ = text; + searchText = text; updateSearchMatches(); invalidate(); } -void VisualDeckStorageSortFilterProxyModel::setTagFilter(const QSet &selectedTags, - const QSet &excludedTags) +void VisualDeckStorageSortFilterProxyModel::setTagFilter(const QSet &newSelectedTags, + const QSet &newExcludedTags) { - if (selectedTags_ == selectedTags && excludedTags_ == excludedTags) { + if (selectedTags == newSelectedTags && excludedTags == newExcludedTags) { return; } - selectedTags_ = selectedTags; - excludedTags_ = excludedTags; + selectedTags = newSelectedTags; + excludedTags = newExcludedTags; updateTagMatches(); invalidate(); } -void VisualDeckStorageSortFilterProxyModel::setColorFilter(FilterMode mode, const QSet &activeColors) +void VisualDeckStorageSortFilterProxyModel::setColorFilter(FilterMode mode, const QSet &colors) { - if (colorFilterMode_ == mode && activeColors_ == activeColors) { + if (colorFilterMode == mode && activeColors == colors) { return; } - colorFilterMode_ = mode; - activeColors_ = activeColors; + colorFilterMode = mode; + activeColors = colors; updateColorMatches(); invalidate(); } @@ -74,25 +75,25 @@ void VisualDeckStorageSortFilterProxyModel::setSortOrder(SortOrder order) { // No equality guard: the initial reapply (with the default order) must still // trigger sort(0), since without a sort the proxy would show scan order. - sortOrder_ = order; + sortOrder = order; sort(0); } void VisualDeckStorageSortFilterProxyModel::reapplyFilters() { - const QList oldSearchMatches = searchMatches_; - const QList oldTagMatches = tagMatches_; - const QList oldColorMatches = colorMatches_; + const QList oldSearchMatches = searchMatches; + const QList oldTagMatches = tagMatches; + const QList oldColorMatches = colorMatches; updateSearchMatches(); updateTagMatches(); updateColorMatches(); - if (searchMatches_ != oldSearchMatches || tagMatches_ != oldTagMatches || colorMatches_ != oldColorMatches) { + if (searchMatches != oldSearchMatches || tagMatches != oldTagMatches || colorMatches != oldColorMatches) { invalidate(); } - if (sortOrder_ == ByName || sortOrder_ == ByLastLoaded) { + if (sortOrder == ByName || sortOrder == ByLastLoaded) { // These orders depend on data that only becomes available when a deck finishes loading. sort(0); } @@ -110,12 +111,12 @@ bool VisualDeckStorageSortFilterProxyModel::filterAcceptsRow(int sourceRow, cons } // If the match lists aren't sized to the current model yet, don't hide anything. - if (sourceRow < 0 || sourceRow >= searchMatches_.size() || sourceRow >= tagMatches_.size() || - sourceRow >= colorMatches_.size()) { + if (sourceRow < 0 || sourceRow >= searchMatches.size() || sourceRow >= tagMatches.size() || + sourceRow >= colorMatches.size()) { return true; } - return searchMatches_.at(sourceRow) && tagMatches_.at(sourceRow) && colorMatches_.at(sourceRow); + return searchMatches.at(sourceRow) && tagMatches.at(sourceRow) && colorMatches.at(sourceRow); } bool VisualDeckStorageSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const @@ -128,7 +129,7 @@ bool VisualDeckStorageSortFilterProxyModel::lessThan(const QModelIndex &left, co const DeckPreviewData &leftData = source->dataForRow(left.row()); const DeckPreviewData &rightData = source->dataForRow(right.row()); - switch (sortOrder_) { + switch (sortOrder) { case ByName: return leftData.deckName < rightData.deckName; case Alphabetical: @@ -146,43 +147,48 @@ bool VisualDeckStorageSortFilterProxyModel::lessThan(const QModelIndex &left, co void VisualDeckStorageSortFilterProxyModel::resizeMatchLists() { const int count = sourceModel() ? sourceModel()->rowCount() : 0; - searchMatches_ = QList(count, true); - tagMatches_ = QList(count, true); - colorMatches_ = QList(count, true); + searchMatches.resize(count); + searchMatches.fill(true); + tagMatches.resize(count); + tagMatches.fill(true); + colorMatches.resize(count); + colorMatches.fill(true); } void VisualDeckStorageSortFilterProxyModel::updateSearchMatches() { const auto *source = deckSourceModel(); if (!source) { - searchMatches_.clear(); + searchMatches.clear(); return; } const int count = source->rowCount(); - if (searchText_.isEmpty()) { - searchMatches_ = QList(count, true); + searchMatches.resize(count); + if (searchText.isEmpty()) { + searchMatches.fill(true); return; } - searchMatches_.resize(count); - DeckFilterString filterString(searchText_); + DeckFilterString filterString(searchText); for (int row = 0; row < count; ++row) { const DeckPreviewData &data = source->dataForRow(row); - // Decks that haven't finished loading yet can't be evaluated; show them and - // re-evaluate once the load finishes (reapplyFilters is triggered by deckLoaded). + // isEmpty() is intentional: if a deck fails to load, loadInProgress becomes false + // but the deck remains empty. Using loadInProgress alone would pass failed decks + // to DeckFilterString::check, which requires a non-empty deck. if (data.deck.isEmpty()) { - searchMatches_[row] = true; + searchMatches[row] = true; continue; } - DeckSearchData searchData; - searchData.deck = &data.deck; - searchData.filePath = data.filePath; - searchData.displayName = data.displayName; - searchData.relativeFilePath = data.relativeFilePath; - searchMatches_[row] = filterString.check(searchData); + DeckSearchData searchData{ + .deck = &data.deck, + .filePath = data.filePath, + .displayName = data.displayName, + .relativeFilePath = data.relativeFilePath, + }; + searchMatches[row] = filterString.check(searchData); } } @@ -190,38 +196,27 @@ void VisualDeckStorageSortFilterProxyModel::updateTagMatches() { const auto *source = deckSourceModel(); if (!source) { - tagMatches_.clear(); + tagMatches.clear(); return; } const int count = source->rowCount(); - tagMatches_.resize(count); + tagMatches.resize(count); - if (selectedTags_.isEmpty() && excludedTags_.isEmpty()) { - tagMatches_.fill(true); + if (selectedTags.isEmpty() && excludedTags.isEmpty()) { + tagMatches.fill(true); return; } for (int row = 0; row < count; ++row) { const QStringList deckTags = source->dataForRow(row).tags; - bool hasAllSelected = true; - for (const QString &tag : selectedTags_) { - if (!deckTags.contains(tag)) { - hasAllSelected = false; - break; - } - } + const bool hasAllSelected = std::all_of(selectedTags.begin(), selectedTags.end(), + [&deckTags](const QString &tag) { return deckTags.contains(tag); }); + const bool hasAnyExcluded = std::any_of(excludedTags.begin(), excludedTags.end(), + [&deckTags](const QString &tag) { return deckTags.contains(tag); }); - bool hasAnyExcluded = false; - for (const QString &tag : excludedTags_) { - if (deckTags.contains(tag)) { - hasAnyExcluded = true; - break; - } - } - - tagMatches_[row] = hasAllSelected && !hasAnyExcluded; + tagMatches[row] = hasAllSelected && !hasAnyExcluded; } } @@ -229,15 +224,15 @@ void VisualDeckStorageSortFilterProxyModel::updateColorMatches() { const auto *source = deckSourceModel(); if (!source) { - colorMatches_.clear(); + colorMatches.clear(); return; } const int count = source->rowCount(); - colorMatches_.resize(count); + colorMatches.resize(count); - if (activeColors_.isEmpty()) { - colorMatches_.fill(true); + if (activeColors.isEmpty()) { + colorMatches.fill(true); return; } @@ -245,10 +240,10 @@ void VisualDeckStorageSortFilterProxyModel::updateColorMatches() const QString colorIdentity = source->dataForRow(row).colorIdentity; bool matches = true; - switch (colorFilterMode_) { + switch (colorFilterMode) { case ExactMatch: { QSet activeColorSet; - for (const QChar &color : activeColors_) { + for (const QChar &color : activeColors) { activeColorSet.insert(color.toUpper()); } @@ -261,24 +256,16 @@ void VisualDeckStorageSortFilterProxyModel::updateColorMatches() break; } case Includes: - for (const QChar &color : activeColors_) { - if (!colorIdentity.contains(color)) { - matches = false; - break; - } - } + matches = std::all_of(activeColors.begin(), activeColors.end(), + [&colorIdentity](const QChar &color) { return colorIdentity.contains(color); }); break; case Excludes: - for (const QChar &color : activeColors_) { - if (colorIdentity.contains(color)) { - matches = false; - break; - } - } + matches = std::none_of(activeColors.begin(), activeColors.end(), + [&colorIdentity](const QChar &color) { return colorIdentity.contains(color); }); break; } - colorMatches_[row] = matches; + colorMatches[row] = matches; } } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.h index c53a04da6..d2842a02f 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.h @@ -52,8 +52,8 @@ public: /// @name Filter input setters (each re-evaluates the affected matches) ///@{ void setSearchText(const QString &text); - void setTagFilter(const QSet &selectedTags, const QSet &excludedTags); - void setColorFilter(FilterMode mode, const QSet &activeColors); + void setTagFilter(const QSet &newSelectedTags, const QSet &newExcludedTags); + void setColorFilter(FilterMode mode, const QSet &colors); ///@} /** @@ -83,16 +83,16 @@ private: void updateColorMatches(); [[nodiscard]] const VisualDeckStorageModel *deckSourceModel() const; - QString searchText_; - QSet selectedTags_; - QSet excludedTags_; - FilterMode colorFilterMode_ = ExactMatch; - QSet activeColors_; - SortOrder sortOrder_ = Alphabetical; + QString searchText; + QSet selectedTags; + QSet excludedTags; + FilterMode colorFilterMode = ExactMatch; + QSet activeColors; + SortOrder sortOrder = Alphabetical; - QList searchMatches_; ///< Per-row search match, sized like the source model. - QList tagMatches_; ///< Per-row tag match. - QList colorMatches_; ///< Per-row color identity match. + QList searchMatches; ///< Per-row search match, sized like the source model. + QList tagMatches; ///< Per-row tag match. + QList colorMatches; ///< Per-row color identity match. }; #endif // VISUAL_DECK_STORAGE_SORT_FILTER_PROXY_MODEL_H