Add a way to filter only rows or columns in QSortFilterProxyModel

If we want to filter away a column without changing the filtering
for the rows calling invalidateFilter() is wasteful because it will
call filterAcceptsRow() for all rows even though that is not needed.
This commit add two functions, invalidateRowsFilter() and
invalidateColumnsFilter() that work the same way as invalidateFilter()
except that they will invoke respectively only filterAcceptsRow() and
filterAcceptsColumn().

Change-Id: Ib4351cf08c229bd97bbbfee6da92397dca579a84
Reviewed-by: David Faure <david.faure@kdab.com>
bb10
Giulio Camuffo 2020-03-05 10:08:40 +01:00
parent 963c47aece
commit b5f6a85d27
4 changed files with 157 additions and 16 deletions

View File

@ -274,6 +274,12 @@ class QSortFilterProxyModelPrivate : public QAbstractProxyModelPrivate
Q_DECLARE_PUBLIC(QSortFilterProxyModel)
public:
enum class Direction {
Rows = 1,
Columns = 2,
All = Rows | Columns
};
struct Mapping {
QVector<int> source_rows;
QVector<int> source_columns;
@ -413,7 +419,7 @@ public:
void update_persistent_indexes(const QModelIndexPairList &source_indexes);
void filter_about_to_be_changed(const QModelIndex &source_parent = QModelIndex());
void filter_changed(const QModelIndex &source_parent = QModelIndex());
void filter_changed(Direction dir, const QModelIndex &source_parent = QModelIndex());
QSet<int> handle_filter_changed(
QVector<int> &source_to_proxy, QVector<int> &proxy_to_source,
const QModelIndex &source_parent, Qt::Orientation orient);
@ -431,6 +437,11 @@ public:
typedef QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping *> IndexMap;
static bool operator&(QSortFilterProxyModelPrivate::Direction a, QSortFilterProxyModelPrivate::Direction b)
{
return int(a) & int(b);
}
void QSortFilterProxyModelPrivate::_q_sourceModelDestroyed()
{
QAbstractProxyModelPrivate::_q_sourceModelDestroyed();
@ -1269,14 +1280,14 @@ void QSortFilterProxyModelPrivate::filter_about_to_be_changed(const QModelIndex
Updates the proxy model (adds/removes rows) based on the
new filter.
*/
void QSortFilterProxyModelPrivate::filter_changed(const QModelIndex &source_parent)
void QSortFilterProxyModelPrivate::filter_changed(Direction dir, const QModelIndex &source_parent)
{
IndexMap::const_iterator it = source_index_mapping.constFind(source_parent);
if (it == source_index_mapping.constEnd())
return;
Mapping *m = it.value();
QSet<int> rows_removed = handle_filter_changed(m->proxy_rows, m->source_rows, source_parent, Qt::Vertical);
QSet<int> columns_removed = handle_filter_changed(m->proxy_columns, m->source_columns, source_parent, Qt::Horizontal);
const QSet<int> rows_removed = (dir & Direction::Rows) ? handle_filter_changed(m->proxy_rows, m->source_rows, source_parent, Qt::Vertical) : QSet<int>();
const QSet<int> columns_removed = (dir & Direction::Columns) ? handle_filter_changed(m->proxy_columns, m->source_columns, source_parent, Qt::Horizontal) : QSet<int>();
// We need to iterate over a copy of m->mapped_children because otherwise it may be changed by other code, invalidating
// the iterator it2.
@ -1290,7 +1301,7 @@ void QSortFilterProxyModelPrivate::filter_changed(const QModelIndex &source_pare
indexesToRemove.push_back(i);
remove_from_mapping(source_child_index);
} else {
filter_changed(source_child_index);
filter_changed(dir, source_child_index);
}
}
QVector<int>::const_iterator removeIt = indexesToRemove.constEnd();
@ -2605,7 +2616,7 @@ void QSortFilterProxyModel::setFilterRegExp(const QRegExp &regExp)
Q_D(QSortFilterProxyModel);
d->filter_about_to_be_changed();
d->filter_data.setRegExp(regExp);
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
}
#if QT_CONFIG(regularexpression)
@ -2634,7 +2645,7 @@ void QSortFilterProxyModel::setFilterRegularExpression(const QRegularExpression
Q_D(QSortFilterProxyModel);
d->filter_about_to_be_changed();
d->filter_data.setRegularExpression(regularExpression);
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
}
#endif
@ -2657,7 +2668,7 @@ void QSortFilterProxyModel::setFilterKeyColumn(int column)
Q_D(QSortFilterProxyModel);
d->filter_about_to_be_changed();
d->filter_column = column;
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
}
/*!
@ -2683,7 +2694,7 @@ void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs)
return;
d->filter_about_to_be_changed();
d->filter_data.setCaseSensitivity(cs);
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
emit filterCaseSensitivityChanged(cs);
}
@ -2754,7 +2765,7 @@ void QSortFilterProxyModel::setFilterRegExp(const QString &pattern)
QRegExp rx(pattern);
rx.setCaseSensitivity(d->filter_data.caseSensitivity());
d->filter_data.setRegExp(rx);
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
}
#if QT_CONFIG(regularexpression)
@ -2775,7 +2786,7 @@ void QSortFilterProxyModel::setFilterRegularExpression(const QString &pattern)
d->filter_about_to_be_changed();
QRegularExpression rx(pattern);
d->filter_data.setRegularExpression(rx);
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
}
#endif
@ -2791,7 +2802,7 @@ void QSortFilterProxyModel::setFilterWildcard(const QString &pattern)
d->filter_about_to_be_changed();
QRegExp rx(pattern, d->filter_data.caseSensitivity(), QRegExp::Wildcard);
d->filter_data.setRegExp(rx);
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
}
/*!
@ -2806,7 +2817,7 @@ void QSortFilterProxyModel::setFilterFixedString(const QString &pattern)
d->filter_about_to_be_changed();
QRegExp rx(pattern, d->filter_data.caseSensitivity(), QRegExp::FixedString);
d->filter_data.setRegExp(rx);
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
}
/*!
@ -2886,7 +2897,7 @@ void QSortFilterProxyModel::setFilterRole(int role)
return;
d->filter_about_to_be_changed();
d->filter_role = role;
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
emit filterRoleChanged(role);
}
@ -2913,7 +2924,7 @@ void QSortFilterProxyModel::setRecursiveFilteringEnabled(bool recursive)
return;
d->filter_about_to_be_changed();
d->filter_recursive = recursive;
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
emit recursiveFilteringEnabledChanged(recursive);
}
@ -2964,11 +2975,57 @@ void QSortFilterProxyModel::filterChanged()
(e.g. filterAcceptsRow()), and your filter parameters have changed.
\sa invalidate()
\sa invalidateColumnsFilter()
\sa invalidateRowsFilter()
*/
void QSortFilterProxyModel::invalidateFilter()
{
Q_D(QSortFilterProxyModel);
d->filter_changed();
d->filter_changed(QSortFilterProxyModelPrivate::Direction::All);
}
/*!
\since 6.0
Invalidates the current filtering for the columns.
This function should be called if you are implementing custom filtering
(by filterAcceptsColumn()), and your filter parameters have changed.
This differs from invalidateFilter() in that it will not invoke
filterAcceptsRow(), but only filterAcceptsColumn(). You can use this
instead of invalidateFilter() if you want to hide or show a column where
the rows don't change.
\sa invalidate()
\sa invalidateFilter()
\sa invalidateRowsFilter()
*/
void QSortFilterProxyModel::invalidateColumnsFilter()
{
Q_D(QSortFilterProxyModel);
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Columns);
}
/*!
\since 6.0
Invalidates the current filtering for the rows.
This function should be called if you are implementing custom filtering
(by filterAcceptsRow()), and your filter parameters have changed.
This differs from invalidateFilter() in that it will not invoke
filterAcceptsColumn(), but only filterAcceptsRow(). You can use this
instead of invalidateFilter() if you want to hide or show a row where
the columns don't change.
\sa invalidate()
\sa invalidateFilter()
\sa invalidateColumnsFilter()
*/
void QSortFilterProxyModel::invalidateRowsFilter()
{
Q_D(QSortFilterProxyModel);
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
}
/*!

View File

@ -143,6 +143,8 @@ protected:
QT_DEPRECATED_X("Use QSortFilterProxyModel::invalidateFilter") void filterChanged();
#endif
void invalidateFilter();
void invalidateRowsFilter();
void invalidateColumnsFilter();
public:
using QObject::parent;

View File

@ -4940,4 +4940,84 @@ void tst_QSortFilterProxyModel::filterAndInsertRow()
}
}
void tst_QSortFilterProxyModel::invalidateColumnsOrRowsFilter()
{
class FilterProxy : public QSortFilterProxyModel
{
public:
FilterProxy()
{}
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
rowFiltered++;
if (sourceModel()->data(sourceModel()->index(source_row, 0, source_parent)).toString() == QLatin1String("A1"))
return !rejectA1;
return true;
}
bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override
{
Q_UNUSED(source_column)
Q_UNUSED(source_parent)
columnFiltered++;
return true;
}
mutable int rowFiltered = 0;
mutable int columnFiltered = 0;
bool rejectA1 = false;
using QSortFilterProxyModel::invalidateFilter;
using QSortFilterProxyModel::invalidateRowsFilter;
using QSortFilterProxyModel::invalidateColumnsFilter;
};
QStandardItemModel model(10, 4);
for (int i = 0; i < model.rowCount(); ++i) {
for (int j = 0; j < model.columnCount(); ++j) {
model.setItem(i, j, new QStandardItem(QString('A' + j) + QString::number(i + 1)));
model.item(i, 0)->appendColumn({ new QStandardItem(QString("child col %0").arg(j)) });
}
}
FilterProxy proxy;
proxy.setSourceModel(&model);
QTreeView view;
view.setModel(&proxy);
view.expandAll();
QCOMPARE(proxy.rowFiltered, 20); //10 parents + 10 children
QCOMPARE(proxy.columnFiltered, 44); // 4 parents + 4 * 10 children
proxy.rowFiltered = proxy.columnFiltered = 0;
proxy.invalidateFilter();
QCOMPARE(proxy.rowFiltered, 20);
QCOMPARE(proxy.columnFiltered, 44);
proxy.rowFiltered = proxy.columnFiltered = 0;
proxy.invalidateRowsFilter();
QCOMPARE(proxy.rowFiltered, 20);
QCOMPARE(proxy.columnFiltered, 0);
proxy.rowFiltered = proxy.columnFiltered = 0;
proxy.invalidateColumnsFilter();
QCOMPARE(proxy.rowFiltered, 0);
QCOMPARE(proxy.columnFiltered, 44);
QCOMPARE(proxy.rowCount(), 10);
proxy.rejectA1 = true;
proxy.rowFiltered = proxy.columnFiltered = 0;
proxy.invalidateRowsFilter();
QCOMPARE(proxy.rowCount(), 9);
QCOMPARE(proxy.rowFiltered, 19); // it will not check the child row of A1
proxy.rowFiltered = proxy.columnFiltered = 0;
proxy.setRecursiveFilteringEnabled(true); // this triggers invalidateRowsFilter()
QCOMPARE(proxy.rowCount(), 10);
QCOMPARE(proxy.rowFiltered, 20);
}
#include "tst_qsortfilterproxymodel.moc"

View File

@ -159,6 +159,8 @@ private slots:
void removeIntervals_data();
void removeIntervals();
void invalidateColumnsOrRowsFilter();
protected:
void buildHierarchy(const QStringList &data, QAbstractItemModel *model);
void checkHierarchy(const QStringList &data, const QAbstractItemModel *model);