diff --git a/src/corelib/kernel/qabstractitemmodel.cpp b/src/corelib/kernel/qabstractitemmodel.cpp index fbeb59a553..cbbb20a8c9 100644 --- a/src/corelib/kernel/qabstractitemmodel.cpp +++ b/src/corelib/kernel/qabstractitemmodel.cpp @@ -1306,7 +1306,7 @@ void QAbstractItemModelPrivate::columnsRemoved(const QModelIndex &parent, */ /*! - \fn void QAbstractItemModel::layoutAboutToBeChanged() + \fn void QAbstractItemModel::layoutAboutToBeChanged(const QList &parents = QList()) \since 4.2 This signal is emitted just before the layout of a model is changed. @@ -1316,11 +1316,15 @@ void QAbstractItemModelPrivate::columnsRemoved(const QModelIndex &parent, Subclasses should update any persistent model indexes after emitting layoutAboutToBeChanged(). + The optional @p parents parameter is used to give a more specific notification + about what parts of the layout of the model are changing. An empty list indicates + a change to the layout of the entire model. + \sa layoutChanged(), changePersistentIndex() */ /*! - \fn void QAbstractItemModel::layoutChanged() + \fn void QAbstractItemModel::layoutChanged(const QList &parents = QList()) This signal is emitted whenever the layout of items exposed by the model has changed; for example, when the model has been sorted. When this signal @@ -1332,6 +1336,10 @@ void QAbstractItemModelPrivate::columnsRemoved(const QModelIndex &parent, altering the structure of the data you expose to views, and emit layoutChanged() after changing the layout. + The optional @p parents parameter is used to give a more specific notification + about what parts of the layout of the model are changing. An empty list indicates + a change to the layout of the entire model. + Subclasses should update any persistent model indexes before emitting layoutChanged(). In other words, when the structure changes: diff --git a/src/corelib/kernel/qabstractitemmodel.h b/src/corelib/kernel/qabstractitemmodel.h index 97c5b58482..0aa8144602 100644 --- a/src/corelib/kernel/qabstractitemmodel.h +++ b/src/corelib/kernel/qabstractitemmodel.h @@ -233,8 +233,8 @@ public: Q_SIGNALS: void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QSet &roles = QSet()); void headerDataChanged(Qt::Orientation orientation, int first, int last); - void layoutChanged(); - void layoutAboutToBeChanged(); + void layoutChanged(const QList &parents = QList()); + void layoutAboutToBeChanged(const QList &parents = QList()); #if !defined(Q_MOC_RUN) && !defined(qdoc) private: // can only be emitted by QAbstractItemModel diff --git a/tests/auto/corelib/kernel/qabstractitemmodel/tst_qabstractitemmodel.cpp b/tests/auto/corelib/kernel/qabstractitemmodel/tst_qabstractitemmodel.cpp index 4b09b455ad..c704329717 100644 --- a/tests/auto/corelib/kernel/qabstractitemmodel/tst_qabstractitemmodel.cpp +++ b/tests/auto/corelib/kernel/qabstractitemmodel/tst_qabstractitemmodel.cpp @@ -112,6 +112,8 @@ private slots: void testDataChanged(); + void testChildrenLayoutsChanged(); + private: DynamicTreeModel *m_model; }; @@ -1841,6 +1843,176 @@ void tst_QAbstractItemModel::testDataChanged() QVERIFY(thirdRoles.contains(CustomRoleModel::Custom1)); } +Q_DECLARE_METATYPE(QList) + +class SignalArgumentChecker : public QObject +{ + Q_OBJECT +public: + SignalArgumentChecker(const QModelIndex &p1, const QModelIndex &p2, QObject *parent = 0) + : QObject(parent), m_p1(p1), m_p2(p2), m_p1Persistent(p1), m_p2Persistent(p2) + { + connect(p1.model(), SIGNAL(layoutAboutToBeChanged(QList)), SLOT(layoutAboutToBeChanged(QList))); + connect(p1.model(), SIGNAL(layoutChanged(QList)), SLOT(layoutChanged(QList))); + } + +private slots: + void layoutAboutToBeChanged(const QList &parents) + { + QCOMPARE(parents.size(), 2); + QVERIFY(parents.first() != parents.at(1)); + QVERIFY(parents.contains(m_p1)); + QVERIFY(parents.contains(m_p2)); + } + + void layoutChanged(const QList &parents) + { + QCOMPARE(parents.size(), 2); + QVERIFY(parents.first() != parents.at(1)); + QVERIFY(parents.contains(m_p1Persistent)); + QVERIFY(parents.contains(m_p2Persistent)); + QVERIFY(!parents.contains(m_p2)); // Has changed + } + +private: + QModelIndex m_p1; + QModelIndex m_p2; + QPersistentModelIndex m_p1Persistent; + QPersistentModelIndex m_p2Persistent; +}; + +void tst_QAbstractItemModel::testChildrenLayoutsChanged() +{ + DynamicTreeModel model; + + ModelInsertCommand *insertCommand = new ModelInsertCommand(&model, this); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + insertCommand->doCommand(); + + insertCommand = new ModelInsertCommand(&model, this); + insertCommand->setAncestorRowNumbers(QList() << 2); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + insertCommand->doCommand(); + + insertCommand = new ModelInsertCommand(&model, this); + insertCommand->setAncestorRowNumbers(QList() << 5); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + insertCommand->doCommand(); + + qRegisterMetaType >(); + + { + const QModelIndex p1 = model.index(2, 0); + const QModelIndex p2 = model.index(5, 0); + + const QPersistentModelIndex p1FirstPersistent = model.index(0, 0, p1); + const QPersistentModelIndex p1LastPersistent = model.index(9, 0, p1); + const QPersistentModelIndex p2FirstPersistent = model.index(0, 0, p2); + const QPersistentModelIndex p2LastPersistent = model.index(9, 0, p2); + + QVERIFY(p1.isValid()); + QVERIFY(p2.isValid()); + + QCOMPARE(model.rowCount(), 10); + QCOMPARE(model.rowCount(p1), 10); + QCOMPARE(model.rowCount(p2), 10); + + QSignalSpy beforeSpy(&model, SIGNAL(layoutAboutToBeChanged(QList))); + QSignalSpy afterSpy(&model, SIGNAL(layoutChanged(QList))); + + ModelChangeChildrenLayoutsCommand *changeCommand = new ModelChangeChildrenLayoutsCommand(&model, this); + changeCommand->setAncestorRowNumbers(QList() << 2); + changeCommand->setSecondAncestorRowNumbers(QList() << 5); + changeCommand->doCommand(); + + QCOMPARE(beforeSpy.size(), 1); + QCOMPARE(afterSpy.size(), 1); + + const QVariantList beforeSignal = beforeSpy.first(); + const QVariantList afterSignal = afterSpy.first(); + QCOMPARE(beforeSignal.size(), 1); + QCOMPARE(afterSignal.size(), 1); + + const QList beforeParents = beforeSignal.first().value >(); + QCOMPARE(beforeParents.size(), 2); + QVERIFY(beforeParents.first() != beforeParents.at(1)); + QVERIFY(beforeParents.contains(p1)); + QVERIFY(beforeParents.contains(p2)); + + const QList afterParents = afterSignal.first().value >(); + QCOMPARE(afterParents.size(), 2); + QVERIFY(afterParents.first() != afterParents.at(1)); + QVERIFY(afterParents.contains(p1)); + QVERIFY(afterParents.contains(p2)); + + // The first will be the last, and the lest will be the first. + QVERIFY(p1FirstPersistent.row() == 1); + QVERIFY(p1LastPersistent.row() == 0); + QVERIFY(p2FirstPersistent.row() == 9); + QVERIFY(p2LastPersistent.row() == 8); + + } + + insertCommand = new ModelInsertCommand(&model, this); + insertCommand->setAncestorRowNumbers(QList() << 5 << 4); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + insertCommand->doCommand(); + + delete insertCommand; + + // Even when p2 itself is moved around, signal emission remains correct for its children. + { + const QModelIndex p1 = model.index(5, 0); + const QModelIndex p2 = model.index(4, 0, p1); + + QVERIFY(p1.isValid()); + QVERIFY(p2.isValid()); + + QCOMPARE(model.rowCount(), 10); + QCOMPARE(model.rowCount(p1), 10); + QCOMPARE(model.rowCount(p2), 10); + + const QPersistentModelIndex p1Persistent = p1; + const QPersistentModelIndex p2Persistent = p2; + + const QPersistentModelIndex p1FirstPersistent = model.index(0, 0, p1); + const QPersistentModelIndex p1LastPersistent = model.index(9, 0, p1); + const QPersistentModelIndex p2FirstPersistent = model.index(0, 0, p2); + const QPersistentModelIndex p2LastPersistent = model.index(9, 0, p2); + + QSignalSpy beforeSpy(&model, SIGNAL(layoutAboutToBeChanged(QList))); + QSignalSpy afterSpy(&model, SIGNAL(layoutChanged(QList))); + + // Because the arguments in the signal are persistent, we need to check them for the aboutToBe + // case at emission time - before they get updated. + SignalArgumentChecker checker(p1, p2); + + ModelChangeChildrenLayoutsCommand *changeCommand = new ModelChangeChildrenLayoutsCommand(&model, this); + changeCommand->setAncestorRowNumbers(QList() << 5); + changeCommand->setSecondAncestorRowNumbers(QList() << 5 << 4); + changeCommand->doCommand(); + + // p2 has been moved. + QCOMPARE(p2Persistent.row(), p2.row() + 1); + + QCOMPARE(beforeSpy.size(), 1); + QCOMPARE(afterSpy.size(), 1); + + const QVariantList beforeSignal = beforeSpy.first(); + const QVariantList afterSignal = afterSpy.first(); + QCOMPARE(beforeSignal.size(), 1); + QCOMPARE(afterSignal.size(), 1); + + QVERIFY(p1FirstPersistent.row() == 1); + QVERIFY(p1LastPersistent.row() == 0); + QVERIFY(p2FirstPersistent.row() == 9); + QVERIFY(p2LastPersistent.row() == 8); + } +} QTEST_MAIN(tst_QAbstractItemModel) #include "tst_qabstractitemmodel.moc" diff --git a/tests/auto/integrationtests/modeltest/dynamictreemodel.cpp b/tests/auto/integrationtests/modeltest/dynamictreemodel.cpp index 2f8bb0a730..5ab37ab112 100644 --- a/tests/auto/integrationtests/modeltest/dynamictreemodel.cpp +++ b/tests/auto/integrationtests/modeltest/dynamictreemodel.cpp @@ -338,3 +338,55 @@ void ModelResetCommandFixed::emitPostSignal() m_model->endResetModel(); } +ModelChangeChildrenLayoutsCommand::ModelChangeChildrenLayoutsCommand(DynamicTreeModel* model, QObject* parent) + : ModelChangeCommand(model, parent) +{ + +} + +void ModelChangeChildrenLayoutsCommand::doCommand() +{ + const QPersistentModelIndex parent1 = findIndex(m_rowNumbers); + const QPersistentModelIndex parent2 = findIndex(m_secondRowNumbers); + + QList parents; + parents << parent1; + parents << parent2; + + emit m_model->layoutAboutToBeChanged(parents); + + int rowSize1 = -1; + int rowSize2 = -1; + + for (int column = 0; column < m_numCols; ++column) + { + { + QList &l = m_model->m_childItems[parent1.internalId()][column]; + rowSize1 = l.size(); + l.prepend(l.takeLast()); + } + { + QList &l = m_model->m_childItems[parent2.internalId()][column]; + rowSize2 = l.size(); + l.append(l.takeFirst()); + } + } + + foreach (const QModelIndex &idx, m_model->persistentIndexList()) { + if (idx.parent() == parent1) { + if (idx.row() == rowSize1 - 1) { + m_model->changePersistentIndex(idx, m_model->createIndex(0, idx.column(), idx.internalPointer())); + } else { + m_model->changePersistentIndex(idx, m_model->createIndex(idx.row() + 1, idx.column(), idx.internalPointer())); + } + } else if (idx.parent() == parent2) { + if (idx.row() == 0) { + m_model->changePersistentIndex(idx, m_model->createIndex(rowSize2 - 1, idx.column(), idx.internalPointer())); + } else { + m_model->changePersistentIndex(idx, m_model->createIndex(idx.row() - 1, idx.column(), idx.internalPointer())); + } + } + } + + emit m_model->layoutChanged(parents); +} diff --git a/tests/auto/integrationtests/modeltest/dynamictreemodel.h b/tests/auto/integrationtests/modeltest/dynamictreemodel.h index 81ef80cc40..6f52d78588 100644 --- a/tests/auto/integrationtests/modeltest/dynamictreemodel.h +++ b/tests/auto/integrationtests/modeltest/dynamictreemodel.h @@ -89,6 +89,7 @@ private: friend class ModelMoveCommand; friend class ModelResetCommand; friend class ModelResetCommandFixed; + friend class ModelChangeChildrenLayoutsCommand; }; @@ -193,5 +194,21 @@ public: }; +class ModelChangeChildrenLayoutsCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + ModelChangeChildrenLayoutsCommand(DynamicTreeModel *model, QObject *parent); + + virtual ~ModelChangeChildrenLayoutsCommand() {} + + virtual void doCommand(); + + void setSecondAncestorRowNumbers( QList rows ) { m_secondRowNumbers = rows; } + +protected: + QList m_secondRowNumbers; + int m_destRow; +}; #endif