Skip to content

Commit

Permalink
QSortFilterProxyModel: add a protected beginFilterChange
Browse files Browse the repository at this point in the history
If the filter gets changed and invalidated while there is no mapping
(perhaps because the model had been invalidated first), then we fail
to notice the change and don't emit rowsInserted/Removed. And as the
new filter is already in place by the time invalidateFilter gets
called, we cannot know what the size of the model was before the
change.

The only way to fix that is to introduce a beginFilterChange protected
function that makes sure that we have a mapping from the source model
with the old filter. That is a no-op if a mapping is already in place,
costing only the lookup in a hash table.

By calling this function, custom models with their own filtering logic
can make sure that their model emits the changed-signals as expected.

Add test coverage and documentation and fix the relevant examples
snippet to use that new protected function as recommended, and to
invalidate only the rows filter.

[ChangeLog][Core][QSortFilterProxyModel] Added a new protected
function beginFilterChange() that subclasses overriding
filterAcceptsRow or filterAcceptsColumn should call before the filter
parameter is changed. This makes sure that the signals informing
about rows or columns changing get correctly emitted.

Fixes: QTBUG-115717
Change-Id: Ib73a7119ac9dd9c4bcf220f1274d6b4ed093e7ff
Reviewed-by: David Faure <[email protected]>
  • Loading branch information
vohi committed Aug 28, 2024
1 parent 03c547f commit 00ce45e
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ MySortFilterProxyModel::MySortFilterProxyModel(QObject *parent)
//! [1]
void MySortFilterProxyModel::setFilterMinimumDate(QDate date)
{
beginFilterChange();
minDate = date;
invalidateFilter();
invalidateRowsFilter();
}
//! [1]

//! [2]
void MySortFilterProxyModel::setFilterMaximumDate(QDate date)
{
beginFilterChange();
maxDate = date;
invalidateFilter();
invalidateRowsFilter();
}
//! [2]

Expand Down
40 changes: 31 additions & 9 deletions src/corelib/itemmodels/qsortfilterproxymodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3079,6 +3079,25 @@ void QSortFilterProxyModel::invalidate()
emit layoutChanged();
}

/*!
\since 6.9
Prepares a change of the filter.
This function should be called if you are implementing custom filtering
(e.g. filterAcceptsRow()), and your filter parameter is about to be changed.
\snippet ../widgets/itemviews/customsortfiltermodel/mysortfilterproxymodel.cpp 2
\sa invalidateFilter(), invalidateColumnsFilter(), invalidateRowsFilter()
*/

void QSortFilterProxyModel::beginFilterChange()
{
Q_D(QSortFilterProxyModel);
d->create_mapping({});
}

/*!
\since 4.3
Expand All @@ -3087,9 +3106,12 @@ void QSortFilterProxyModel::invalidate()
This function should be called if you are implementing custom filtering
(e.g. filterAcceptsRow()), and your filter parameters have changed.
\sa invalidate()
\sa invalidateColumnsFilter()
\sa invalidateRowsFilter()
Before your filter parameters change, call beginFilterChange().
\snippet ../widgets/itemviews/customsortfiltermodel/mysortfilterproxymodel.cpp 2
\sa invalidate(), invalidateColumnsFilter(), invalidateRowsFilter(),
beginFilterChange()
*/
void QSortFilterProxyModel::invalidateFilter()
{
Expand All @@ -3109,9 +3131,9 @@ void QSortFilterProxyModel::invalidateFilter()
instead of invalidateFilter() if you want to hide or show a column where
the rows don't change.
\sa invalidate()
\sa invalidateFilter()
\sa invalidateRowsFilter()
Before your filter parameters change, call beginFilterChange().
\sa invalidate(), invalidateRowsFilter(), beginFilterChange()
*/
void QSortFilterProxyModel::invalidateColumnsFilter()
{
Expand All @@ -3131,9 +3153,9 @@ void QSortFilterProxyModel::invalidateColumnsFilter()
instead of invalidateFilter() if you want to hide or show a row where
the columns don't change.
\sa invalidate()
\sa invalidateFilter()
\sa invalidateColumnsFilter()
Before your filter parameters change, call beginFilterChange().
\sa invalidate(), invalidateFilter(), invalidateColumnsFilter()
*/
void QSortFilterProxyModel::invalidateRowsFilter()
{
Expand Down
1 change: 1 addition & 0 deletions src/corelib/itemmodels/qsortfilterproxymodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public Q_SLOTS:
virtual bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const;
virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;

void beginFilterChange();
void invalidateFilter();
void invalidateRowsFilter();
void invalidateColumnsFilter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5497,5 +5497,64 @@ void tst_QSortFilterProxyModel::createPersistentOnLayoutAboutToBeChanged() // QT
QCOMPARE(layoutChangedSpy.size(), 1);
}

void tst_QSortFilterProxyModel::filterChangeEmitsModelChangedSignals()
{
QStringListModel model({"1", "2", "3", "4", "5"});

class FilterModel : public QSortFilterProxyModel
{
QString m_matchString;
public:
void setFilter(const QString &s)
{
if (m_matchString == s)
return;

beginFilterChange();
m_matchString = s;
invalidateFilter();
}

bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
{
const auto index = sourceModel()->index(sourceRow, 0, sourceParent);
if (!index.isValid())
return false;

return index.data().value<QString>() == m_matchString;
}
};

FilterModel filterModel;

// Reject all source data at the start
filterModel.setFilter("X");
// Trigger an evaluation
filterModel.sort(0, Qt::AscendingOrder);
filterModel.setSourceModel(&model);
QCOMPARE(filterModel.rowCount(), 0);
filterModel.invalidate();

QSignalSpy rowsInsertedSpy(&filterModel, &QSortFilterProxyModel::rowsInserted);
QSignalSpy rowsRemovedSpy(&filterModel, &QSortFilterProxyModel::rowsRemoved);

filterModel.setFilter("3");
QCOMPARE(filterModel.rowCount(), 1);
QCOMPARE(rowsInsertedSpy.count(), 1);
rowsInsertedSpy.clear();

filterModel.setFilter("2");
QCOMPARE(filterModel.rowCount(), 1);
QCOMPARE(rowsInsertedSpy.count(), 1);
QCOMPARE(rowsRemovedSpy.count(), 1);
rowsInsertedSpy.clear();
rowsRemovedSpy.clear();

filterModel.setFilter("X");
QCOMPARE(filterModel.rowCount(), 0);
QCOMPARE(rowsInsertedSpy.count(), 0);
QCOMPARE(rowsRemovedSpy.count(), 1);
}

QTEST_MAIN(tst_QSortFilterProxyModel)
#include "tst_qsortfilterproxymodel.moc"
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ private slots:
void filterCaseSensitivityBinding();
void filterRegularExpressionBinding();

void filterChangeEmitsModelChangedSignals();

protected:
void buildHierarchy(const QStringList &data, QAbstractItemModel *model);
void checkHierarchy(const QStringList &data, const QAbstractItemModel *model);
Expand Down

0 comments on commit 00ce45e

Please sign in to comment.