diff --git a/src/corelib/itemmodels/qsortfilterproxymodel.cpp b/src/corelib/itemmodels/qsortfilterproxymodel.cpp index 9ef75f6073..1b5dc0dec2 100644 --- a/src/corelib/itemmodels/qsortfilterproxymodel.cpp +++ b/src/corelib/itemmodels/qsortfilterproxymodel.cpp @@ -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::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 QSortFilterProxyModel::bindableFilterRegularExpression() +{ + Q_D(QSortFilterProxyModel); + return QBindable(&d->filter_regularexpression); +} + void QSortFilterProxyModel::setFilterRegularExpression(const QRegularExpression ®ularExpression) { 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 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 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 QSortFilterProxyModel::bindableFilterCaseSensitivity() +{ + Q_D(QSortFilterProxyModel); + return QBindable(&d->filter_casesensitive); } /*! @@ -2755,14 +2826,20 @@ QBindable 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()); } /*! diff --git a/src/corelib/itemmodels/qsortfilterproxymodel.h b/src/corelib/itemmodels/qsortfilterproxymodel.h index f29126d460..b3c7b74453 100644 --- a/src/corelib/itemmodels/qsortfilterproxymodel.h +++ b/src/corelib/itemmodels/qsortfilterproxymodel.h @@ -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 bindableFilterRegularExpression(); int filterKeyColumn() const; void setFilterKeyColumn(int column); @@ -100,6 +104,7 @@ public: Qt::CaseSensitivity filterCaseSensitivity() const; void setFilterCaseSensitivity(Qt::CaseSensitivity cs); + QBindable bindableFilterCaseSensitivity(); Qt::CaseSensitivity sortCaseSensitivity() const; void setSortCaseSensitivity(Qt::CaseSensitivity cs); diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp index 2cee97348f..7df927eccc 100644 --- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp @@ -5356,4 +5356,101 @@ void tst_QSortFilterProxyModel::autoAcceptChildRowsBinding() "autoAcceptChildRows"); } +void tst_QSortFilterProxyModel::filterCaseSensitivityBinding() +{ + QSortFilterProxyModel proxyModel; + QCOMPARE(proxyModel.filterCaseSensitivity(), Qt::CaseSensitive); + QTestPrivate::testReadWritePropertyBasics( + proxyModel, Qt::CaseInsensitive, Qt::CaseSensitive, "filterCaseSensitivity"); + if (QTest::currentTestFailed()) + return; + + // Make sure that setting QRegularExpression updates filterCaseSensitivity + // and invalidates its binding. + QProperty 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( + proxyModel, initial, changed, "filterRegularExpression"); + if (QTest::currentTestFailed()) + return; + + // Make sure that setting filterCaseSensitivity updates QRegularExpression + // and invalidates its binding. + QProperty 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 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" diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h index 130ec510dd..23687b05b2 100644 --- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h @@ -173,6 +173,8 @@ private slots: void filterRoleBinding(); void recursiveFilteringEnabledBinding(); void autoAcceptChildRowsBinding(); + void filterCaseSensitivityBinding(); + void filterRegularExpressionBinding(); protected: void buildHierarchy(const QStringList &data, QAbstractItemModel *model);