Add support for recursive filtering in QSFPM
You can now use the recursiveFiltering property to recurse into children to find potential matching children to filter in. Change-Id: I411a2fb29489fd56b9c881b3e6b8d1860cce630c Reviewed-by: Stephen Kelly <steveire@gmail.com>
This commit is contained in:
parent
29bcbeab90
commit
7f1a220ee4
@ -56,6 +56,15 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
typedef QVector<QPair<QModelIndex, QPersistentModelIndex> > QModelIndexPairList;
|
||||
|
||||
struct QSortFilterProxyModelDataChanged
|
||||
{
|
||||
QSortFilterProxyModelDataChanged(const QModelIndex &tl, const QModelIndex &br)
|
||||
: topLeft(tl), bottomRight(br) { }
|
||||
|
||||
QModelIndex topLeft;
|
||||
QModelIndex bottomRight;
|
||||
};
|
||||
|
||||
static inline QSet<int> qVectorToSet(const QVector<int> &vector)
|
||||
{
|
||||
QSet<int> set;
|
||||
@ -164,9 +173,12 @@ public:
|
||||
bool sort_localeaware;
|
||||
|
||||
int filter_column;
|
||||
QRegExp filter_regexp;
|
||||
int filter_role;
|
||||
QRegExp filter_regexp;
|
||||
QModelIndex last_top_source;
|
||||
|
||||
bool filter_recursive;
|
||||
bool complete_insert;
|
||||
bool dynamic_sortfilter;
|
||||
QRowsRemoval itemsBeingRemoved;
|
||||
|
||||
@ -289,6 +301,9 @@ public:
|
||||
Qt::Orientation orient, int start, int end, int delta_item_count, bool remove);
|
||||
|
||||
virtual void _q_sourceModelDestroyed() Q_DECL_OVERRIDE;
|
||||
|
||||
bool filterAcceptsRowInternal(int source_row, const QModelIndex &source_parent) const;
|
||||
bool filterRecursiveAcceptsRow(int source_row, const QModelIndex &source_parent) const;
|
||||
};
|
||||
|
||||
typedef QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping *> IndexMap;
|
||||
@ -300,6 +315,32 @@ void QSortFilterProxyModelPrivate::_q_sourceModelDestroyed()
|
||||
source_index_mapping.clear();
|
||||
}
|
||||
|
||||
bool QSortFilterProxyModelPrivate::filterAcceptsRowInternal(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_Q(const QSortFilterProxyModel);
|
||||
return filter_recursive
|
||||
? filterRecursiveAcceptsRow(source_row, source_parent)
|
||||
: q->filterAcceptsRow(source_row, source_parent);
|
||||
}
|
||||
|
||||
bool QSortFilterProxyModelPrivate::filterRecursiveAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_Q(const QSortFilterProxyModel);
|
||||
|
||||
if (q->filterAcceptsRow(source_row, source_parent))
|
||||
return true;
|
||||
|
||||
const QModelIndex index = model->index(source_row, 0, source_parent);
|
||||
const int count = model->rowCount(index);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (filterRecursiveAcceptsRow(i, index))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void QSortFilterProxyModelPrivate::remove_from_mapping(const QModelIndex &source_parent)
|
||||
{
|
||||
if (Mapping *m = source_index_mapping.take(source_parent)) {
|
||||
@ -340,7 +381,7 @@ IndexMap::const_iterator QSortFilterProxyModelPrivate::create_mapping(
|
||||
int source_rows = model->rowCount(source_parent);
|
||||
m->source_rows.reserve(source_rows);
|
||||
for (int i = 0; i < source_rows; ++i) {
|
||||
if (q->filterAcceptsRow(i, source_parent))
|
||||
if (filterAcceptsRowInternal(i, source_parent))
|
||||
m->source_rows.append(i);
|
||||
}
|
||||
int source_cols = model->columnCount(source_parent);
|
||||
@ -794,7 +835,7 @@ void QSortFilterProxyModelPrivate::source_items_inserted(
|
||||
QVector<int> source_items;
|
||||
for (int i = start; i <= end; ++i) {
|
||||
if ((orient == Qt::Vertical)
|
||||
? q->filterAcceptsRow(i, source_parent)
|
||||
? filterAcceptsRowInternal(i, source_parent)
|
||||
: q->filterAcceptsColumn(i, source_parent)) {
|
||||
source_items.append(i);
|
||||
}
|
||||
@ -814,7 +855,7 @@ void QSortFilterProxyModelPrivate::source_items_inserted(
|
||||
orthogonal_source_to_proxy.resize(ortho_end);
|
||||
|
||||
for (int ortho_item = 0; ortho_item < ortho_end; ++ortho_item) {
|
||||
if ((orient == Qt::Horizontal) ? q->filterAcceptsRow(ortho_item, source_parent)
|
||||
if ((orient == Qt::Horizontal) ? filterAcceptsRowInternal(ortho_item, source_parent)
|
||||
: q->filterAcceptsColumn(ortho_item, source_parent)) {
|
||||
orthogonal_proxy_to_source.append(ortho_item);
|
||||
}
|
||||
@ -1125,7 +1166,7 @@ QSet<int> QSortFilterProxyModelPrivate::handle_filter_changed(
|
||||
for (int i = 0; i < proxy_to_source.count(); ++i) {
|
||||
const int source_item = proxy_to_source.at(i);
|
||||
if ((orient == Qt::Vertical)
|
||||
? !q->filterAcceptsRow(source_item, source_parent)
|
||||
? !filterAcceptsRowInternal(source_item, source_parent)
|
||||
: !q->filterAcceptsColumn(source_item, source_parent)) {
|
||||
// This source item does not satisfy the filter, so it must be removed
|
||||
source_items_remove.append(source_item);
|
||||
@ -1137,7 +1178,7 @@ QSet<int> QSortFilterProxyModelPrivate::handle_filter_changed(
|
||||
for (int source_item = 0; source_item < source_count; ++source_item) {
|
||||
if (source_to_proxy.at(source_item) == -1) {
|
||||
if ((orient == Qt::Vertical)
|
||||
? q->filterAcceptsRow(source_item, source_parent)
|
||||
? filterAcceptsRowInternal(source_item, source_parent)
|
||||
: q->filterAcceptsColumn(source_item, source_parent)) {
|
||||
// This source item satisfies the filter, so it must be added
|
||||
source_items_insert.append(source_item);
|
||||
@ -1163,11 +1204,29 @@ void QSortFilterProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &sourc
|
||||
Q_Q(QSortFilterProxyModel);
|
||||
if (!source_top_left.isValid() || !source_bottom_right.isValid())
|
||||
return;
|
||||
|
||||
std::vector<QSortFilterProxyModelDataChanged> data_changed_list;
|
||||
data_changed_list.emplace_back(source_top_left, source_bottom_right);
|
||||
|
||||
// Do check parents if the filter role have changed and we are recursive
|
||||
if (filter_recursive && (roles.isEmpty() || roles.contains(filter_role))) {
|
||||
QModelIndex source_parent = source_top_left.parent();
|
||||
|
||||
while (source_parent.isValid()) {
|
||||
data_changed_list.emplace_back(source_parent, source_parent);
|
||||
source_parent = source_parent.parent();
|
||||
}
|
||||
}
|
||||
|
||||
for (const QSortFilterProxyModelDataChanged &data_changed : data_changed_list) {
|
||||
const QModelIndex &source_top_left = data_changed.topLeft;
|
||||
const QModelIndex &source_bottom_right = data_changed.bottomRight;
|
||||
const QModelIndex source_parent = source_top_left.parent();
|
||||
|
||||
IndexMap::const_iterator it = source_index_mapping.constFind(source_parent);
|
||||
if (it == source_index_mapping.constEnd()) {
|
||||
// Don't care, since we don't have mapping for this index
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
Mapping *m = it.value();
|
||||
|
||||
@ -1180,7 +1239,7 @@ void QSortFilterProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &sourc
|
||||
for (int source_row = source_top_left.row(); source_row <= end; ++source_row) {
|
||||
if (dynamic_sortfilter) {
|
||||
if (m->proxy_rows.at(source_row) != -1) {
|
||||
if (!q->filterAcceptsRow(source_row, source_parent)) {
|
||||
if (!filterAcceptsRowInternal(source_row, source_parent)) {
|
||||
// This source row no longer satisfies the filter, so it must be removed
|
||||
source_rows_remove.append(source_row);
|
||||
} else if (source_sort_column >= source_top_left.column() && source_sort_column <= source_bottom_right.column()) {
|
||||
@ -1191,7 +1250,7 @@ void QSortFilterProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &sourc
|
||||
source_rows_change.append(source_row);
|
||||
}
|
||||
} else {
|
||||
if (!itemsBeingRemoved.contains(source_parent, source_row) && q->filterAcceptsRow(source_row, source_parent)) {
|
||||
if (!itemsBeingRemoved.contains(source_parent, source_row) && filterAcceptsRowInternal(source_row, source_parent)) {
|
||||
// This source row now satisfies the filter, so it must be added
|
||||
source_rows_insert.append(source_row);
|
||||
}
|
||||
@ -1265,6 +1324,7 @@ void QSortFilterProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &sourc
|
||||
source_rows_insert, source_parent, Qt::Vertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QSortFilterProxyModelPrivate::_q_sourceHeaderDataChanged(Qt::Orientation orientation,
|
||||
int start, int end)
|
||||
@ -1386,18 +1446,60 @@ void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeInserted(
|
||||
{
|
||||
Q_UNUSED(start);
|
||||
Q_UNUSED(end);
|
||||
|
||||
const bool toplevel = !source_parent.isValid();
|
||||
const bool recursive_accepted = filter_recursive && !toplevel && filterAcceptsRowInternal(source_parent.row(), source_parent.parent());
|
||||
//Force the creation of a mapping now, even if its empty.
|
||||
//We need it because the proxy can be acessed at the moment it emits rowsAboutToBeInserted in insert_source_items
|
||||
if (!filter_recursive || toplevel || recursive_accepted) {
|
||||
if (can_create_mapping(source_parent))
|
||||
create_mapping(source_parent);
|
||||
if (filter_recursive)
|
||||
complete_insert = true;
|
||||
} else {
|
||||
// The row could have been rejected or the parent might be not yet known... let's try to discover it
|
||||
QModelIndex top_source_parent = source_parent;
|
||||
QModelIndex parent = source_parent.parent();
|
||||
QModelIndex grandParent = parent.parent();
|
||||
|
||||
while (parent.isValid() && !filterAcceptsRowInternal(parent.row(), grandParent)) {
|
||||
top_source_parent = parent;
|
||||
parent = grandParent;
|
||||
grandParent = parent.parent();
|
||||
}
|
||||
|
||||
last_top_source = top_source_parent;
|
||||
}
|
||||
}
|
||||
|
||||
void QSortFilterProxyModelPrivate::_q_sourceRowsInserted(
|
||||
const QModelIndex &source_parent, int start, int end)
|
||||
{
|
||||
if (!filter_recursive || complete_insert) {
|
||||
if (filter_recursive)
|
||||
complete_insert = false;
|
||||
source_items_inserted(source_parent, start, end, Qt::Vertical);
|
||||
if (update_source_sort_column() && dynamic_sortfilter) //previous call to update_source_sort_column may fail if the model has no column.
|
||||
sort(); // now it should succeed so we need to make sure to sort again
|
||||
return;
|
||||
}
|
||||
|
||||
if (filter_recursive) {
|
||||
bool accept = false;
|
||||
|
||||
for (int row = start; row <= end; ++row) {
|
||||
if (filterAcceptsRowInternal(row, source_parent)) {
|
||||
accept = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!accept) // the new rows have no descendants that match the filter, filter them out.
|
||||
return;
|
||||
|
||||
// last_top_source should now become visible
|
||||
_q_sourceDataChanged(last_top_source, last_top_source, QVector<int>());
|
||||
}
|
||||
}
|
||||
|
||||
void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeRemoved(
|
||||
@ -1413,6 +1515,27 @@ void QSortFilterProxyModelPrivate::_q_sourceRowsRemoved(
|
||||
{
|
||||
itemsBeingRemoved = QRowsRemoval();
|
||||
source_items_removed(source_parent, start, end, Qt::Vertical);
|
||||
|
||||
if (filter_recursive) {
|
||||
// Find out if removing this visible row means that some ascendant
|
||||
// row can now be hidden.
|
||||
// We go up until we find a row that should still be visible
|
||||
// and then make QSFPM re-evaluate the last one we saw before that, to hide it.
|
||||
|
||||
QModelIndex to_hide;
|
||||
QModelIndex source_ascendant = source_parent;
|
||||
|
||||
while (source_ascendant.isValid()) {
|
||||
if (filterAcceptsRowInternal(source_ascendant.row(), source_ascendant.parent()))
|
||||
break;
|
||||
|
||||
to_hide = source_ascendant;
|
||||
source_ascendant = source_ascendant.parent();
|
||||
}
|
||||
|
||||
if (to_hide.isValid())
|
||||
_q_sourceDataChanged(to_hide, to_hide, QVector<int>());
|
||||
}
|
||||
}
|
||||
|
||||
void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeMoved(
|
||||
@ -1685,7 +1808,9 @@ QSortFilterProxyModel::QSortFilterProxyModel(QObject *parent)
|
||||
d->sort_localeaware = false;
|
||||
d->filter_column = 0;
|
||||
d->filter_role = Qt::DisplayRole;
|
||||
d->filter_recursive = false;
|
||||
d->dynamic_sortfilter = true;
|
||||
d->complete_insert = false;
|
||||
connect(this, SIGNAL(modelReset()), this, SLOT(_q_clearMapping()));
|
||||
}
|
||||
|
||||
@ -2505,6 +2630,32 @@ void QSortFilterProxyModel::setFilterRole(int role)
|
||||
d->filter_changed();
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.9
|
||||
\property QSortFilterProxyModel::recursiveFiltering
|
||||
\brief whether the filter to be applied recursively on children, and for
|
||||
any matching child, its parents will be visible as well.
|
||||
|
||||
The default value is false.
|
||||
|
||||
\sa filterAcceptsRow()
|
||||
*/
|
||||
bool QSortFilterProxyModel::recursiveFiltering() const
|
||||
{
|
||||
Q_D(const QSortFilterProxyModel);
|
||||
return d->filter_recursive;
|
||||
}
|
||||
|
||||
void QSortFilterProxyModel::setRecursiveFiltering(bool recursive)
|
||||
{
|
||||
Q_D(QSortFilterProxyModel);
|
||||
if (d->filter_recursive == recursive)
|
||||
return;
|
||||
d->filter_about_to_be_changed();
|
||||
d->filter_recursive = recursive;
|
||||
d->filter_changed();
|
||||
}
|
||||
|
||||
/*!
|
||||
\obsolete
|
||||
|
||||
|
@ -67,6 +67,7 @@ class Q_CORE_EXPORT QSortFilterProxyModel : public QAbstractProxyModel
|
||||
Q_PROPERTY(bool isSortLocaleAware READ isSortLocaleAware WRITE setSortLocaleAware)
|
||||
Q_PROPERTY(int sortRole READ sortRole WRITE setSortRole)
|
||||
Q_PROPERTY(int filterRole READ filterRole WRITE setFilterRole)
|
||||
Q_PROPERTY(bool recursiveFiltering READ recursiveFiltering WRITE setRecursiveFiltering)
|
||||
|
||||
public:
|
||||
explicit QSortFilterProxyModel(QObject *parent = Q_NULLPTR);
|
||||
@ -107,6 +108,9 @@ public:
|
||||
int filterRole() const;
|
||||
void setFilterRole(int role);
|
||||
|
||||
bool recursiveFiltering() const;
|
||||
void setRecursiveFiltering(bool recursive);
|
||||
|
||||
public Q_SLOTS:
|
||||
void setFilterRegExp(const QString &pattern);
|
||||
void setFilterWildcard(const QString &pattern);
|
||||
|
@ -7,6 +7,7 @@ qtHaveModule(gui): SUBDIRS += \
|
||||
qabstractproxymodel \
|
||||
qidentityproxymodel \
|
||||
qitemselectionmodel \
|
||||
qsortfilterproxymodel_recursive \
|
||||
|
||||
qtHaveModule(widgets): SUBDIRS += \
|
||||
qitemmodel \
|
||||
|
1
tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/.gitignore
vendored
Normal file
1
tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
tst_qsortfilterproxymodel_recursive
|
@ -0,0 +1,8 @@
|
||||
CONFIG += testcase
|
||||
CONFIG += parallel_test
|
||||
TARGET = tst_qsortfilterproxymodel_recursive
|
||||
|
||||
QT += testlib
|
||||
|
||||
SOURCES += tst_qsortfilterproxymodel_recursive.cpp
|
||||
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
|
@ -0,0 +1,727 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, authors Filipe Azevedo <filipe.azevedo@kdab.com> and David Faure <david.faure@kdab.com>
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the test suite of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
** 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 http://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at http://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** As a special exception, The Qt Company gives you certain additional
|
||||
** rights. These rights are described in The Qt Company LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QTest>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include <QtCore/QSortFilterProxyModel>
|
||||
#include <QtGui/QStandardItem>
|
||||
|
||||
Q_DECLARE_METATYPE(QModelIndex)
|
||||
|
||||
class ModelSignalSpy : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ModelSignalSpy(QAbstractItemModel &model) {
|
||||
connect(&model, &QAbstractItemModel::rowsInserted, this, &ModelSignalSpy::onRowsInserted);
|
||||
connect(&model, &QAbstractItemModel::rowsRemoved, this, &ModelSignalSpy::onRowsRemoved);
|
||||
connect(&model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelSignalSpy::onRowsAboutToBeInserted);
|
||||
connect(&model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelSignalSpy::onRowsAboutToBeRemoved);
|
||||
connect(&model, &QAbstractItemModel::rowsMoved, this, &ModelSignalSpy::onRowsMoved);
|
||||
connect(&model, &QAbstractItemModel::dataChanged, this, &ModelSignalSpy::onDataChanged);
|
||||
connect(&model, &QAbstractItemModel::layoutChanged, this, &ModelSignalSpy::onLayoutChanged);
|
||||
connect(&model, &QAbstractItemModel::modelReset, this, &ModelSignalSpy::onModelReset);
|
||||
}
|
||||
|
||||
QStringList mSignals;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onRowsInserted(QModelIndex p, int start, int end) {
|
||||
mSignals << QLatin1String("rowsInserted(") + textForRowSpy(p, start, end) + ')';
|
||||
}
|
||||
void onRowsRemoved(QModelIndex p, int start, int end) {
|
||||
mSignals << QLatin1String("rowsRemoved(") + textForRowSpy(p, start, end) + ')';
|
||||
}
|
||||
void onRowsAboutToBeInserted(QModelIndex p, int start, int end) {
|
||||
mSignals << QLatin1String("rowsAboutToBeInserted(") + textForRowSpy(p, start, end) + ')';
|
||||
}
|
||||
void onRowsAboutToBeRemoved(QModelIndex p, int start, int end) {
|
||||
mSignals << QLatin1String("rowsAboutToBeRemoved(") + textForRowSpy(p, start, end) + ')';
|
||||
}
|
||||
void onRowsMoved(QModelIndex,int,int,QModelIndex,int) {
|
||||
mSignals << QStringLiteral("rowsMoved");
|
||||
}
|
||||
void onDataChanged(const QModelIndex &from, const QModelIndex& ) {
|
||||
mSignals << QStringLiteral("dataChanged(%1)").arg(from.data().toString());
|
||||
}
|
||||
void onLayoutChanged() {
|
||||
mSignals << QStringLiteral("layoutChanged");
|
||||
}
|
||||
void onModelReset() {
|
||||
mSignals << QStringLiteral("modelReset");
|
||||
}
|
||||
private:
|
||||
QString textForRowSpy(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
QString txt = parent.data().toString();
|
||||
if (!txt.isEmpty())
|
||||
txt += QLatin1Char('.');
|
||||
txt += QString::number(start+1);
|
||||
if (start != end)
|
||||
txt += QLatin1Char('-') + QString::number(end+1);
|
||||
return txt;
|
||||
}
|
||||
};
|
||||
|
||||
class TestModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TestModel(QAbstractItemModel *sourceModel)
|
||||
: QSortFilterProxyModel()
|
||||
{
|
||||
setRecursiveFiltering(true);
|
||||
setSourceModel(sourceModel);
|
||||
}
|
||||
|
||||
virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
|
||||
{
|
||||
return sourceModel()->index(sourceRow, 0, sourceParent).data(Qt::UserRole +1).toBool()
|
||||
&& QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
|
||||
}
|
||||
};
|
||||
|
||||
// Represents this tree
|
||||
// - A
|
||||
// - - B
|
||||
// - - - C
|
||||
// - - - D
|
||||
// - - E
|
||||
// as a single string, englobing children in brackets, like this:
|
||||
// [A[B[C D] E]]
|
||||
// In addition, items that match the filtering (data(UserRole+1) == true) have a * after their value.
|
||||
static QString treeAsString(const QAbstractItemModel &model, const QModelIndex &parent = QModelIndex())
|
||||
{
|
||||
QString ret;
|
||||
const int rowCount = model.rowCount(parent);
|
||||
if (rowCount > 0) {
|
||||
ret += QLatin1Char('[');
|
||||
for (int row = 0 ; row < rowCount; ++row) {
|
||||
if (row > 0) {
|
||||
ret += ' ';
|
||||
}
|
||||
const QModelIndex child = model.index(row, 0, parent);
|
||||
ret += child.data().toString();
|
||||
if (child.data(Qt::UserRole+1).toBool())
|
||||
ret += QLatin1Char('*');
|
||||
ret += treeAsString(model, child);
|
||||
}
|
||||
ret += QLatin1Char(']');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Fill a tree model based on a string representation (see treeAsString)
|
||||
static void fillModel(QStandardItemModel &model, const QString &str)
|
||||
{
|
||||
QCOMPARE(str.count('['), str.count(']'));
|
||||
QStandardItem *item = 0;
|
||||
QString data;
|
||||
for ( int i = 0 ; i < str.length() ; ++i ) {
|
||||
const QChar ch = str.at(i);
|
||||
if ((ch == '[' || ch == ']' || ch == ' ') && !data.isEmpty()) {
|
||||
if (data.endsWith('*')) {
|
||||
item->setData(true, Qt::UserRole + 1);
|
||||
data.chop(1);
|
||||
}
|
||||
item->setText(data);
|
||||
data.clear();
|
||||
}
|
||||
if (ch == '[') {
|
||||
// Create new child
|
||||
QStandardItem *child = new QStandardItem;
|
||||
if (item)
|
||||
item->appendRow(child);
|
||||
else
|
||||
model.appendRow(child);
|
||||
item = child;
|
||||
} else if (ch == ']') {
|
||||
// Go up to parent
|
||||
item = item->parent();
|
||||
} else if (ch == ' ') {
|
||||
// Create new sibling
|
||||
QStandardItem *child = new QStandardItem;
|
||||
QStandardItem *parent = item->parent();
|
||||
if (parent)
|
||||
parent->appendRow(child);
|
||||
else
|
||||
model.appendRow(child);
|
||||
item = child;
|
||||
} else {
|
||||
data += ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class tst_QSortFilterProxyModel_Recursive : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
private Q_SLOTS:
|
||||
void testInitialFiltering_data()
|
||||
{
|
||||
QTest::addColumn<QString>("sourceStr");
|
||||
QTest::addColumn<QString>("proxyStr");
|
||||
|
||||
QTest::newRow("empty") << "[]" << "";
|
||||
QTest::newRow("no") << "[1]" << "";
|
||||
QTest::newRow("yes") << "[1*]" << "[1*]";
|
||||
QTest::newRow("second") << "[1 2*]" << "[2*]";
|
||||
QTest::newRow("child_yes") << "[1 2[2.1*]]" << "[2[2.1*]]";
|
||||
QTest::newRow("grandchild_yes") << "[1 2[2.1[2.1.1*]]]" << "[2[2.1[2.1.1*]]]";
|
||||
// 1, 3.1 and 4.2.1 match, so their parents are in the model
|
||||
QTest::newRow("more") << "[1* 2[2.1] 3[3.1*] 4[4.1 4.2[4.2.1*]]]" << "[1* 3[3.1*] 4[4.2[4.2.1*]]]";
|
||||
}
|
||||
|
||||
void testInitialFiltering()
|
||||
{
|
||||
QFETCH(QString, sourceStr);
|
||||
QFETCH(QString, proxyStr);
|
||||
|
||||
QStandardItemModel model;
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), proxyStr);
|
||||
}
|
||||
|
||||
// Test changing a role that is unrelated to the filtering.
|
||||
void testUnrelatedDataChange()
|
||||
{
|
||||
QStandardItemModel model;
|
||||
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]");
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), sourceStr);
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0);
|
||||
|
||||
// When changing the text on the item
|
||||
item_1_1_1->setText(QStringLiteral("ME"));
|
||||
|
||||
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[ME*]]]"));
|
||||
|
||||
QCOMPARE(spy.mSignals, QStringList()
|
||||
<< QStringLiteral("dataChanged(ME)")
|
||||
<< QStringLiteral("dataChanged(1.1)")
|
||||
<< QStringLiteral("dataChanged(1)"));
|
||||
}
|
||||
|
||||
// Test changing a role that is unrelated to the filtering, in a hidden item.
|
||||
void testHiddenDataChange()
|
||||
{
|
||||
QStandardItemModel model;
|
||||
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]");
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), QString());
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0);
|
||||
|
||||
// When changing the text on a hidden item
|
||||
item_1_1_1->setText(QStringLiteral("ME"));
|
||||
|
||||
QCOMPARE(treeAsString(proxy), QString());
|
||||
QCOMPARE(spy.mSignals, QStringList());
|
||||
}
|
||||
|
||||
// Test that we properly react to a data-changed signal in a descendant and include all required rows
|
||||
void testDataChangeIn_data()
|
||||
{
|
||||
QTest::addColumn<QString>("sourceStr");
|
||||
QTest::addColumn<QString>("initialProxyStr");
|
||||
QTest::addColumn<QString>("add"); // set the flag on this item
|
||||
QTest::addColumn<QString>("expectedProxyStr");
|
||||
QTest::addColumn<QStringList>("expectedSignals");
|
||||
|
||||
QTest::newRow("toplevel") << "[1]" << "" << "1" << "[1*]"
|
||||
<< (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)"));
|
||||
QTest::newRow("show_parents") << "[1[1.1[1.1.1]]]" << "" << "1.1.1" << "[1[1.1[1.1.1*]]]"
|
||||
<< (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)"));
|
||||
|
||||
const QStringList insert_1_1_1 = QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeInserted(1.1.1)")
|
||||
<< QStringLiteral("rowsInserted(1.1.1)")
|
||||
<< QStringLiteral("dataChanged(1.1)")
|
||||
<< QStringLiteral("dataChanged(1)")
|
||||
;
|
||||
QTest::newRow("parent_visible") << "[1[1.1*[1.1.1]]]" << "[1[1.1*]]" << "1.1.1" << "[1[1.1*[1.1.1*]]]"
|
||||
<< insert_1_1_1;
|
||||
|
||||
QTest::newRow("sibling_visible") << "[1[1.1[1.1.1 1.1.2*]]]" << "[1[1.1[1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.1* 1.1.2*]]]"
|
||||
<< insert_1_1_1;
|
||||
|
||||
QTest::newRow("visible_cousin") << "[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]"
|
||||
<< insert_1_1_1;
|
||||
|
||||
QTest::newRow("show_parent") << "[1[1.1[1.1.1 1.1.2] 1.2*]]" << "[1[1.2*]]" << "1.1.1" << "[1[1.1[1.1.1*] 1.2*]]"
|
||||
<< (QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeInserted(1.1)")
|
||||
<< QStringLiteral("rowsInserted(1.1)")
|
||||
<< QStringLiteral("dataChanged(1)"));
|
||||
|
||||
QTest::newRow("with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "1.1.1" << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]"
|
||||
<< (QStringList()
|
||||
<< QStringLiteral("dataChanged(1.1.1)")
|
||||
<< QStringLiteral("dataChanged(1.1)")
|
||||
<< QStringLiteral("dataChanged(1)"));
|
||||
|
||||
}
|
||||
|
||||
void testDataChangeIn()
|
||||
{
|
||||
QFETCH(QString, sourceStr);
|
||||
QFETCH(QString, initialProxyStr);
|
||||
QFETCH(QString, add);
|
||||
QFETCH(QString, expectedProxyStr);
|
||||
QFETCH(QStringList, expectedSignals);
|
||||
|
||||
QStandardItemModel model;
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), initialProxyStr);
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
// When changing the data on the designated item to show this row
|
||||
QStandardItem *itemToChange = itemByText(model, add);
|
||||
QVERIFY(!itemToChange->data().toBool());
|
||||
itemToChange->setData(true);
|
||||
|
||||
// The proxy should update as expected
|
||||
QCOMPARE(treeAsString(proxy), expectedProxyStr);
|
||||
|
||||
//qDebug() << spy.mSignals;
|
||||
QCOMPARE(spy.mSignals, expectedSignals);
|
||||
}
|
||||
|
||||
void testDataChangeOut_data()
|
||||
{
|
||||
QTest::addColumn<QString>("sourceStr");
|
||||
QTest::addColumn<QString>("initialProxyStr");
|
||||
QTest::addColumn<QString>("remove"); // unset the flag on this item
|
||||
QTest::addColumn<QString>("expectedProxyStr");
|
||||
QTest::addColumn<QStringList>("expectedSignals");
|
||||
|
||||
const QStringList remove1_1_1 = (QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1.1)")
|
||||
<< QStringLiteral("dataChanged(1.1)")
|
||||
<< QStringLiteral("dataChanged(1)"));
|
||||
|
||||
QTest::newRow("toplevel") << "[1*]" << "[1*]" << "1" << ""
|
||||
<< (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)"));
|
||||
|
||||
QTest::newRow("hide_parent") << "[1[1.1[1.1.1*]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << "" <<
|
||||
(QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1)")
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1)")
|
||||
<< QStringLiteral("rowsRemoved(1)"));
|
||||
|
||||
QTest::newRow("parent_visible") << "[1[1.1*[1.1.1*]]]" << "[1[1.1*[1.1.1*]]]" << "1.1.1" << "[1[1.1*]]"
|
||||
<< remove1_1_1;
|
||||
|
||||
QTest::newRow("visible") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.2*]]]"
|
||||
<< remove1_1_1;
|
||||
QTest::newRow("visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.2[1.1.2.1*]]]]"
|
||||
<< remove1_1_1;
|
||||
|
||||
// The following tests trigger the removal of an ascendant.
|
||||
QTest::newRow("remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" << "[1[1.1[1.1.1*] 1.2*]]" << "1.1.1" << "[1[1.2*]]"
|
||||
<< (QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1)")
|
||||
<< QStringLiteral("dataChanged(1)"));
|
||||
|
||||
QTest::newRow("with_children") << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" << "1.1.1" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]"
|
||||
<< (QStringList()
|
||||
<< QStringLiteral("dataChanged(1.1.1)")
|
||||
<< QStringLiteral("dataChanged(1.1)")
|
||||
<< QStringLiteral("dataChanged(1)"));
|
||||
|
||||
QTest::newRow("last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << ""
|
||||
<< (QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1)")
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1)")
|
||||
<< QStringLiteral("rowsRemoved(1)"));
|
||||
|
||||
}
|
||||
|
||||
void testDataChangeOut()
|
||||
{
|
||||
QFETCH(QString, sourceStr);
|
||||
QFETCH(QString, initialProxyStr);
|
||||
QFETCH(QString, remove);
|
||||
QFETCH(QString, expectedProxyStr);
|
||||
QFETCH(QStringList, expectedSignals);
|
||||
|
||||
QStandardItemModel model;
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), initialProxyStr);
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
|
||||
// When changing the data on the designated item to exclude this row again
|
||||
QStandardItem *itemToChange = itemByText(model, remove);
|
||||
QVERIFY(itemToChange->data().toBool());
|
||||
itemToChange->setData(false);
|
||||
|
||||
// The proxy should update as expected
|
||||
QCOMPARE(treeAsString(proxy), expectedProxyStr);
|
||||
|
||||
//qDebug() << spy.mSignals;
|
||||
QCOMPARE(spy.mSignals, expectedSignals);
|
||||
}
|
||||
|
||||
void testInsert()
|
||||
{
|
||||
QStandardItemModel model;
|
||||
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]");
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), QString());
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0);
|
||||
QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
|
||||
item_1_1_1_1->setData(true);
|
||||
item_1_1_1->appendRow(item_1_1_1_1);
|
||||
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]"));
|
||||
|
||||
QCOMPARE(spy.mSignals, QStringList() << QStringLiteral("rowsAboutToBeInserted(1)")
|
||||
<< QStringLiteral("rowsInserted(1)"));
|
||||
}
|
||||
|
||||
// Start from [1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]
|
||||
// where 1.1.1 is hidden but 1.1 is shown, we want to insert a shown child in 1.1.1.
|
||||
// The proxy ensures dataChanged is called on 1.1,
|
||||
// so that 1.1.1 and 1.1.1.1 are included in the model.
|
||||
void testInsertCousin()
|
||||
{
|
||||
QStandardItemModel model;
|
||||
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]");
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.2[1.1.2.1*]]]]"));
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
{
|
||||
QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
|
||||
item_1_1_1_1->setData(true);
|
||||
QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0);
|
||||
item_1_1_1->appendRow(item_1_1_1_1);
|
||||
}
|
||||
|
||||
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*] 1.1.2[1.1.2.1*]]]]"));
|
||||
//qDebug() << spy.mSignals;
|
||||
QCOMPARE(spy.mSignals, QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeInserted(1.1.1)")
|
||||
<< QStringLiteral("rowsInserted(1.1.1)")
|
||||
<< QStringLiteral("dataChanged(1.1)")
|
||||
<< QStringLiteral("dataChanged(1)"));
|
||||
}
|
||||
|
||||
void testInsertWithChildren()
|
||||
{
|
||||
QStandardItemModel model;
|
||||
const QString sourceStr = QStringLiteral("[1[1.1]]");
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), QString());
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
{
|
||||
QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1"));
|
||||
QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
|
||||
item_1_1_1_1->setData(true);
|
||||
item_1_1_1->appendRow(item_1_1_1_1);
|
||||
|
||||
QStandardItem *item_1_1 = model.item(0)->child(0);
|
||||
item_1_1->appendRow(item_1_1_1);
|
||||
}
|
||||
|
||||
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]"));
|
||||
QCOMPARE(spy.mSignals, QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeInserted(1)")
|
||||
<< QStringLiteral("rowsInserted(1)"));
|
||||
}
|
||||
|
||||
void testInsertIntoVisibleWithChildren()
|
||||
{
|
||||
QStandardItemModel model;
|
||||
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]");
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), sourceStr);
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
{
|
||||
QStandardItem *item_1_1_2 = new QStandardItem(QStringLiteral("1.1.2"));
|
||||
QStandardItem *item_1_1_2_1 = new QStandardItem(QStringLiteral("1.1.2.1"));
|
||||
item_1_1_2_1->setData(true);
|
||||
item_1_1_2->appendRow(item_1_1_2_1);
|
||||
|
||||
QStandardItem *item_1_1 = model.item(0)->child(0);
|
||||
item_1_1->appendRow(item_1_1_2);
|
||||
}
|
||||
|
||||
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]"));
|
||||
QCOMPARE(spy.mSignals, QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeInserted(1.1.2)")
|
||||
<< QStringLiteral("rowsInserted(1.1.2)"));
|
||||
}
|
||||
|
||||
void testInsertBefore()
|
||||
{
|
||||
QStandardItemModel model;
|
||||
const QString sourceStr = "[1[1.1[1.1.2*]]]";
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), sourceStr);
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
{
|
||||
QStandardItem *item_1_1_1 = new QStandardItem("1.1.1");
|
||||
|
||||
QStandardItem *item_1_1 = model.item(0)->child(0);
|
||||
item_1_1->insertRow(0, item_1_1_1);
|
||||
}
|
||||
|
||||
QCOMPARE(treeAsString(proxy), QString("[1[1.1[1.1.2*]]]"));
|
||||
QCOMPARE(spy.mSignals, QStringList());
|
||||
}
|
||||
|
||||
void testInsertHidden() // inserting filtered-out rows shouldn't emit anything
|
||||
{
|
||||
QStandardItemModel model;
|
||||
const QString sourceStr = QStringLiteral("[1[1.1]]");
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), QString());
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
{
|
||||
QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1"));
|
||||
QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
|
||||
item_1_1_1->appendRow(item_1_1_1_1);
|
||||
|
||||
QStandardItem *item_1_1 = model.item(0)->child(0);
|
||||
item_1_1->appendRow(item_1_1_1);
|
||||
}
|
||||
|
||||
QCOMPARE(treeAsString(proxy), QString());
|
||||
QCOMPARE(spy.mSignals, QStringList());
|
||||
}
|
||||
|
||||
void testConsecutiveInserts_data()
|
||||
{
|
||||
testInitialFiltering_data();
|
||||
}
|
||||
|
||||
void testConsecutiveInserts()
|
||||
{
|
||||
QFETCH(QString, sourceStr);
|
||||
QFETCH(QString, proxyStr);
|
||||
|
||||
QStandardItemModel model;
|
||||
TestModel proxy(&model); // this time the proxy listens to the model while we fill it
|
||||
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
QCOMPARE(treeAsString(proxy), proxyStr);
|
||||
}
|
||||
|
||||
void testRemove_data()
|
||||
{
|
||||
QTest::addColumn<QString>("sourceStr");
|
||||
QTest::addColumn<QString>("initialProxyStr");
|
||||
QTest::addColumn<QString>("remove"); // remove this item
|
||||
QTest::addColumn<QString>("expectedProxyStr");
|
||||
QTest::addColumn<QStringList>("expectedSignals");
|
||||
|
||||
const QStringList remove1_1_1 = (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)"));
|
||||
|
||||
QTest::newRow("toplevel") << "[1* 2* 3*]" << "[1* 2* 3*]" << "1" << "[2* 3*]"
|
||||
<< (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)"));
|
||||
|
||||
QTest::newRow("remove_hidden") << "[1 2* 3*]" << "[2* 3*]" << "1" << "[2* 3*]" << QStringList();
|
||||
|
||||
QTest::newRow("parent_hidden") << "[1[1.1[1.1.1]]]" << "" << "1.1.1" << "" << QStringList();
|
||||
|
||||
QTest::newRow("child_hidden") << "[1[1.1*[1.1.1]]]" << "[1[1.1*]]" << "1.1.1" << "[1[1.1*]]" << QStringList();
|
||||
|
||||
QTest::newRow("parent_visible") << "[1[1.1*[1.1.1*]]]" << "[1[1.1*[1.1.1*]]]" << "1.1.1" << "[1[1.1*]]"
|
||||
<< remove1_1_1;
|
||||
|
||||
QTest::newRow("visible") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.2*]]]"
|
||||
<< remove1_1_1;
|
||||
QTest::newRow("visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.2[1.1.2.1*]]]]"
|
||||
<< remove1_1_1;
|
||||
|
||||
// The following tests trigger the removal of an ascendant.
|
||||
// We could optimize the rows{AboutToBe,}Removed(1.1.1) away...
|
||||
|
||||
QTest::newRow("remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" << "[1[1.1[1.1.1*] 1.2*]]" << "1.1.1" << "[1[1.2*]]"
|
||||
<< (QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1)")
|
||||
<< QStringLiteral("dataChanged(1)"));
|
||||
|
||||
QTest::newRow("with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "1.1.1" << "[2*]"
|
||||
<< (QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1)")
|
||||
<< QStringLiteral("rowsRemoved(1)"));
|
||||
|
||||
QTest::newRow("last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << ""
|
||||
<< (QStringList()
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsRemoved(1.1.1)")
|
||||
<< QStringLiteral("rowsAboutToBeRemoved(1)")
|
||||
<< QStringLiteral("rowsRemoved(1)"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
void testRemove()
|
||||
{
|
||||
QFETCH(QString, sourceStr);
|
||||
QFETCH(QString, initialProxyStr);
|
||||
QFETCH(QString, remove);
|
||||
QFETCH(QString, expectedProxyStr);
|
||||
QFETCH(QStringList, expectedSignals);
|
||||
|
||||
QStandardItemModel model;
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), initialProxyStr);
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
QStandardItem *itemToRemove = itemByText(model, remove);
|
||||
QVERIFY(itemToRemove);
|
||||
if (itemToRemove->parent())
|
||||
itemToRemove->parent()->removeRow(itemToRemove->row());
|
||||
else
|
||||
model.removeRow(itemToRemove->row());
|
||||
QCOMPARE(treeAsString(proxy), expectedProxyStr);
|
||||
|
||||
//qDebug() << spy.mSignals;
|
||||
QCOMPARE(spy.mSignals, expectedSignals);
|
||||
}
|
||||
|
||||
void testStandardFiltering_data()
|
||||
{
|
||||
QTest::addColumn<QString>("sourceStr");
|
||||
QTest::addColumn<QString>("initialProxyStr");
|
||||
QTest::addColumn<QString>("filter");
|
||||
QTest::addColumn<QString>("expectedProxyStr");
|
||||
|
||||
QTest::newRow("select_child") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]"
|
||||
<< "1.1.2" << "[1[1.1[1.1.2*]]]";
|
||||
|
||||
QTest::newRow("filter_all_out") << "[1[1.1[1.1.1*]]]" << "[1[1.1[1.1.1*]]]"
|
||||
<< "test" << "";
|
||||
|
||||
QTest::newRow("select_parent") << "[1[1.1[1.1.1*[child*] 1.1.2*]]]" << "[1[1.1[1.1.1*[child*] 1.1.2*]]]"
|
||||
<< "1.1.1" << "[1[1.1[1.1.1*]]]";
|
||||
|
||||
}
|
||||
|
||||
void testStandardFiltering()
|
||||
{
|
||||
QFETCH(QString, sourceStr);
|
||||
QFETCH(QString, initialProxyStr);
|
||||
QFETCH(QString, filter);
|
||||
QFETCH(QString, expectedProxyStr);
|
||||
|
||||
QStandardItemModel model;
|
||||
fillModel(model, sourceStr);
|
||||
QCOMPARE(treeAsString(model), sourceStr);
|
||||
|
||||
TestModel proxy(&model);
|
||||
QCOMPARE(treeAsString(proxy), initialProxyStr);
|
||||
|
||||
ModelSignalSpy spy(proxy);
|
||||
|
||||
//qDebug() << "setFilterFixedString";
|
||||
proxy.setFilterFixedString(filter);
|
||||
|
||||
QCOMPARE(treeAsString(proxy), expectedProxyStr);
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
QStandardItem *itemByText(const QStandardItemModel& model, const QString &text) const {
|
||||
QModelIndexList list = model.match(model.index(0, 0), Qt::DisplayRole, text, 1, Qt::MatchRecursive);
|
||||
return list.isEmpty() ? 0 : model.itemFromIndex(list.first());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(tst_QSortFilterProxyModel_Recursive)
|
||||
#include "tst_qsortfilterproxymodel_recursive.moc"
|
Loading…
Reference in New Issue
Block a user