diff options
-rw-r--r-- | src/corelib/itemmodels/qsortfilterproxymodel.cpp | 31 | ||||
-rw-r--r-- | tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp | 126 |
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" |