Add support for QRegularExpression to QSortFilterProxyModel

This patch implements the support for QRegularExpression in
QSortFilterProxyModel.

[ChangeLog][QtCore][QSFPM] QSortFilterProxyModel now supports
QRegularExpression.

Task-number: QTBUG-46810
Change-Id: If932d55f98f9b8bcf3a72c03ffd51da52cb50ad1
Reviewed-by: Christian Ehrlicher <ch.ehrlicher@gmx.de>
Reviewed-by: Luca Beldi <v.ronin@yahoo.it>
Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
Samuel Gaist 2018-07-31 16:58:21 +02:00
parent bcd80eebf9
commit 346c15102b
13 changed files with 645 additions and 198 deletions

View File

@ -43,6 +43,7 @@
#include <qdebug.h>
#include <qdatetime.h>
#include <qpair.h>
#include <qregularexpression.h>
#include <qstringlist.h>
#include <private/qabstractitemmodel_p.h>
#include <private/qabstractproxymodel_p.h>
@ -146,6 +147,133 @@ private:
int end;
};
class RegularExpressionData {
private:
enum class ExpressionType {
RegExp,
#if QT_CONFIG(regularexpression)
RegularExpression
#endif
};
public:
RegularExpressionData() :
m_type(ExpressionType::RegExp)
{}
#if QT_CONFIG(regularexpression)
QRegularExpression regularExpression() const
{
if (m_type == ExpressionType::RegularExpression)
return m_regularExpression;
return QRegularExpression();
}
void setRegularExpression(const QRegularExpression &rx)
{
m_type = ExpressionType::RegularExpression;
m_regularExpression = rx;
m_regExp = QRegExp();
}
#endif
QRegExp regExp() const
{
if (m_type == ExpressionType::RegExp)
return m_regExp;
return QRegExp();
}
void setRegExp(const QRegExp &rx)
{
m_type = ExpressionType::RegExp;
m_regExp = rx;
#if QT_CONFIG(regularexpression)
m_regularExpression = QRegularExpression();
#endif
}
bool isEmpty() const
{
bool result = true;
switch (m_type) {
case ExpressionType::RegExp:
result = m_regExp.isEmpty();
break;
#if QT_CONFIG(regularexpression)
case ExpressionType::RegularExpression:
result = m_regularExpression.pattern().isEmpty();
break;
#endif
}
return result;
}
Qt::CaseSensitivity caseSensitivity() const
{
Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive;
switch (m_type) {
case ExpressionType::RegExp:
sensitivity = m_regExp.caseSensitivity();
break;
#if QT_CONFIG(regularexpression)
case ExpressionType::RegularExpression:
{
QRegularExpression::PatternOptions options = m_regularExpression.patternOptions();
if (!(options & QRegularExpression::CaseInsensitiveOption))
sensitivity = Qt::CaseSensitive;
}
break;
#endif
}
return sensitivity;
}
void setCaseSensitivity(Qt::CaseSensitivity cs)
{
switch (m_type) {
case ExpressionType::RegExp:
m_regExp.setCaseSensitivity(cs);
break;
#if QT_CONFIG(regularexpression)
case ExpressionType::RegularExpression:
{
QRegularExpression::PatternOptions options = m_regularExpression.patternOptions();
options.setFlag(QRegularExpression::CaseInsensitiveOption, cs == Qt::CaseSensitive);
m_regularExpression.setPatternOptions(options);
}
break;
#endif
}
}
bool hasMatch(const QString &str) const
{
bool result = false;
switch (m_type) {
case ExpressionType::RegExp:
result = str.contains(m_regExp);
break;
#if QT_CONFIG(regularexpression)
case ExpressionType::RegularExpression:
result = str.contains(m_regularExpression);
break;
#endif
}
return result;
}
private:
ExpressionType m_type;
QRegExp m_regExp;
#if QT_CONFIG(regularexpression)
QRegularExpression m_regularExpression;
#endif
};
class QSortFilterProxyModelPrivate : public QAbstractProxyModelPrivate
{
Q_DECLARE_PUBLIC(QSortFilterProxyModel)
@ -171,7 +299,7 @@ public:
int filter_column;
int filter_role;
QRegExp filter_regexp;
RegularExpressionData filter_data;
QModelIndex last_top_source;
bool filter_recursive;
@ -1109,7 +1237,7 @@ void QSortFilterProxyModelPrivate::update_persistent_indexes(
*/
void QSortFilterProxyModelPrivate::filter_about_to_be_changed(const QModelIndex &source_parent)
{
if (!filter_regexp.pattern().isEmpty() &&
if (!filter_data.isEmpty() &&
source_index_mapping.constFind(source_parent) == source_index_mapping.constEnd())
create_mapping(source_parent);
}
@ -1786,9 +1914,9 @@ void QSortFilterProxyModelPrivate::_q_sourceColumnsMoved(
If a parent item doesn't match the filter, none of its children will be
shown.
A common use case is to let the user specify the filter regexp, wildcard
pattern, or fixed string in a QLineEdit and to connect the
\l{QLineEdit::textChanged()}{textChanged()} signal to setFilterRegExp(),
A common use case is to let the user specify the filter regular expression,
wildcard pattern, or fixed string in a QLineEdit and to connect the
\l{QLineEdit::textChanged()}{textChanged()} signal to setFilterRegularExpression(),
setFilterWildcard(), or setFilterFixedString() to reapply the filter.
Custom filtering behavior can be achieved by reimplementing the
@ -1825,6 +1953,21 @@ void QSortFilterProxyModelPrivate::_q_sourceColumnsMoved(
\note Some general guidelines for subclassing models are available in the
\l{Model Subclassing Reference}.
\note With Qt 5, regular expression support has been improved through the
QRegularExpression class. QSortFilterProxyModel dating back prior to that
class creation, it originally supported only QRegExp. Since Qt 5.12,
QRegularExpression APIs have been added. Therefore, QRegExp APIs should be
considered deprecated and the QRegularExpression version should be used in
place.
\warning Don't mix calls to the getters and setters of different regexp types
as this will lead to unexpected results. For maximum compatibility, the original
implementation has been kept. Therefore, if, for example, a call to
setFilterRegularExpression is made followed by another one to
setFilterFixedString, the first call will setup a QRegularExpression object
to use as filter while the second will setup a QRegExp in FixedString mode.
However, this is an implementation detail that might change in the future.
\sa QAbstractProxyModel, QAbstractItemModel, {Model/View Programming},
{Basic Sort/Filter Model Example}, {Custom Sort/Filter Model Example}, QIdentityProxyModel
*/
@ -2437,17 +2580,47 @@ Qt::SortOrder QSortFilterProxyModel::sortOrder() const
QRegExp QSortFilterProxyModel::filterRegExp() const
{
Q_D(const QSortFilterProxyModel);
return d->filter_regexp;
return d->filter_data.regExp();
}
void QSortFilterProxyModel::setFilterRegExp(const QRegExp &regExp)
{
Q_D(QSortFilterProxyModel);
d->filter_about_to_be_changed();
d->filter_regexp = regExp;
d->filter_data.setRegExp(regExp);
d->filter_changed();
}
#if QT_CONFIG(regularexpression)
/*!
\since 5.12
\property QSortFilterProxyModel::filterRegularExpression
\brief the QRegularExpression used to filter the contents of the source model
Setting this property overwrites the current
\l{QSortFilterProxyModel::filterCaseSensitivity}{filterCaseSensitivity}.
By default, the QRegularExpression is an empty string matching all contents.
If no QRegularExpression or an empty string is set, everything in the source
model will be accepted.
\sa filterCaseSensitivity, setFilterWildcard(), setFilterFixedString()
*/
QRegularExpression QSortFilterProxyModel::filterRegularExpression() const
{
Q_D(const QSortFilterProxyModel);
return d->filter_data.regularExpression();
}
void QSortFilterProxyModel::setFilterRegularExpression(const QRegularExpression &regularExpression)
{
Q_D(QSortFilterProxyModel);
d->filter_about_to_be_changed();
d->filter_data.setRegularExpression(regularExpression);
d->filter_changed();
}
#endif
/*!
\property QSortFilterProxyModel::filterKeyColumn
\brief the column where the key used to filter the contents of the
@ -2483,16 +2656,16 @@ void QSortFilterProxyModel::setFilterKeyColumn(int column)
Qt::CaseSensitivity QSortFilterProxyModel::filterCaseSensitivity() const
{
Q_D(const QSortFilterProxyModel);
return d->filter_regexp.caseSensitivity();
return d->filter_data.caseSensitivity();
}
void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs)
{
Q_D(QSortFilterProxyModel);
if (cs == d->filter_regexp.caseSensitivity())
if (cs == d->filter_data.caseSensitivity())
return;
d->filter_about_to_be_changed();
d->filter_regexp.setCaseSensitivity(cs);
d->filter_data.setCaseSensitivity(cs);
d->filter_changed();
}
@ -2558,11 +2731,33 @@ void QSortFilterProxyModel::setFilterRegExp(const QString &pattern)
{
Q_D(QSortFilterProxyModel);
d->filter_about_to_be_changed();
d->filter_regexp.setPatternSyntax(QRegExp::RegExp);
d->filter_regexp.setPattern(pattern);
QRegExp rx(pattern);
d->filter_data.setRegExp(rx);
d->filter_changed();
}
#if QT_CONFIG(regularexpression)
/*!
\since 5.12
Sets the regular expression used to filter the contents
of the source model to \a pattern.
This method should be preferred for new code as it will use
QRegularExpression internally.
\sa setFilterCaseSensitivity(), setFilterWildcard(), setFilterFixedString(), filterRegularExpression()
*/
void QSortFilterProxyModel::setFilterRegularExpression(const QString &pattern)
{
Q_D(QSortFilterProxyModel);
d->filter_about_to_be_changed();
QRegularExpression rx(pattern);
d->filter_data.setRegularExpression(rx);
d->filter_changed();
}
#endif
/*!
Sets the wildcard expression used to filter the contents
of the source model to the given \a pattern.
@ -2573,8 +2768,8 @@ void QSortFilterProxyModel::setFilterWildcard(const QString &pattern)
{
Q_D(QSortFilterProxyModel);
d->filter_about_to_be_changed();
d->filter_regexp.setPatternSyntax(QRegExp::Wildcard);
d->filter_regexp.setPattern(pattern);
QRegExp rx(pattern, d->filter_data.caseSensitivity(), QRegExp::Wildcard);
d->filter_data.setRegExp(rx);
d->filter_changed();
}
@ -2588,8 +2783,8 @@ void QSortFilterProxyModel::setFilterFixedString(const QString &pattern)
{
Q_D(QSortFilterProxyModel);
d->filter_about_to_be_changed();
d->filter_regexp.setPatternSyntax(QRegExp::FixedString);
d->filter_regexp.setPattern(pattern);
QRegExp rx(pattern, d->filter_data.caseSensitivity(), QRegExp::FixedString);
d->filter_data.setRegExp(rx);
d->filter_changed();
}
@ -2814,14 +3009,15 @@ bool QSortFilterProxyModel::lessThan(const QModelIndex &source_left, const QMode
bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_D(const QSortFilterProxyModel);
if (d->filter_regexp.isEmpty())
if (d->filter_data.isEmpty())
return true;
if (d->filter_column == -1) {
int column_count = d->model->columnCount(source_parent);
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_regexp))
if (d->filter_data.hasMatch(key))
return true;
}
return false;
@ -2830,7 +3026,7 @@ bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &
if (!source_index.isValid()) // the column may not exist
return true;
QString key = d->model->data(source_index, d->filter_role).toString();
return key.contains(d->filter_regexp);
return d->filter_data.hasMatch(key);
}
/*!

View File

@ -42,6 +42,7 @@
#include <QtCore/qabstractproxymodel.h>
#include <QtCore/qregexp.h>
#include <QtCore/qregularexpression.h>
QT_REQUIRE_CONFIG(sortfilterproxymodel);
@ -59,6 +60,9 @@ class Q_CORE_EXPORT QSortFilterProxyModel : public QAbstractProxyModel
Q_OBJECT
Q_PROPERTY(QRegExp filterRegExp READ filterRegExp WRITE setFilterRegExp)
#if QT_CONFIG(regularexpression)
Q_PROPERTY(QRegularExpression filterRegularExpression READ filterRegularExpression WRITE setFilterRegularExpression)
#endif
Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn)
Q_PROPERTY(bool dynamicSortFilter READ dynamicSortFilter WRITE setDynamicSortFilter)
Q_PROPERTY(Qt::CaseSensitivity filterCaseSensitivity READ filterCaseSensitivity WRITE setFilterCaseSensitivity)
@ -83,6 +87,9 @@ public:
QRegExp filterRegExp() const;
void setFilterRegExp(const QRegExp &regExp);
QRegularExpression filterRegularExpression() const;
void setFilterRegularExpression(const QRegularExpression &regularExpression);
int filterKeyColumn() const;
void setFilterKeyColumn(int column);
@ -112,6 +119,9 @@ public:
public Q_SLOTS:
void setFilterRegExp(const QString &pattern);
#if QT_CONFIG(regularexpression)
void setFilterRegularExpression(const QString &pattern);
#endif
void setFilterWildcard(const QString &pattern);
void setFilterFixedString(const QString &pattern);
#if QT_DEPRECATED_SINCE(5, 11)

View File

@ -11,7 +11,8 @@ qtHaveModule(gui): SUBDIRS += \
qtHaveModule(widgets) {
SUBDIRS += \
qsortfilterproxymodel
qsortfilterproxymodel_regexp \
qsortfilterproxymodel_regularexpression
qtHaveModule(sql): SUBDIRS += \
qitemmodel

View File

@ -1 +0,0 @@
tst_qsortfilterproxymodel

View File

@ -1,9 +0,0 @@
CONFIG += testcase
TARGET = tst_qsortfilterproxymodel
QT += widgets testlib
mtdir = ../../../other/qabstractitemmodelutils
INCLUDEPATH += $$PWD/$${mtdir}
SOURCES += tst_qsortfilterproxymodel.cpp $${mtdir}/dynamictreemodel.cpp
HEADERS += $${mtdir}/dynamictreemodel.h

View File

@ -27,6 +27,7 @@
****************************************************************************/
#include <QtTest/QtTest>
#include "tst_qsortfilterproxymodel.h"
#include "dynamictreemodel.h"
#include <QtCore/QCoreApplication>
@ -36,137 +37,6 @@
#include <qdebug.h>
typedef QList<int> IntList;
typedef QPair<int, int> IntPair;
typedef QList<IntPair> IntPairList;
Q_DECLARE_METATYPE(QList<QPersistentModelIndex>)
class tst_QSortFilterProxyModel : public QObject
{
Q_OBJECT
public:
tst_QSortFilterProxyModel();
public slots:
void initTestCase();
void cleanupTestCase();
void cleanup();
private slots:
void getSetCheck();
void sort_data();
void sort();
void sortHierarchy_data();
void sortHierarchy();
void insertRows_data();
void insertRows();
void prependRow();
void removeRows_data();
void removeRows();
void removeColumns_data();
void removeColumns();
void insertAfterSelect();
void removeAfterSelect();
void filter_data();
void filter();
void filterHierarchy_data();
void filterHierarchy();
void filterColumns_data();
void filterColumns();
void filterTable();
void filterCurrent();
void filter_qtbug30662();
void changeSourceLayout();
void changeSourceLayoutFilteredOut();
void removeSourceRows_data();
void removeSourceRows();
void insertSourceRows_data();
void insertSourceRows();
void changeFilter_data();
void changeFilter();
void changeSourceData_data();
void changeSourceData();
void changeSourceDataKeepsStableSorting_qtbug1548();
void changeSourceDataForwardsRoles_qtbug35440();
void resortingDoesNotBreakTreeModels();
void dynamicFilterWithoutSort();
void sortFilterRole();
void selectionFilteredOut();
void match_data();
void match();
void insertIntoChildrenlessItem();
void invalidateMappedChildren();
void insertRowIntoFilteredParent();
void filterOutParentAndFilterInChild();
void sourceInsertRows();
void sourceModelDeletion();
void sortColumnTracking1();
void sortColumnTracking2();
void sortStable();
void hiddenColumns();
void insertRowsSort();
void staticSorting();
void dynamicSorting();
void fetchMore();
void hiddenChildren();
void mapFromToSource();
void removeRowsRecursive();
void doubleProxySelectionSetSourceModel();
void appearsAndSort();
void unnecessaryDynamicSorting();
void unnecessaryMapCreation();
void resetInvalidate_data();
void resetInvalidate();
void testMultipleProxiesWithSelection();
void mapSelectionFromSource();
void testResetInternalData();
void filteredColumns();
void headerDataChanged();
void testParentLayoutChanged();
void moveSourceRows();
void hierarchyFilterInvalidation();
void simpleFilterInvalidation();
void chainedProxyModelRoleNames();
void noMapAfterSourceDelete();
void forwardDropApi();
void canDropMimeData();
void filterHint();
void sourceLayoutChangeLeavesValidPersistentIndexes();
void rowMoveLeavesValidPersistentIndexes();
void emitLayoutChangedOnlyIfSortingChanged_data();
void emitLayoutChangedOnlyIfSortingChanged();
void checkSetNewModel();
void removeIntervals_data();
void removeIntervals();
protected:
void buildHierarchy(const QStringList &data, QAbstractItemModel *model);
void checkHierarchy(const QStringList &data, const QAbstractItemModel *model);
private:
QStandardItemModel *m_model;
QSortFilterProxyModel *m_proxy;
};
Q_DECLARE_METATYPE(QAbstractItemModel::LayoutChangeHint)
// Testing get/set functions
void tst_QSortFilterProxyModel::getSetCheck()
{
@ -204,7 +74,15 @@ void tst_QSortFilterProxyModel::cleanupTestCase()
void tst_QSortFilterProxyModel::cleanup()
{
switch (m_filterType) {
case FilterType::RegExp:
m_proxy->setFilterRegExp(QRegExp());
break;
case FilterType::RegularExpression:
m_proxy->setFilterRegularExpression(QRegularExpression());
break;
}
m_proxy->sort(-1, Qt::AscendingOrder);
m_model->clear();
m_model->insertColumns(0, 1);
@ -915,8 +793,9 @@ void tst_QSortFilterProxyModel::removeRows()
if (sortOrder != -1)
proxy.sort(0, static_cast<Qt::SortOrder>(sortOrder));
if (!filter.isEmpty())
proxy.setFilterRegExp(QRegExp(filter));
setupFilter(&proxy, filter);
// remove the rows
QCOMPARE(proxy.removeRows(position, count, QModelIndex()), success);
@ -938,14 +817,29 @@ class MyFilteredColumnProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
MyFilteredColumnProxyModel(QObject *parent = 0)
: QSortFilterProxyModel(parent) { }
MyFilteredColumnProxyModel(FilterType filterType, QObject *parent = 0) :
QSortFilterProxyModel(parent),
m_filterType(filterType)
{ }
protected:
bool filterAcceptsColumn(int sourceColumn, const QModelIndex &) const
{
QString key = sourceModel()->headerData(sourceColumn, Qt::Horizontal).toString();
return key.contains(filterRegExp());
bool result = false;
switch (m_filterType) {
case FilterType::RegExp:
result = key.contains(filterRegExp());
break;
case FilterType::RegularExpression:
result = key.contains(filterRegularExpression());
break;
}
return result;
}
private:
FilterType m_filterType;
};
void tst_QSortFilterProxyModel::removeColumns_data()
@ -1154,10 +1048,10 @@ void tst_QSortFilterProxyModel::removeColumns()
QFETCH(QStringList, expectedSource);
QStandardItemModel model;
MyFilteredColumnProxyModel proxy;
MyFilteredColumnProxyModel proxy(m_filterType);
proxy.setSourceModel(&model);
if (!filter.isEmpty())
proxy.setFilterRegExp(QRegExp(filter));
setupFilter(&proxy, filter);
// prepare model
model.setHorizontalHeaderLabels(initial);
@ -1225,7 +1119,8 @@ void tst_QSortFilterProxyModel::filterColumns()
QModelIndex index = m_model->index(0, col, QModelIndex());
m_model->setData(index, initial.at(col), Qt::DisplayRole);
}
m_proxy->setFilterRegExp(pattern);
setupFilter(m_proxy, pattern);
m_proxy->setFilterKeyColumn(-1);
// make sure the model is unchanged
for (int col = 0; col < m_model->columnCount(QModelIndex()); ++col) {
@ -1291,6 +1186,7 @@ void tst_QSortFilterProxyModel::filter()
QFETCH(QString, pattern);
QFETCH(QStringList, initial);
QFETCH(QStringList, expected);
// prepare model
QVERIFY(m_model->insertRows(0, initial.count(), QModelIndex()));
QCOMPARE(m_model->rowCount(QModelIndex()), initial.count());
@ -1300,7 +1196,7 @@ void tst_QSortFilterProxyModel::filter()
QModelIndex index = m_model->index(row, 0, QModelIndex());
m_model->setData(index, initial.at(row), Qt::DisplayRole);
}
m_proxy->setFilterRegExp(pattern);
setupFilter(m_proxy, pattern);
// make sure the proxy is unfiltered
QCOMPARE(m_proxy->columnCount(QModelIndex()), 1);
QCOMPARE(m_proxy->rowCount(QModelIndex()), expected.count());
@ -1339,7 +1235,7 @@ void tst_QSortFilterProxyModel::filterHierarchy()
QFETCH(QStringList, initial);
QFETCH(QStringList, expected);
buildHierarchy(initial, m_model);
m_proxy->setFilterRegExp(pattern);
setupFilter(m_proxy, pattern);
checkHierarchy(initial, m_model);
checkHierarchy(expected, m_proxy);
}
@ -1404,6 +1300,18 @@ void tst_QSortFilterProxyModel::checkHierarchy(const QStringList &l, const QAbst
}
}
void tst_QSortFilterProxyModel::setupFilter(QSortFilterProxyModel *model, const QString& pattern)
{
switch (m_filterType) {
case FilterType::RegExp:
model->setFilterRegExp(pattern);
break;
case FilterType::RegularExpression:
model->setFilterRegularExpression(pattern);
break;
}
}
class TestModel: public QAbstractTableModel
{
public:
@ -1422,7 +1330,7 @@ void tst_QSortFilterProxyModel::filterTable()
TestModel model;
QSortFilterProxyModel filter;
filter.setSourceModel(&model);
filter.setFilterRegExp("9");
setupFilter(&filter, QLatin1String("9"));
for (int i = 0; i < filter.rowCount(); ++i)
QVERIFY(filter.data(filter.index(i, 0)).toString().contains(QLatin1Char('9')));
@ -1486,7 +1394,7 @@ void tst_QSortFilterProxyModel::filterCurrent()
view.setCurrentIndex(proxy.index(0, 0));
QCOMPARE(spy.count(), 1);
proxy.setFilterRegExp(QRegExp("^B"));
setupFilter(&proxy, QLatin1String("^B"));
QCOMPARE(spy.count(), 2);
}
@ -1497,7 +1405,7 @@ void tst_QSortFilterProxyModel::filter_qtbug30662()
proxy.setSourceModel(&model);
// make sure the filter does not match any entry
proxy.setFilterRegExp(QRegExp("[0-9]+"));
setupFilter(&proxy, QLatin1String("[0-9]+"));
QStringList slSource;
slSource << "z" << "x" << "a" << "b";
@ -1507,7 +1415,7 @@ void tst_QSortFilterProxyModel::filter_qtbug30662()
model.setStringList(slSource);
// without fix for QTBUG-30662 this will make all entries visible - but unsorted
proxy.setFilterRegExp(QRegExp("[a-z]+"));
setupFilter(&proxy, QLatin1String("[a-z]+"));
QStringList slResult;
for (int i = 0; i < proxy.rowCount(); ++i)
@ -1553,7 +1461,8 @@ void tst_QSortFilterProxyModel::changeSourceLayoutFilteredOut()
QSignalSpy removeSpy(&proxy, &QSortFilterProxyModel::rowsRemoved);
// Filter everything out
proxy.setFilterRegExp(QRegExp("c"));
setupFilter(&proxy, QLatin1String("c"));
QCOMPARE(removeSpy.count(), 1);
QCOMPARE(0, proxy.rowCount());
@ -1562,7 +1471,8 @@ void tst_QSortFilterProxyModel::changeSourceLayoutFilteredOut()
QSignalSpy insertSpy(&proxy, &QSortFilterProxyModel::rowsInserted);
// Remove filter; we expect an insert
proxy.setFilterRegExp(QRegExp(""));
setupFilter(&proxy, "");
QCOMPARE(insertSpy.count(), 1);
QCOMPARE(beforeSortFilter, proxy.rowCount());
}
@ -1842,7 +1752,7 @@ void tst_QSortFilterProxyModel::changeFilter()
QVERIFY(initialRemoveSpy.isValid());
QVERIFY(initialInsertSpy.isValid());
proxy.setFilterRegExp(initialFilter);
setupFilter(&proxy, initialFilter);
QCOMPARE(initialRemoveSpy.count(), initialRemoveIntervals.count());
QCOMPARE(initialInsertSpy.count(), 0);
@ -1866,7 +1776,7 @@ void tst_QSortFilterProxyModel::changeFilter()
QVERIFY(finalRemoveSpy.isValid());
QVERIFY(finalInsertSpy.isValid());
proxy.setFilterRegExp(finalFilter);
setupFilter(&proxy, finalFilter);
QCOMPARE(finalRemoveSpy.count(), finalRemoveIntervals.count());
for (int i = 0; i < finalRemoveSpy.count(); ++i) {
@ -2060,7 +1970,7 @@ void tst_QSortFilterProxyModel::changeSourceData()
proxy.sort(0, static_cast<Qt::SortOrder>(sortOrder));
(void)proxy.rowCount(QModelIndex()); // force mapping
proxy.setFilterRegExp(filter);
setupFilter(&proxy, filter);
QCOMPARE(proxy.rowCount(), expectedInitialProxyItems.count());
for (int i = 0; i < expectedInitialProxyItems.count(); ++i) {
@ -2253,7 +2163,8 @@ void tst_QSortFilterProxyModel::sortFilterRole()
model.setData(index, sourceItems.at(i).second, Qt::UserRole);
}
proxy.setFilterRegExp("2");
setupFilter(&proxy, QLatin1String("2"));
QCOMPARE(proxy.rowCount(), 0); // Qt::DisplayRole is default role
proxy.setFilterRole(Qt::UserRole);
@ -2262,7 +2173,8 @@ void tst_QSortFilterProxyModel::sortFilterRole()
proxy.setFilterRole(Qt::DisplayRole);
QCOMPARE(proxy.rowCount(), 0);
proxy.setFilterRegExp("1|2|3");
setupFilter(&proxy, QLatin1String("1|2|3"));
QCOMPARE(proxy.rowCount(), 0);
proxy.setFilterRole(Qt::UserRole);
@ -2273,7 +2185,8 @@ void tst_QSortFilterProxyModel::sortFilterRole()
proxy.setSortRole(Qt::UserRole);
proxy.setFilterRole(Qt::DisplayRole);
proxy.setFilterRegExp("a|c");
setupFilter(&proxy, QLatin1String("a|c"));
QCOMPARE(proxy.rowCount(), orderedItems.count());
for (int i = 0; i < proxy.rowCount(); ++i) {
QModelIndex index = proxy.index(i, 0, QModelIndex());
@ -2297,7 +2210,8 @@ void tst_QSortFilterProxyModel::selectionFilteredOut()
view.setCurrentIndex(proxy.index(0, 0));
QCOMPARE(spy.count(), 1);
proxy.setFilterRegExp(QRegExp("^B"));
setupFilter(&proxy, QLatin1String("^B"));
QCOMPARE(spy.count(), 2);
}
@ -2383,7 +2297,7 @@ void tst_QSortFilterProxyModel::match()
}
proxy.sort(0, static_cast<Qt::SortOrder>(sortOrder));
proxy.setFilterRegExp(filter);
setupFilter(&proxy, filter);
QModelIndex startIndex = proxy.index(proxyStartRow, 0);
QModelIndexList indexes = proxy.match(startIndex, Qt::DisplayRole, what,
@ -2508,7 +2422,8 @@ void tst_QSortFilterProxyModel::filterOutParentAndFilterInChild()
QSortFilterProxyModel proxy;
proxy.setSourceModel(&model);
proxy.setFilterRegExp("A|B");
setupFilter(&proxy, QLatin1String("A|B"));
QStandardItem *itemA = new QStandardItem("A");
model.appendRow(itemA); // not filtered
QStandardItem *itemB = new QStandardItem("B");
@ -2522,7 +2437,7 @@ void tst_QSortFilterProxyModel::filterOutParentAndFilterInChild()
QVERIFY(removedSpy.isValid());
QVERIFY(insertedSpy.isValid());
proxy.setFilterRegExp("C"); // A and B will be filtered out, C filtered in
setupFilter(&proxy, QLatin1String("C")); // A and B will be filtered out, C filtered in
// we should now have been notified that the subtree represented by itemA has been removed
QCOMPARE(removedSpy.count(), 1);
@ -2935,7 +2850,7 @@ void tst_QSortFilterProxyModel::hiddenChildren()
itemA->appendRow(itemB);
QStandardItem *itemC = new QStandardItem("C");
itemA->appendRow(itemC);
proxy.setFilterRegExp("VISIBLE");
setupFilter(&proxy, QLatin1String("VISIBLE"));
QCOMPARE(proxy.rowCount(QModelIndex()) , 1);
QPersistentModelIndex indexA = proxy.index(0,0);
@ -2962,7 +2877,8 @@ void tst_QSortFilterProxyModel::hiddenChildren()
QModelIndex indexC = proxy.index(0, 0, indexA);
QCOMPARE(proxy.data(indexC).toString(), QString::fromLatin1("C VISIBLE"));
proxy.setFilterRegExp("C");
setupFilter(&proxy, QLatin1String("C"));
QCOMPARE(proxy.rowCount(QModelIndex()), 0);
itemC->setText("invisible");
itemA->setText("AC");
@ -3284,7 +3200,8 @@ void tst_QSortFilterProxyModel::mapSelectionFromSource()
QSortFilterProxyModel proxy;
proxy.setDynamicSortFilter(true);
proxy.setFilterRegExp("d.*");
setupFilter(&proxy, QLatin1String("d.*"));
proxy.setSourceModel(&model);
// Only "delta" remains.
@ -3839,13 +3756,13 @@ void tst_QSortFilterProxyModel::moveSourceRows()
filterProxy.setDynamicSortFilter(true);
filterProxy.sort(0, Qt::AscendingOrder);
filterProxy.setSourceModel(&proxy);
filterProxy.setFilterRegExp("6"); // One of the parents
setupFilter(&filterProxy, QLatin1String("6")); // One of the parents
QSortFilterProxyModel filterBothProxy;
filterBothProxy.setDynamicSortFilter(true);
filterBothProxy.sort(0, Qt::AscendingOrder);
filterBothProxy.setSourceModel(&proxy);
filterBothProxy.setFilterRegExp("5"); // The parents are 6 and 3. This filters both out.
setupFilter(&filterBothProxy, QLatin1String("5")); // The parents are 6 and 3. This filters both out.
QSignalSpy modelBeforeSpy(&model, &DynamicTreeModel::rowsAboutToBeMoved);
QSignalSpy modelAfterSpy(&model, &DynamicTreeModel::rowsMoved);
@ -4243,7 +4160,7 @@ void tst_QSortFilterProxyModel::filterHint()
QSortFilterProxyModel proxy2;
proxy2.setSourceModel(&proxy1);
proxy2.setFilterRole(Qt::DisplayRole);
proxy2.setFilterRegExp("^[^ ]*$");
setupFilter(&proxy2, QLatin1String("^[^ ]*$"));
proxy2.setDynamicSortFilter(true);
QSignalSpy proxy1BeforeSpy(&proxy1, &QSortFilterProxyModel::layoutAboutToBeChanged);
@ -4371,8 +4288,7 @@ void tst_QSortFilterProxyModel::sourceLayoutChangeLeavesValidPersistentIndexes()
QSortFilterProxyModel proxy1;
proxy1.setSourceModel(&model);
Q_SET_OBJECT_NAME(proxy1);
proxy1.setFilterRegExp("1|2");
setupFilter(&proxy1, QLatin1String("1|2"));
// The current state of things:
// model proxy
@ -4419,7 +4335,7 @@ void tst_QSortFilterProxyModel::rowMoveLeavesValidPersistentIndexes()
proxy1.setSourceModel(&model);
Q_SET_OBJECT_NAME(proxy1);
proxy1.setFilterRegExp("1|2");
setupFilter(&proxy1, QLatin1String("1|2"));
auto item5 = model.match(model.index(0, 0), Qt::DisplayRole, "5", 1, Qt::MatchRecursive).first();
auto item3 = model.match(model.index(0, 0), Qt::DisplayRole, "3", 1, Qt::MatchRecursive).first();
@ -4653,7 +4569,7 @@ void tst_QSortFilterProxyModel::removeIntervals()
if (sortOrder != -1)
proxy.sort(0, static_cast<Qt::SortOrder>(sortOrder));
if (!filter.isEmpty())
proxy.setFilterRegExp(QRegExp(filter));
setupFilter(&proxy, filter);
(void)proxy.rowCount(QModelIndex()); // force mapping
@ -4746,5 +4662,4 @@ void tst_QSortFilterProxyModel::checkSetNewModel()
QCoreApplication::processEvents();
}
QTEST_MAIN(tst_QSortFilterProxyModel)
#include "tst_qsortfilterproxymodel.moc"

View File

@ -0,0 +1,183 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef TST_QSORTFILTERPROXYMODEL_H
#define TST_QSORTFILTERPROXYMODEL_H
#include <QtTest/QtTest>
#include "dynamictreemodel.h"
#include <QtCore/QCoreApplication>
#include <QtGui/QStandardItem>
#include <QtWidgets/QTreeView>
#include <QtWidgets/QTableView>
#include <qdebug.h>
typedef QList<int> IntList;
typedef QPair<int, int> IntPair;
typedef QList<IntPair> IntPairList;
enum class FilterType {
RegExp,
RegularExpression
};
Q_DECLARE_METATYPE(QList<QPersistentModelIndex>)
class tst_QSortFilterProxyModel : public QObject
{
Q_OBJECT
public:
tst_QSortFilterProxyModel();
public slots:
void initTestCase();
void cleanupTestCase();
void cleanup();
private slots:
void getSetCheck();
void sort_data();
void sort();
void sortHierarchy_data();
void sortHierarchy();
void insertRows_data();
void insertRows();
void prependRow();
void removeRows_data();
void removeRows();
void removeColumns_data();
void removeColumns();
void insertAfterSelect();
void removeAfterSelect();
void filter_data();
void filter();
void filterHierarchy_data();
void filterHierarchy();
void filterColumns_data();
void filterColumns();
void filterTable();
void filterCurrent();
void filter_qtbug30662();
void changeSourceLayout();
void changeSourceLayoutFilteredOut();
void removeSourceRows_data();
void removeSourceRows();
void insertSourceRows_data();
void insertSourceRows();
void changeFilter_data();
void changeFilter();
void changeSourceData_data();
void changeSourceData();
void changeSourceDataKeepsStableSorting_qtbug1548();
void changeSourceDataForwardsRoles_qtbug35440();
void resortingDoesNotBreakTreeModels();
void dynamicFilterWithoutSort();
void sortFilterRole();
void selectionFilteredOut();
void match_data();
void match();
void insertIntoChildrenlessItem();
void invalidateMappedChildren();
void insertRowIntoFilteredParent();
void filterOutParentAndFilterInChild();
void sourceInsertRows();
void sourceModelDeletion();
void sortColumnTracking1();
void sortColumnTracking2();
void sortStable();
void hiddenColumns();
void insertRowsSort();
void staticSorting();
void dynamicSorting();
void fetchMore();
void hiddenChildren();
void mapFromToSource();
void removeRowsRecursive();
void doubleProxySelectionSetSourceModel();
void appearsAndSort();
void unnecessaryDynamicSorting();
void unnecessaryMapCreation();
void resetInvalidate_data();
void resetInvalidate();
void testMultipleProxiesWithSelection();
void mapSelectionFromSource();
void testResetInternalData();
void filteredColumns();
void headerDataChanged();
void testParentLayoutChanged();
void moveSourceRows();
void hierarchyFilterInvalidation();
void simpleFilterInvalidation();
void chainedProxyModelRoleNames();
void noMapAfterSourceDelete();
void forwardDropApi();
void canDropMimeData();
void filterHint();
void sourceLayoutChangeLeavesValidPersistentIndexes();
void rowMoveLeavesValidPersistentIndexes();
void emitLayoutChangedOnlyIfSortingChanged_data();
void emitLayoutChangedOnlyIfSortingChanged();
void checkSetNewModel();
void removeIntervals_data();
void removeIntervals();
protected:
void buildHierarchy(const QStringList &data, QAbstractItemModel *model);
void checkHierarchy(const QStringList &data, const QAbstractItemModel *model);
void setupFilter(QSortFilterProxyModel *model, const QString& pattern);
protected:
FilterType m_filterType;
private:
QStandardItemModel *m_model;
QSortFilterProxyModel *m_proxy;
};
Q_DECLARE_METATYPE(QAbstractItemModel::LayoutChangeHint)
#endif // TST_QSORTFILTERPROXYMODEL_H

View File

@ -0,0 +1 @@
tst_qsortfilterproxymodel_regexp

View File

@ -0,0 +1,16 @@
CONFIG += testcase
TARGET = tst_qsortfilterproxymodel_regexp
QT += widgets testlib
mtdir = ../../../other/qabstractitemmodelutils
qsfpmdir = ../qsortfilterproxymodel_common
INCLUDEPATH += $$PWD/$${mtdir} $$PWD/$${qsfpmdir}
SOURCES += \
tst_qsortfilterproxymodel_regexp.cpp \
$${qsfpmdir}/tst_qsortfilterproxymodel.cpp \
$${mtdir}/dynamictreemodel.cpp
HEADERS += \
$${qsfpmdir}/tst_qsortfilterproxymodel.h \
$${mtdir}/dynamictreemodel.h

View File

@ -0,0 +1,59 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include "tst_qsortfilterproxymodel.h"
class tst_QSortFilterProxyModelRegExp : public tst_QSortFilterProxyModel
{
Q_OBJECT
public:
tst_QSortFilterProxyModelRegExp();
private slots:
void tst_invalid();
};
tst_QSortFilterProxyModelRegExp::tst_QSortFilterProxyModelRegExp() :
tst_QSortFilterProxyModel()
{
m_filterType = FilterType::RegExp;
}
void tst_QSortFilterProxyModelRegExp::tst_invalid()
{
const QLatin1String pattern("test");
QSortFilterProxyModel model;
model.setFilterRegExp(pattern);
QCOMPARE(model.filterRegExp(), QRegExp(pattern));
model.setFilterRegularExpression(pattern);
QCOMPARE(model.filterRegExp(), QRegExp());
}
QTEST_MAIN(tst_QSortFilterProxyModelRegExp)
#include "tst_qsortfilterproxymodel_regexp.moc"

View File

@ -0,0 +1 @@
tst_qsortfilterproxymodel_regularexpression

View File

@ -0,0 +1,16 @@
CONFIG += testcase
TARGET = tst_qsortfilterproxymodel_regularexpression
QT += widgets testlib
mtdir = ../../../other/qabstractitemmodelutils
qsfpmdir = ../qsortfilterproxymodel_common
INCLUDEPATH += $$PWD/$${mtdir} $${qsfpmdir}
SOURCES += \
tst_qsortfilterproxymodel_regularexpression.cpp \
$${qsfpmdir}/tst_qsortfilterproxymodel.cpp \
$${mtdir}/dynamictreemodel.cpp
HEADERS += \
$${qsfpmdir}/tst_qsortfilterproxymodel.h \
$${mtdir}/dynamictreemodel.h

View File

@ -0,0 +1,59 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include "tst_qsortfilterproxymodel.h"
class tst_QSortFilterProxyModelRegularExpression : public tst_QSortFilterProxyModel
{
Q_OBJECT
public:
tst_QSortFilterProxyModelRegularExpression();
private slots:
void tst_invalid();
};
tst_QSortFilterProxyModelRegularExpression::tst_QSortFilterProxyModelRegularExpression() :
tst_QSortFilterProxyModel()
{
m_filterType = FilterType::RegularExpression;
}
void tst_QSortFilterProxyModelRegularExpression::tst_invalid()
{
const QLatin1String pattern("test");
QSortFilterProxyModel model;
model.setFilterRegularExpression(pattern);
QCOMPARE(model.filterRegularExpression(), QRegularExpression(pattern));
model.setFilterRegExp(pattern);
QCOMPARE(model.filterRegularExpression(), QRegularExpression());
}
QTEST_MAIN(tst_QSortFilterProxyModelRegularExpression)
#include "tst_qsortfilterproxymodel_regularexpression.moc"