QSFPM: make filterRegularExpression and filterCaseSensitivity bindable

This takes care of the last two QSFPM properties. They were postponed
because of the tricky relation between them and some bugs in handling
the changes.
The bug was fixed in bcbbbdb2d6 and
now the behavior is well-determined: updating filter regexp does
trigger case sensitivity change and vice versa. However updating
only regexp pattern (via QString overload or setFilterFixedString
or setFilterWildcard) does not change case sensitivity.

Task-number: QTBUG-85520
Change-Id: Idc41cf817de9e6263ed65a80fa40fc8415c8c856
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Andreas Buhr <andreas.buhr@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
bb10
Ivan Solovev 2021-04-23 16:00:17 +02:00
parent 9134113c4f
commit bdaaf99228
4 changed files with 233 additions and 40 deletions

View File

@ -202,6 +202,20 @@ public:
void setDynamicSortFilterForwarder(bool enable) { q_func()->setDynamicSortFilter(enable); }
void setFilterCaseSensitivityForwarder(Qt::CaseSensitivity cs)
{
q_func()->setFilterCaseSensitivity(cs);
}
void filterCaseSensitivityChangedForwarder(Qt::CaseSensitivity cs)
{
emit q_func()->filterCaseSensitivityChanged(cs);
}
void setFilterRegularExpressionForwarder(const QRegularExpression &re)
{
q_func()->setFilterRegularExpression(re);
}
int source_sort_column = -1;
int proxy_sort_column = -1;
Qt::SortOrder sort_order = Qt::AscendingOrder;
@ -245,7 +259,15 @@ public:
&QSortFilterProxyModelPrivate::setDynamicSortFilterForwarder,
true)
QRegularExpression filter_regularexpression;
Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(
QSortFilterProxyModelPrivate, Qt::CaseSensitivity, filter_casesensitive,
&QSortFilterProxyModelPrivate::setFilterCaseSensitivityForwarder,
&QSortFilterProxyModelPrivate::filterCaseSensitivityChangedForwarder, Qt::CaseSensitive)
Q_OBJECT_COMPAT_PROPERTY(QSortFilterProxyModelPrivate, QRegularExpression,
filter_regularexpression,
&QSortFilterProxyModelPrivate::setFilterRegularExpressionForwarder)
QModelIndex last_top_source;
QRowsRemoval itemsBeingRemoved;
@ -266,9 +288,14 @@ public:
*/
void set_filter_pattern(const QString &pattern)
{
filter_regularexpression.setPattern(pattern);
filter_regularexpression.setPatternOptions(filter_regularexpression.patternOptions()
& QRegularExpression::CaseInsensitiveOption);
QRegularExpression re = filter_regularexpression.value();
const auto cs = re.patternOptions() & QRegularExpression::CaseInsensitiveOption;
re.setPattern(pattern);
re.setPatternOptions(cs);
// This is a helper function, which is supposed to be called from a
// more complicated context. Because of that, the caller is responsible
// for calling notify() and removeBindingUnlessInWrapper(), if needed.
filter_regularexpression.setValueBypassingBindings(re);
}
inline QHash<QModelIndex, Mapping *>::const_iterator index_to_iterator(
@ -1247,7 +1274,7 @@ void QSortFilterProxyModelPrivate::update_persistent_indexes(
*/
void QSortFilterProxyModelPrivate::filter_about_to_be_changed(const QModelIndex &source_parent)
{
if (!filter_regularexpression.pattern().isEmpty()
if (!filter_regularexpression.value().pattern().isEmpty()
&& source_index_mapping.constFind(source_parent) == source_index_mapping.constEnd()) {
create_mapping(source_parent);
}
@ -2567,6 +2594,12 @@ Qt::SortOrder QSortFilterProxyModel::sortOrder() const
If no QRegularExpression or an empty string is set, everything in the source
model will be accepted.
\note Setting this property propagates the case sensitivity of the new
regular expression to the \l filterCaseSensitivity property, and so breaks
its binding. Likewise explicitly setting \l filterCaseSensitivity changes
the case sensitivity of the current regular expression, thereby breaking
its binding.
\sa filterCaseSensitivity, setFilterWildcard(), setFilterFixedString()
*/
QRegularExpression QSortFilterProxyModel::filterRegularExpression() const
@ -2575,16 +2608,35 @@ QRegularExpression QSortFilterProxyModel::filterRegularExpression() const
return d->filter_regularexpression;
}
QBindable<QRegularExpression> QSortFilterProxyModel::bindableFilterRegularExpression()
{
Q_D(QSortFilterProxyModel);
return QBindable<QRegularExpression>(&d->filter_regularexpression);
}
void QSortFilterProxyModel::setFilterRegularExpression(const QRegularExpression &regularExpression)
{
Q_D(QSortFilterProxyModel);
Qt::beginPropertyUpdateGroup();
const bool regExpChanged = regularExpression != d->filter_regularexpression.value();
d->filter_regularexpression.removeBindingUnlessInWrapper();
d->filter_casesensitive.removeBindingUnlessInWrapper();
const Qt::CaseSensitivity cs = filterCaseSensitivity();
d->filter_about_to_be_changed();
d->filter_regularexpression = regularExpression;
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
const Qt::CaseSensitivity updatedCs = filterCaseSensitivity();
const Qt::CaseSensitivity updatedCs =
regularExpression.patternOptions() & QRegularExpression::CaseInsensitiveOption
? Qt::CaseInsensitive : Qt::CaseSensitive;
d->filter_regularexpression.setValueBypassingBindings(regularExpression);
if (cs != updatedCs)
emit filterCaseSensitivityChanged(updatedCs);
d->filter_casesensitive.setValueBypassingBindings(updatedCs);
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
// Do not change the evaluation logic, but notify only if the regular
// expression has actually changed.
if (regExpChanged)
d->filter_regularexpression.notify();
if (cs != updatedCs)
d->filter_casesensitive.notify();
Qt::endPropertyUpdateGroup();
}
/*!
@ -2632,6 +2684,11 @@ QBindable<int> QSortFilterProxyModel::bindableFilterKeyColumn()
By default, the filter is case sensitive.
\note Setting this property propagates the new case sensitivity to the
\l filterRegularExpression property, and so breaks its binding. Likewise
explicitly setting \l filterRegularExpression changes the current case
sensitivity, thereby breaking its binding.
\sa filterRegularExpression, sortCaseSensitivity
*/
@ -2644,23 +2701,37 @@ QBindable<int> QSortFilterProxyModel::bindableFilterKeyColumn()
Qt::CaseSensitivity QSortFilterProxyModel::filterCaseSensitivity() const
{
Q_D(const QSortFilterProxyModel);
return d->filter_regularexpression.patternOptions() & QRegularExpression::CaseInsensitiveOption
? Qt::CaseInsensitive
: Qt::CaseSensitive;
return d->filter_casesensitive;
}
void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs)
{
Q_D(QSortFilterProxyModel);
QRegularExpression::PatternOptions options = d->filter_regularexpression.patternOptions();
options.setFlag(QRegularExpression::CaseInsensitiveOption, cs == Qt::CaseInsensitive);
if (d->filter_regularexpression.patternOptions() == options)
d->filter_casesensitive.removeBindingUnlessInWrapper();
d->filter_regularexpression.removeBindingUnlessInWrapper();
if (cs == d->filter_casesensitive)
return;
Qt::beginPropertyUpdateGroup();
QRegularExpression::PatternOptions options =
d->filter_regularexpression.value().patternOptions();
options.setFlag(QRegularExpression::CaseInsensitiveOption, cs == Qt::CaseInsensitive);
d->filter_casesensitive.setValueBypassingBindings(cs);
d->filter_about_to_be_changed();
d->filter_regularexpression.setPatternOptions(options);
QRegularExpression re = d->filter_regularexpression;
re.setPatternOptions(options);
d->filter_regularexpression.setValueBypassingBindings(re);
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
emit filterCaseSensitivityChanged(cs);
d->filter_regularexpression.notify();
d->filter_casesensitive.notify();
Qt::endPropertyUpdateGroup();
}
QBindable<Qt::CaseSensitivity> QSortFilterProxyModel::bindableFilterCaseSensitivity()
{
Q_D(QSortFilterProxyModel);
return QBindable<Qt::CaseSensitivity>(&d->filter_casesensitive);
}
/*!
@ -2755,14 +2826,20 @@ QBindable<bool> QSortFilterProxyModel::bindableIsSortLocaleAware()
This method will reset the regular expression options
but respect case sensitivity.
\note Calling this method updates the regular expression, thereby breaking
the binding for \l filterRegularExpression. However it has no effect on the
\l filterCaseSensitivity bindings.
\sa setFilterCaseSensitivity(), setFilterWildcard(), setFilterFixedString(), filterRegularExpression()
*/
void QSortFilterProxyModel::setFilterRegularExpression(const QString &pattern)
{
Q_D(QSortFilterProxyModel);
d->filter_regularexpression.removeBindingUnlessInWrapper();
d->filter_about_to_be_changed();
d->set_filter_pattern(pattern);
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
d->filter_regularexpression.notify();
}
/*!
@ -2772,15 +2849,21 @@ void QSortFilterProxyModel::setFilterRegularExpression(const QString &pattern)
This method will reset the regular expression options
but respect case sensitivity.
\note Calling this method updates the regular expression, thereby breaking
the binding for \l filterRegularExpression. However it has no effect on the
\l filterCaseSensitivity bindings.
\sa setFilterCaseSensitivity(), setFilterRegularExpression(), setFilterFixedString(), filterRegularExpression()
*/
void QSortFilterProxyModel::setFilterWildcard(const QString &pattern)
{
Q_D(QSortFilterProxyModel);
d->filter_regularexpression.removeBindingUnlessInWrapper();
d->filter_about_to_be_changed();
d->set_filter_pattern(QRegularExpression::wildcardToRegularExpression(
pattern, QRegularExpression::UnanchoredWildcardConversion));
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
d->filter_regularexpression.notify();
}
/*!
@ -2790,14 +2873,20 @@ void QSortFilterProxyModel::setFilterWildcard(const QString &pattern)
This method will reset the regular expression options
but respect case sensitivity.
\note Calling this method updates the regular expression, thereby breaking
the binding for \l filterRegularExpression. However it has no effect on the
\l filterCaseSensitivity bindings.
\sa setFilterCaseSensitivity(), setFilterRegularExpression(), setFilterWildcard(), filterRegularExpression()
*/
void QSortFilterProxyModel::setFilterFixedString(const QString &pattern)
{
Q_D(QSortFilterProxyModel);
d->filter_regularexpression.removeBindingUnlessInWrapper();
d->filter_about_to_be_changed();
d->set_filter_pattern(QRegularExpression::escape(pattern));
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
d->filter_regularexpression.notify();
}
/*!
@ -3151,7 +3240,7 @@ bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &
{
Q_D(const QSortFilterProxyModel);
if (d->filter_regularexpression.pattern().isEmpty())
if (d->filter_regularexpression.value().pattern().isEmpty())
return true;
int column_count = d->model->columnCount(source_parent);
@ -3159,7 +3248,7 @@ bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &
for (int column = 0; column < column_count; ++column) {
QModelIndex source_index = d->model->index(source_row, column, source_parent);
QString key = d->model->data(source_index, d->filter_role).toString();
if (key.contains(d->filter_regularexpression))
if (key.contains(d->filter_regularexpression.value()))
return true;
}
return false;
@ -3169,7 +3258,7 @@ bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &
return true;
QModelIndex source_index = d->model->index(source_row, d->filter_column, source_parent);
QString key = d->model->data(source_index, d->filter_role).toString();
return key.contains(d->filter_regularexpression);
return key.contains(d->filter_regularexpression.value());
}
/*!

View File

@ -59,26 +59,29 @@ class Q_CORE_EXPORT QSortFilterProxyModel : public QAbstractProxyModel
friend class QSortFilterProxyModelGreaterThan;
Q_OBJECT
Q_PROPERTY(QRegularExpression filterRegularExpression READ filterRegularExpression WRITE setFilterRegularExpression)
Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn BINDABLE
bindableFilterKeyColumn)
Q_PROPERTY(bool dynamicSortFilter READ dynamicSortFilter WRITE setDynamicSortFilter BINDABLE
bindableDynamicSortFilter)
Q_PROPERTY(Qt::CaseSensitivity filterCaseSensitivity READ filterCaseSensitivity WRITE setFilterCaseSensitivity NOTIFY filterCaseSensitivityChanged)
Q_PROPERTY(Qt::CaseSensitivity sortCaseSensitivity READ sortCaseSensitivity WRITE
setSortCaseSensitivity NOTIFY sortCaseSensitivityChanged BINDABLE
bindableSortCaseSensitivity)
Q_PROPERTY(bool isSortLocaleAware READ isSortLocaleAware WRITE setSortLocaleAware NOTIFY
sortLocaleAwareChanged BINDABLE bindableIsSortLocaleAware)
Q_PROPERTY(int sortRole READ sortRole WRITE setSortRole NOTIFY sortRoleChanged BINDABLE
bindableSortRole)
Q_PROPERTY(int filterRole READ filterRole WRITE setFilterRole NOTIFY filterRoleChanged BINDABLE
bindableFilterRole)
Q_PROPERTY(bool recursiveFilteringEnabled READ isRecursiveFilteringEnabled WRITE
setRecursiveFilteringEnabled NOTIFY recursiveFilteringEnabledChanged BINDABLE
bindableRecursiveFilteringEnabled)
Q_PROPERTY(bool autoAcceptChildRows READ autoAcceptChildRows WRITE setAutoAcceptChildRows NOTIFY
autoAcceptChildRowsChanged BINDABLE bindableAutoAcceptChildRows)
Q_PROPERTY(QRegularExpression filterRegularExpression READ filterRegularExpression
WRITE setFilterRegularExpression BINDABLE bindableFilterRegularExpression)
Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn
BINDABLE bindableFilterKeyColumn)
Q_PROPERTY(bool dynamicSortFilter READ dynamicSortFilter WRITE setDynamicSortFilter
BINDABLE bindableDynamicSortFilter)
Q_PROPERTY(Qt::CaseSensitivity filterCaseSensitivity READ filterCaseSensitivity
WRITE setFilterCaseSensitivity NOTIFY filterCaseSensitivityChanged
BINDABLE bindableFilterCaseSensitivity)
Q_PROPERTY(Qt::CaseSensitivity sortCaseSensitivity READ sortCaseSensitivity
WRITE setSortCaseSensitivity NOTIFY sortCaseSensitivityChanged
BINDABLE bindableSortCaseSensitivity)
Q_PROPERTY(bool isSortLocaleAware READ isSortLocaleAware WRITE setSortLocaleAware
NOTIFY sortLocaleAwareChanged BINDABLE bindableIsSortLocaleAware)
Q_PROPERTY(int sortRole READ sortRole WRITE setSortRole NOTIFY sortRoleChanged
BINDABLE bindableSortRole)
Q_PROPERTY(int filterRole READ filterRole WRITE setFilterRole NOTIFY filterRoleChanged
BINDABLE bindableFilterRole)
Q_PROPERTY(bool recursiveFilteringEnabled READ isRecursiveFilteringEnabled
WRITE setRecursiveFilteringEnabled NOTIFY recursiveFilteringEnabledChanged
BINDABLE bindableRecursiveFilteringEnabled)
Q_PROPERTY(bool autoAcceptChildRows READ autoAcceptChildRows WRITE setAutoAcceptChildRows
NOTIFY autoAcceptChildRowsChanged BINDABLE bindableAutoAcceptChildRows)
public:
explicit QSortFilterProxyModel(QObject *parent = nullptr);
@ -93,6 +96,7 @@ public:
QItemSelection mapSelectionFromSource(const QItemSelection &sourceSelection) const override;
QRegularExpression filterRegularExpression() const;
QBindable<QRegularExpression> bindableFilterRegularExpression();
int filterKeyColumn() const;
void setFilterKeyColumn(int column);
@ -100,6 +104,7 @@ public:
Qt::CaseSensitivity filterCaseSensitivity() const;
void setFilterCaseSensitivity(Qt::CaseSensitivity cs);
QBindable<Qt::CaseSensitivity> bindableFilterCaseSensitivity();
Qt::CaseSensitivity sortCaseSensitivity() const;
void setSortCaseSensitivity(Qt::CaseSensitivity cs);

View File

@ -5356,4 +5356,101 @@ void tst_QSortFilterProxyModel::autoAcceptChildRowsBinding()
"autoAcceptChildRows");
}
void tst_QSortFilterProxyModel::filterCaseSensitivityBinding()
{
QSortFilterProxyModel proxyModel;
QCOMPARE(proxyModel.filterCaseSensitivity(), Qt::CaseSensitive);
QTestPrivate::testReadWritePropertyBasics<QSortFilterProxyModel, Qt::CaseSensitivity>(
proxyModel, Qt::CaseInsensitive, Qt::CaseSensitive, "filterCaseSensitivity");
if (QTest::currentTestFailed())
return;
// Make sure that setting QRegularExpression updates filterCaseSensitivity
// and invalidates its binding.
QProperty<Qt::CaseSensitivity> setter(Qt::CaseSensitive);
proxyModel.bindableFilterCaseSensitivity().setBinding(Qt::makePropertyBinding(setter));
QCOMPARE(proxyModel.filterCaseSensitivity(), Qt::CaseSensitive);
QVERIFY(proxyModel.bindableFilterCaseSensitivity().hasBinding());
QSignalSpy spy(&proxyModel, &QSortFilterProxyModel::filterCaseSensitivityChanged);
QRegularExpression regExp("pattern", QRegularExpression::CaseInsensitiveOption);
proxyModel.setFilterRegularExpression(regExp);
QCOMPARE(proxyModel.filterCaseSensitivity(), Qt::CaseInsensitive);
QCOMPARE(spy.count(), 1);
QVERIFY(!proxyModel.bindableFilterCaseSensitivity().hasBinding());
}
void tst_QSortFilterProxyModel::filterRegularExpressionBinding()
{
QSortFilterProxyModel proxyModel;
QCOMPARE(proxyModel.filterRegularExpression(), QRegularExpression());
const QRegularExpression initial("initial", QRegularExpression::CaseInsensitiveOption);
const QRegularExpression changed("changed");
QTestPrivate::testReadWritePropertyBasics<QSortFilterProxyModel, QRegularExpression>(
proxyModel, initial, changed, "filterRegularExpression");
if (QTest::currentTestFailed())
return;
// Make sure that setting filterCaseSensitivity updates QRegularExpression
// and invalidates its binding.
QProperty<QRegularExpression> setter(initial);
proxyModel.bindableFilterRegularExpression().setBinding(Qt::makePropertyBinding(setter));
QCOMPARE(proxyModel.filterRegularExpression(), initial);
QVERIFY(proxyModel.bindableFilterRegularExpression().hasBinding());
int counter = 0;
auto handler = proxyModel.bindableFilterRegularExpression().onValueChanged(
[&counter]() { ++counter; });
Q_UNUSED(handler);
proxyModel.setFilterCaseSensitivity(Qt::CaseSensitive);
QCOMPARE(proxyModel.filterRegularExpression(), QRegularExpression(initial.pattern()));
QCOMPARE(counter, 1);
QVERIFY(!proxyModel.bindableFilterRegularExpression().hasBinding());
QProperty<Qt::CaseSensitivity> csSetter(Qt::CaseInsensitive);
// Make sure that setting filter string updates QRegularExpression, but does
// not break the binding for case sensitivity.
proxyModel.setFilterRegularExpression(initial);
proxyModel.bindableFilterCaseSensitivity().setBinding(Qt::makePropertyBinding(csSetter));
QVERIFY(proxyModel.bindableFilterCaseSensitivity().hasBinding());
counter = 0;
proxyModel.setFilterRegularExpression("ch(ang|opp)ed");
// The pattern has changed, but the case sensitivity options are the same.
QCOMPARE(proxyModel.filterRegularExpression(),
QRegularExpression("ch(ang|opp)ed", QRegularExpression::CaseInsensitiveOption));
QCOMPARE(counter, 1);
QVERIFY(proxyModel.bindableFilterCaseSensitivity().hasBinding());
// Make sure that setting filter wildcard updates QRegularExpression, but
// does not break the binding for case sensitivity.
proxyModel.setFilterRegularExpression(initial);
proxyModel.bindableFilterCaseSensitivity().setBinding(Qt::makePropertyBinding(csSetter));
QVERIFY(proxyModel.bindableFilterCaseSensitivity().hasBinding());
counter = 0;
proxyModel.setFilterWildcard("*.jpeg");
const QString wildcardStr = QRegularExpression::wildcardToRegularExpression(
"*.jpeg", QRegularExpression::UnanchoredWildcardConversion);
// The pattern has changed, but the case sensitivity options are the same.
QCOMPARE(proxyModel.filterRegularExpression(),
QRegularExpression(wildcardStr, QRegularExpression::CaseInsensitiveOption));
QCOMPARE(counter, 1);
QVERIFY(proxyModel.bindableFilterCaseSensitivity().hasBinding());
// Make sure that setting filter fixed string updates QRegularExpression,
// but does not break the binding for case sensitivity.
proxyModel.setFilterRegularExpression(initial);
proxyModel.bindableFilterCaseSensitivity().setBinding(Qt::makePropertyBinding(csSetter));
QVERIFY(proxyModel.bindableFilterCaseSensitivity().hasBinding());
counter = 0;
proxyModel.setFilterFixedString("test");
// The pattern has changed, but the case sensitivity options are the same.
QCOMPARE(proxyModel.filterRegularExpression(),
QRegularExpression("test", QRegularExpression::CaseInsensitiveOption));
QCOMPARE(counter, 1);
QVERIFY(proxyModel.bindableFilterCaseSensitivity().hasBinding());
}
#include "tst_qsortfilterproxymodel.moc"

View File

@ -173,6 +173,8 @@ private slots:
void filterRoleBinding();
void recursiveFilteringEnabledBinding();
void autoAcceptChildRowsBinding();
void filterCaseSensitivityBinding();
void filterRegularExpressionBinding();
protected:
void buildHierarchy(const QStringList &data, QAbstractItemModel *model);