summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/itemmodels/qsortfilterproxymodel.cpp31
-rw-r--r--tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp126
2 files changed, 137 insertions, 20 deletions
diff --git a/src/corelib/itemmodels/qsortfilterproxymodel.cpp b/src/corelib/itemmodels/qsortfilterproxymodel.cpp
index b0ddfa879d..333152138e 100644
--- a/src/corelib/itemmodels/qsortfilterproxymodel.cpp
+++ b/src/corelib/itemmodels/qsortfilterproxymodel.cpp
@@ -171,6 +171,7 @@ public:
QRowsRemoval itemsBeingRemoved;
QModelIndexPairList saved_persistent_indexes;
+ QList<QPersistentModelIndex> saved_layoutChange_parents;
QHash<QModelIndex, Mapping *>::const_iterator create_mapping(
const QModelIndex &source_parent) const;
@@ -1331,23 +1332,23 @@ void QSortFilterProxyModelPrivate::_q_sourceLayoutAboutToBeChanged(const QList<Q
Q_UNUSED(hint); // We can't forward Hint because we might filter additional rows or columns
saved_persistent_indexes.clear();
- QList<QPersistentModelIndex> parents;
+ saved_layoutChange_parents.clear();
for (const QPersistentModelIndex &parent : sourceParents) {
if (!parent.isValid()) {
- parents << QPersistentModelIndex();
+ saved_layoutChange_parents << QPersistentModelIndex();
continue;
}
const QModelIndex mappedParent = q->mapFromSource(parent);
// Might be filtered out.
if (mappedParent.isValid())
- parents << mappedParent;
+ saved_layoutChange_parents << mappedParent;
}
// All parents filtered out.
- if (!sourceParents.isEmpty() && parents.isEmpty())
+ if (!sourceParents.isEmpty() && saved_layoutChange_parents.isEmpty())
return;
- emit q->layoutAboutToBeChanged(parents);
+ emit q->layoutAboutToBeChanged(saved_layoutChange_parents);
if (persistent.indexes.isEmpty())
return;
@@ -1359,6 +1360,9 @@ void QSortFilterProxyModelPrivate::_q_sourceLayoutChanged(const QList<QPersisten
Q_Q(QSortFilterProxyModel);
Q_UNUSED(hint); // We can't forward Hint because we might filter additional rows or columns
+ if (!sourceParents.isEmpty() && saved_layoutChange_parents.isEmpty())
+ return;
+
// Optimize: We only actually have to clear the mapping related to the contents of
// sourceParents, not everything.
qDeleteAll(source_index_mapping);
@@ -1373,21 +1377,8 @@ void QSortFilterProxyModelPrivate::_q_sourceLayoutChanged(const QList<QPersisten
source_index_mapping.clear();
}
- QList<QPersistentModelIndex> parents;
- for (const QPersistentModelIndex &parent : sourceParents) {
- if (!parent.isValid()) {
- parents << QPersistentModelIndex();
- continue;
- }
- const QModelIndex mappedParent = q->mapFromSource(parent);
- if (mappedParent.isValid())
- parents << mappedParent;
- }
-
- if (!sourceParents.isEmpty() && parents.isEmpty())
- return;
-
- emit q->layoutChanged(parents);
+ emit q->layoutChanged(saved_layoutChange_parents);
+ saved_layoutChange_parents.clear();
}
void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeInserted(
diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp b/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp
index 38e3c6890d..6b98d9f4a3 100644
--- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp
+++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp
@@ -145,6 +145,8 @@ private slots:
void canDropMimeData();
void filterHint();
+ void sourceLayoutChangeLeavesValidPersistentIndexes();
+
protected:
void buildHierarchy(const QStringList &data, QAbstractItemModel *model);
void checkHierarchy(const QStringList &data, const QAbstractItemModel *model);
@@ -4181,5 +4183,129 @@ void tst_QSortFilterProxyModel::filterHint()
QAbstractItemModel::NoLayoutChangeHint);
}
+/**
+
+ Creates a model where each item has one child, to a set depth,
+ and the last item has no children. For a model created with
+ setDepth(4):
+
+ - 1
+ - - 2
+ - - - 3
+ - - - - 4
+*/
+class StepTreeModel : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ StepTreeModel(QObject * parent = 0)
+ : QAbstractItemModel(parent), m_depth(0) {}
+
+ int columnCount(const QModelIndex& = QModelIndex()) const override { return 1; }
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override
+ {
+ quintptr parentId = (parent.isValid()) ? parent.internalId() : 0;
+ return (parentId < m_depth) ? 1 : 0;
+ }
+
+ QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override
+ {
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ return QString::number(index.internalId());
+ }
+
+ QModelIndex index(int, int, const QModelIndex& parent = QModelIndex()) const override
+ {
+ quintptr parentId = (parent.isValid()) ? parent.internalId() : 0;
+ if (parentId >= m_depth)
+ return QModelIndex();
+
+ return createIndex(0, 0, parentId + 1);
+ }
+
+ QModelIndex parent(const QModelIndex& index) const override
+ {
+ if (index.internalId() == 0)
+ return QModelIndex();
+
+ return createIndex(0, 0, index.internalId() - 1);
+ }
+
+ void setDepth(quintptr depth)
+ {
+ int parentIdWithLayoutChange = (m_depth < depth) ? m_depth : depth;
+
+ QList<QPersistentModelIndex> parentsOfLayoutChange;
+ parentsOfLayoutChange.push_back(createIndex(0, 0, parentIdWithLayoutChange));
+
+ layoutAboutToBeChanged(parentsOfLayoutChange);
+
+ auto existing = persistentIndexList();
+
+ QList<QModelIndex> updated;
+
+ for (auto idx : existing) {
+ if (indexDepth(idx) <= depth)
+ updated.push_back(idx);
+ else
+ updated.push_back({});
+ }
+
+ m_depth = depth;
+
+ changePersistentIndexList(existing, updated);
+
+ layoutChanged(parentsOfLayoutChange);
+ }
+
+private:
+ static quintptr indexDepth(QModelIndex const& index)
+ {
+ return (index.isValid()) ? 1 + indexDepth(index.parent()) : 0;
+ }
+
+private:
+ quintptr m_depth;
+};
+
+void tst_QSortFilterProxyModel::sourceLayoutChangeLeavesValidPersistentIndexes()
+{
+ StepTreeModel model;
+ Q_SET_OBJECT_NAME(model);
+ model.setDepth(4);
+
+ QSortFilterProxyModel proxy1;
+ proxy1.setSourceModel(&model);
+ Q_SET_OBJECT_NAME(proxy1);
+
+ proxy1.setFilterRegExp("1|2");
+
+ // The current state of things:
+ // model proxy
+ // - 1 - 1
+ // - - 2 - - 2
+ // - - - 3
+ // - - - - 4
+
+ // The setDepth call below removes '4' with a layoutChanged call.
+ // Because the proxy filters that out anyway, the proxy doesn't need
+ // to emit any signals or update persistent indexes.
+
+ QPersistentModelIndex persistentIndex = proxy1.index(0, 0, proxy1.index(0, 0));
+
+ model.setDepth(3);
+
+ // Calling parent() causes the internalPointer to be used.
+ // Before fixing QTBUG-47711, that could be a dangling pointer.
+ // The use of qDebug here makes sufficient use of the heap to
+ // cause corruption at runtime with normal use on linux (before
+ // the fix). valgrind confirms the fix.
+ qDebug() << persistentIndex.parent();
+ QVERIFY(persistentIndex.parent().isValid());
+}
+
QTEST_MAIN(tst_QSortFilterProxyModel)
#include "tst_qsortfilterproxymodel.moc"