mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-27 16:32:20 -07:00
[VDS] Expose filter matches as a proxy role instead of dropping rows
The folder display scanned source-model rows and probed acceptance with mapFromSource(...).isValid(), reaching into both models for one answer. The proxy now keeps every row and exposes each row's search/tag/color filter result through FilterMatchRole. The folder display and the tag filter read everything off proxy indexes, and hidden previews keep their sorted position in the flow layout instead of being appended at the end. Took 11 minutes
This commit is contained in:
parent
0709cd04c0
commit
717a2deff8
7 changed files with 68 additions and 45 deletions
|
|
@ -105,29 +105,29 @@ void VisualDeckStorageFolderDisplayWidget::continueDeckPass()
|
|||
passTimer.start();
|
||||
|
||||
auto *proxy = visualDeckStorageWidget->proxyModel();
|
||||
auto *model = visualDeckStorageWidget->model();
|
||||
const int sourceRowCount = model->rowCount();
|
||||
const int proxyRowCount = proxy->rowCount();
|
||||
|
||||
// Scan rows of this folder, creating missing previews, until the time budget for this
|
||||
// event loop turn runs out. The rest continues on the next turn.
|
||||
while (deckPassRow < sourceRowCount) {
|
||||
while (deckPassRow < proxyRowCount) {
|
||||
const int row = deckPassRow++;
|
||||
const DeckPreviewData &data = model->dataForRow(row);
|
||||
if (showFolders && data.folderPath != folderPath) {
|
||||
const QModelIndex index = proxy->index(row, 0);
|
||||
if (showFolders && index.data(VisualDeckStorageRoles::FolderPathRole).toString() != folderPath) {
|
||||
continue;
|
||||
}
|
||||
deckPassPresentPaths.insert(data.filePath);
|
||||
const QString filePath = index.data(VisualDeckStorageRoles::FilePathRole).toString();
|
||||
deckPassPresentPaths.insert(filePath);
|
||||
|
||||
DeckPreviewWidget *deckPreviewWidget = deckWidgets.value(data.filePath, nullptr);
|
||||
DeckPreviewWidget *deckPreviewWidget = deckWidgets.value(filePath, nullptr);
|
||||
if (!deckPreviewWidget) {
|
||||
deckPreviewWidget = createDeckPreviewWidget(data.filePath);
|
||||
deckPreviewWidget = createDeckPreviewWidget(filePath);
|
||||
}
|
||||
|
||||
const bool accepted = proxy->mapFromSource(model->index(row, 0)).isValid();
|
||||
if (accepted == deckPreviewWidget->isHidden()) {
|
||||
deckPreviewWidget->setVisible(accepted);
|
||||
const bool matches = index.data(VisualDeckStorageRoles::FilterMatchRole).toBool();
|
||||
if (matches == deckPreviewWidget->isHidden()) {
|
||||
deckPreviewWidget->setVisible(matches);
|
||||
}
|
||||
if (accepted) {
|
||||
if (matches) {
|
||||
++visibleDeckCount;
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ void VisualDeckStorageFolderDisplayWidget::continueDeckPass()
|
|||
return;
|
||||
}
|
||||
|
||||
if (deckPassRow < sourceRowCount) {
|
||||
if (deckPassRow < proxyRowCount) {
|
||||
QMetaObject::invokeMethod(this, &VisualDeckStorageFolderDisplayWidget::continueDeckPass, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
|
@ -152,9 +152,8 @@ void VisualDeckStorageFolderDisplayWidget::continueDeckPass()
|
|||
void VisualDeckStorageFolderDisplayWidget::finishDeckPass()
|
||||
{
|
||||
auto *proxy = visualDeckStorageWidget->proxyModel();
|
||||
auto *model = visualDeckStorageWidget->model();
|
||||
|
||||
// Drop previews of decks that no longer exist in the source model.
|
||||
// Drop previews of decks that no longer exist in the model.
|
||||
for (auto it = deckWidgets.begin(); it != deckWidgets.end();) {
|
||||
if (!deckPassPresentPaths.contains(it.key())) {
|
||||
flowWidget->removeWidget(it.value());
|
||||
|
|
@ -165,28 +164,16 @@ void VisualDeckStorageFolderDisplayWidget::finishDeckPass()
|
|||
}
|
||||
}
|
||||
|
||||
// Order the flow layout like the proxy sorts its rows.
|
||||
// Order the flow layout like the proxy sorts its rows. Filtered-out decks stay part of
|
||||
// the layout, hidden in their sorted place until a filter lets them through again.
|
||||
QStringList orderedFilePaths;
|
||||
orderedFilePaths.reserve(proxy->rowCount());
|
||||
for (int proxyRow = 0; proxyRow < proxy->rowCount(); ++proxyRow) {
|
||||
const QModelIndex sourceIndex = proxy->mapToSource(proxy->index(proxyRow, 0));
|
||||
if (!sourceIndex.isValid()) {
|
||||
continue;
|
||||
}
|
||||
const QString &filePath = model->dataForRow(sourceIndex.row()).filePath;
|
||||
const QString filePath = proxy->index(proxyRow, 0).data(VisualDeckStorageRoles::FilePathRole).toString();
|
||||
if (deckWidgets.contains(filePath)) {
|
||||
orderedFilePaths.append(filePath);
|
||||
}
|
||||
}
|
||||
// Hidden previews are appended so they remain part of the layout and take their
|
||||
// sorted place once a filter lets them through again.
|
||||
QSet<QString> orderedSet(orderedFilePaths.cbegin(), orderedFilePaths.cend());
|
||||
for (auto it = deckWidgets.constBegin(); it != deckWidgets.constEnd(); ++it) {
|
||||
if (!orderedSet.contains(it.key())) {
|
||||
orderedSet.insert(it.key());
|
||||
orderedFilePaths.append(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
// Re-add all widgets so the flow layout order matches the proxy order. Skipped when the
|
||||
// order is unchanged so that data-only updates don't invalidate the flow layout.
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
* @brief Renders the decks of one folder of the Visual Deck Storage.
|
||||
*
|
||||
* This is a pure view: it keeps one persistent DeckPreviewWidget alive per deck
|
||||
* in its folder, and shows or hides those widgets according to the accepted rows
|
||||
* of the VisualDeckStorageSortFilterProxyModel. Subfolders are shown as nested
|
||||
* VisualDeckStorageFolderDisplayWidgets when the "show folders" setting is enabled.
|
||||
* in its folder, and shows or hides those widgets according to each row's
|
||||
* FilterMatchRole in the VisualDeckStorageSortFilterProxyModel. Subfolders are
|
||||
* shown as nested VisualDeckStorageFolderDisplayWidgets when the "show folders"
|
||||
* setting is enabled.
|
||||
*
|
||||
* Reconciling runs as a time-budgeted chunked pass that yields to the event loop
|
||||
* between chunks, so scanning a large collection never stalls the ui thread.
|
||||
|
|
@ -97,7 +98,7 @@ private:
|
|||
bool deckPassRestartRequested = false;
|
||||
/// Whether the first reconcile pass has run to completion at least once.
|
||||
bool initialPassCompleted = false;
|
||||
int deckPassRow = 0; ///< Next source row to scan in the active pass.
|
||||
int deckPassRow = 0; ///< Next proxy row to scan in the active pass.
|
||||
QSet<QString> deckPassPresentPaths; ///< File paths seen so far in the active pass.
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,14 @@ enum
|
|||
LastModifiedRole, /**< QDateTime of the deck file's last modification. */
|
||||
LastLoadedRole, /**< QDateTime when the deck was last loaded from the file. */
|
||||
BannerCardNameRole, /**< Name of the deck's banner card. */
|
||||
BannerCardProviderIdRole /**< Provider id of the deck's banner card. */
|
||||
BannerCardProviderIdRole, /**< Provider id of the deck's banner card. */
|
||||
/**
|
||||
* @brief Whether the row passes the proxy's current search / tag / color filters.
|
||||
*
|
||||
* Not served by this model, but by VisualDeckStorageSortFilterProxyModel on top
|
||||
* of it. Declared here so every role read through a proxy index stays unique.
|
||||
*/
|
||||
FilterMatchRole
|
||||
};
|
||||
} // namespace VisualDeckStorageRoles
|
||||
|
||||
|
|
@ -82,7 +89,8 @@ struct PendingDeckLoad
|
|||
* @brief The list model backing the Visual Deck Storage widget tree.
|
||||
*
|
||||
* Rows are in filesystem scan order; ordering and filtering are handled by
|
||||
* VisualDeckStorageSortFilterProxyModel on top of this model.
|
||||
* VisualDeckStorageSortFilterProxyModel on top of this model. The proxy keeps
|
||||
* every row and exposes each row's filter result through its FilterMatchRole.
|
||||
*/
|
||||
class VisualDeckStorageModel : public QAbstractListModel
|
||||
{
|
||||
|
|
|
|||
|
|
@ -104,12 +104,21 @@ void VisualDeckStorageSortFilterProxyModel::resort()
|
|||
sort(0);
|
||||
}
|
||||
|
||||
bool VisualDeckStorageSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
QVariant VisualDeckStorageSortFilterProxyModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (sourceParent.isValid()) {
|
||||
return true;
|
||||
if (role == VisualDeckStorageRoles::FilterMatchRole) {
|
||||
if (!index.isValid()) {
|
||||
return true;
|
||||
}
|
||||
const QModelIndex sourceIndex = mapToSource(index);
|
||||
return rowMatches(sourceIndex.row());
|
||||
}
|
||||
|
||||
return QSortFilterProxyModel::data(index, role);
|
||||
}
|
||||
|
||||
bool VisualDeckStorageSortFilterProxyModel::rowMatches(int sourceRow) const
|
||||
{
|
||||
// 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()) {
|
||||
|
|
@ -119,6 +128,14 @@ bool VisualDeckStorageSortFilterProxyModel::filterAcceptsRow(int sourceRow, cons
|
|||
return searchMatches.at(sourceRow) && tagMatches.at(sourceRow) && colorMatches.at(sourceRow);
|
||||
}
|
||||
|
||||
bool VisualDeckStorageSortFilterProxyModel::filterAcceptsRow(int /*sourceRow*/,
|
||||
const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
// Rows are never dropped: the filter result is exposed per row through
|
||||
// FilterMatchRole, so views can keep their widgets alive and just hide them.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisualDeckStorageSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
const auto *source = deckSourceModel();
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@
|
|||
* Owns all search / tag / color filter state and the sort order. Filtering is
|
||||
* evaluated against the model's data (never against widgets), so it can run
|
||||
* before any view exists and re-evaluate whenever deck data finishes loading.
|
||||
*
|
||||
* Rows are never removed by filtering. Instead, every row carries the
|
||||
* FilterMatchRole, which views read to show or hide their widgets while keeping
|
||||
* them alive; all rows stay in the proxy so they keep their sorted position.
|
||||
*/
|
||||
|
||||
#ifndef VISUAL_DECK_STORAGE_SORT_FILTER_PROXY_MODEL_H
|
||||
|
|
@ -47,6 +51,8 @@ public:
|
|||
|
||||
explicit VisualDeckStorageSortFilterProxyModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
void setSourceModel(QAbstractItemModel *model) override;
|
||||
|
||||
/// @name Filter input setters (each re-evaluates the affected matches)
|
||||
|
|
@ -77,6 +83,7 @@ protected:
|
|||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool rowMatches(int sourceRow) const;
|
||||
void resizeMatchLists();
|
||||
void updateSearchMatches();
|
||||
void updateTagMatches();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "deck_preview/deck_preview_tag_display_widget.h"
|
||||
#include "visual_deck_storage_model.h"
|
||||
#include "visual_deck_storage_sort_filter_proxy_model.h"
|
||||
#include "visual_deck_storage_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
|
@ -36,14 +38,14 @@ QSet<QString> VisualDeckStorageTagFilterWidget::gatherAllTags() const
|
|||
{
|
||||
QSet<QString> allTags;
|
||||
auto *proxy = parent->proxyModel();
|
||||
auto *model = parent->model();
|
||||
|
||||
for (int proxyRow = 0; proxyRow < proxy->rowCount(); ++proxyRow) {
|
||||
const QModelIndex sourceIndex = proxy->mapToSource(proxy->index(proxyRow, 0));
|
||||
if (!sourceIndex.isValid()) {
|
||||
const QModelIndex index = proxy->index(proxyRow, 0);
|
||||
if (!index.data(VisualDeckStorageRoles::FilterMatchRole).toBool()) {
|
||||
continue;
|
||||
}
|
||||
for (const QString &tag : model->dataForRow(sourceIndex.row()).tags) {
|
||||
const QStringList deckTags = index.data(VisualDeckStorageRoles::TagsRole).toStringList();
|
||||
for (const QString &tag : deckTags) {
|
||||
allTags.insert(tag);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@
|
|||
|
||||
VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
// The model and proxy own all deck data, sorting and filtering. The view widgets below only
|
||||
// display the proxy's accepted rows, so nothing touches the filesystem outside the model.
|
||||
// The model and proxy own all deck data, sorting and filtering. The view widgets below
|
||||
// only display the proxy's rows and their FilterMatchRole, so nothing touches the
|
||||
// filesystem outside the model.
|
||||
storageModel = new VisualDeckStorageModel(this);
|
||||
storageProxyModel = new VisualDeckStorageSortFilterProxyModel(this);
|
||||
storageProxyModel->setSourceModel(storageModel);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue