1/****************************************************************************
2**
3** Copyright (C) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Stephen Kelly <stephen.kelly@kdab.com>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QAbstractItemModelTester>
30#include <QCoreApplication>
31#include <QSignalSpy>
32#include <QStandardItemModel>
33#include <QStringListModel>
34#include <QTest>
35#include <QTransposeProxyModel>
36#include <QLoggingCategory>
37
38#include "dynamictreemodel.h"
39#include "qidentityproxymodel.h"
40
41Q_LOGGING_CATEGORY(lcItemModels, "qt.corelib.tests.itemmodels")
42
43class DataChangedModel : public QAbstractListModel
44{
45public:
46 int rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : 1; }
47 QVariant data(const QModelIndex&, int) const { return QVariant(); }
48 QModelIndex index(int row, int column, const QModelIndex &) const { return createIndex(arow: row, acolumn: column); }
49
50 void changeData()
51 {
52 const QModelIndex idx = index(row: 0, column: 0, QModelIndex());
53 Q_EMIT dataChanged(topLeft: idx, bottomRight: idx, roles: QVector<int>() << 1);
54 }
55};
56
57class tst_QIdentityProxyModel : public QObject
58{
59 Q_OBJECT
60
61public:
62 tst_QIdentityProxyModel();
63
64public slots:
65 void initTestCase();
66 void cleanupTestCase();
67 void cleanup();
68
69private slots:
70 void insertRows();
71 void removeRows();
72 void moveRows();
73 void moveColumns();
74 void reset();
75 void dataChanged();
76
77 void itemData();
78
79 void persistIndexOnLayoutChange();
80
81protected:
82 void verifyIdentity(QAbstractItemModel *model, const QModelIndex &parent = QModelIndex());
83
84private:
85 QStandardItemModel *m_model;
86 QIdentityProxyModel *m_proxy;
87 QAbstractItemModelTester *m_modelTest;
88};
89
90tst_QIdentityProxyModel::tst_QIdentityProxyModel()
91 : m_model(0), m_proxy(0)
92{
93}
94
95void tst_QIdentityProxyModel::initTestCase()
96{
97 qRegisterMetaType<QVector<int> >();
98 m_model = new QStandardItemModel(0, 1);
99 m_proxy = new QIdentityProxyModel();
100 m_modelTest = new QAbstractItemModelTester(m_proxy, this);
101}
102
103void tst_QIdentityProxyModel::cleanupTestCase()
104{
105 delete m_proxy;
106 delete m_model;
107 delete m_modelTest;
108}
109
110void tst_QIdentityProxyModel::cleanup()
111{
112 m_model->clear();
113 m_model->insertColumns(column: 0, count: 1);
114}
115
116void tst_QIdentityProxyModel::verifyIdentity(QAbstractItemModel *model, const QModelIndex &parent)
117{
118 const int rows = model->rowCount(parent);
119 const int columns = model->columnCount(parent);
120 const QModelIndex proxyParent = m_proxy->mapFromSource(sourceIndex: parent);
121
122 QCOMPARE(m_proxy->mapToSource(proxyParent), parent);
123 QCOMPARE(rows, m_proxy->rowCount(proxyParent));
124 QCOMPARE(columns, m_proxy->columnCount(proxyParent));
125
126 for (int row = 0; row < rows; ++row) {
127 for (int column = 0; column < columns; ++column) {
128 const QModelIndex idx = model->index(row, column, parent);
129 const QModelIndex proxyIdx = m_proxy->mapFromSource(sourceIndex: idx);
130 QCOMPARE(proxyIdx.model(), m_proxy);
131 QCOMPARE(m_proxy->mapToSource(proxyIdx), idx);
132 QVERIFY(proxyIdx.isValid());
133 QCOMPARE(proxyIdx.row(), row);
134 QCOMPARE(proxyIdx.column(), column);
135 QCOMPARE(proxyIdx.parent(), proxyParent);
136 QCOMPARE(proxyIdx.data(), idx.data());
137 QCOMPARE(proxyIdx.flags(), idx.flags());
138 const int childCount = m_proxy->rowCount(parent: proxyIdx);
139 const bool hasChildren = m_proxy->hasChildren(parent: proxyIdx);
140 QCOMPARE(model->hasChildren(idx), hasChildren);
141 QVERIFY((childCount > 0) == hasChildren);
142
143 if (hasChildren)
144 verifyIdentity(model, parent: idx);
145 }
146 }
147}
148
149/*
150 tests
151*/
152
153void tst_QIdentityProxyModel::insertRows()
154{
155 QStandardItem *parentItem = m_model->invisibleRootItem();
156 for (int i = 0; i < 4; ++i) {
157 QStandardItem *item = new QStandardItem(QLatin1String("item ") + QString::number(i));
158 parentItem->appendRow(aitem: item);
159 parentItem = item;
160 }
161
162 m_proxy->setSourceModel(m_model);
163
164 verifyIdentity(model: m_model);
165
166 QSignalSpy modelBeforeSpy(m_model, &QStandardItemModel::rowsAboutToBeInserted);
167 QSignalSpy modelAfterSpy(m_model, &QStandardItemModel::rowsInserted);
168 QSignalSpy proxyBeforeSpy(m_proxy, &QStandardItemModel::rowsAboutToBeInserted);
169 QSignalSpy proxyAfterSpy(m_proxy, &QStandardItemModel::rowsInserted);
170
171 QVERIFY(modelBeforeSpy.isValid());
172 QVERIFY(modelAfterSpy.isValid());
173 QVERIFY(proxyBeforeSpy.isValid());
174 QVERIFY(proxyAfterSpy.isValid());
175
176 QStandardItem *item = new QStandardItem(QString("new item"));
177 parentItem->appendRow(aitem: item);
178
179 QVERIFY(modelBeforeSpy.size() == 1 && 1 == proxyBeforeSpy.size());
180 QVERIFY(modelAfterSpy.size() == 1 && 1 == proxyAfterSpy.size());
181
182 QCOMPARE(modelBeforeSpy.first().first().value<QModelIndex>(), m_proxy->mapToSource(proxyBeforeSpy.first().first().value<QModelIndex>()));
183 QCOMPARE(modelBeforeSpy.first().at(1), proxyBeforeSpy.first().at(1));
184 QCOMPARE(modelBeforeSpy.first().at(2), proxyBeforeSpy.first().at(2));
185
186 QCOMPARE(modelAfterSpy.first().first().value<QModelIndex>(), m_proxy->mapToSource(proxyAfterSpy.first().first().value<QModelIndex>()));
187 QCOMPARE(modelAfterSpy.first().at(1), proxyAfterSpy.first().at(1));
188 QCOMPARE(modelAfterSpy.first().at(2), proxyAfterSpy.first().at(2));
189
190 verifyIdentity(model: m_model);
191}
192
193void tst_QIdentityProxyModel::removeRows()
194{
195 QStandardItem *parentItem = m_model->invisibleRootItem();
196 for (int i = 0; i < 4; ++i) {
197 QStandardItem *item = new QStandardItem(QLatin1String("item ") + QString::number(i));
198 parentItem->appendRow(aitem: item);
199 parentItem = item;
200 }
201
202 m_proxy->setSourceModel(m_model);
203
204 verifyIdentity(model: m_model);
205
206 QSignalSpy modelBeforeSpy(m_model, &QStandardItemModel::rowsAboutToBeRemoved);
207 QSignalSpy modelAfterSpy(m_model, &QStandardItemModel::rowsRemoved);
208 QSignalSpy proxyBeforeSpy(m_proxy, &QStandardItemModel::rowsAboutToBeRemoved);
209 QSignalSpy proxyAfterSpy(m_proxy, &QStandardItemModel::rowsRemoved);
210
211 QVERIFY(modelBeforeSpy.isValid());
212 QVERIFY(modelAfterSpy.isValid());
213 QVERIFY(proxyBeforeSpy.isValid());
214 QVERIFY(proxyAfterSpy.isValid());
215
216 const QModelIndex topLevel = m_model->index(row: 0, column: 0, parent: QModelIndex());
217 const QModelIndex secondLevel = m_model->index(row: 0, column: 0, parent: topLevel);
218 const QModelIndex thirdLevel = m_model->index(row: 0, column: 0, parent: secondLevel);
219
220 QVERIFY(thirdLevel.isValid());
221
222 m_model->removeRow(arow: 0, aparent: secondLevel);
223
224 QVERIFY(modelBeforeSpy.size() == 1 && 1 == proxyBeforeSpy.size());
225 QVERIFY(modelAfterSpy.size() == 1 && 1 == proxyAfterSpy.size());
226
227 QCOMPARE(modelBeforeSpy.first().first().value<QModelIndex>(), m_proxy->mapToSource(proxyBeforeSpy.first().first().value<QModelIndex>()));
228 QCOMPARE(modelBeforeSpy.first().at(1), proxyBeforeSpy.first().at(1));
229 QCOMPARE(modelBeforeSpy.first().at(2), proxyBeforeSpy.first().at(2));
230
231 QCOMPARE(modelAfterSpy.first().first().value<QModelIndex>(), m_proxy->mapToSource(proxyAfterSpy.first().first().value<QModelIndex>()));
232 QCOMPARE(modelAfterSpy.first().at(1), proxyAfterSpy.first().at(1));
233 QCOMPARE(modelAfterSpy.first().at(2), proxyAfterSpy.first().at(2));
234
235 verifyIdentity(model: m_model);
236}
237
238void tst_QIdentityProxyModel::moveRows()
239{
240 QStringListModel model({"A", "B", "C", "D", "E", "F"});
241
242 m_proxy->setSourceModel(&model);
243
244 verifyIdentity(model: &model);
245
246 QSignalSpy modelBeforeSpy(&model, &QAbstractItemModel::rowsAboutToBeMoved);
247 QSignalSpy modelAfterSpy(&model, &QAbstractItemModel::rowsMoved);
248 QSignalSpy proxyBeforeSpy(m_proxy, &QAbstractItemModel::rowsAboutToBeMoved);
249 QSignalSpy proxyAfterSpy(m_proxy, &QAbstractItemModel::rowsMoved);
250
251 QVERIFY(m_proxy->moveRows(QModelIndex(), 1, 2, QModelIndex(), 5));
252 QCOMPARE(model.stringList(), QStringList({"A", "D", "E", "B", "C", "F"}));
253
254 QCOMPARE(modelBeforeSpy.size(), 1);
255 QCOMPARE(proxyBeforeSpy.size(), 1);
256 QCOMPARE(modelAfterSpy.size(), 1);
257 QCOMPARE(proxyAfterSpy.size(), 1);
258
259 QCOMPARE(modelBeforeSpy.first().first().value<QModelIndex>(), m_proxy->mapToSource(proxyBeforeSpy.first().first().value<QModelIndex>()));
260 QCOMPARE(modelBeforeSpy.first().at(1), proxyBeforeSpy.first().at(1));
261 QCOMPARE(modelBeforeSpy.first().at(2), proxyBeforeSpy.first().at(2));
262 QCOMPARE(modelBeforeSpy.first().at(3).value<QModelIndex>(), m_proxy->mapToSource(proxyBeforeSpy.first().at(3).value<QModelIndex>()));
263 QCOMPARE(modelBeforeSpy.first().at(4), proxyBeforeSpy.first().at(4));
264
265 QCOMPARE(modelAfterSpy.first().first().value<QModelIndex>(), m_proxy->mapToSource(proxyAfterSpy.first().first().value<QModelIndex>()));
266 QCOMPARE(modelAfterSpy.first().at(1), proxyAfterSpy.first().at(1));
267 QCOMPARE(modelAfterSpy.first().at(2), proxyAfterSpy.first().at(2));
268 QCOMPARE(modelAfterSpy.first().at(3).value<QModelIndex>(), m_proxy->mapToSource(proxyAfterSpy.first().at(3).value<QModelIndex>()));
269 QCOMPARE(modelAfterSpy.first().at(4), proxyAfterSpy.first().at(4));
270
271 QVERIFY(m_proxy->moveRows(QModelIndex(), 3, 2, QModelIndex(), 1));
272 QCOMPARE(model.stringList(), QStringList({"A", "B", "C", "D", "E", "F"}));
273 QVERIFY(m_proxy->moveRows(QModelIndex(), 0, 3, QModelIndex(), 6));
274 QCOMPARE(model.stringList(), QStringList({"D", "E", "F", "A", "B", "C"}));
275
276 verifyIdentity(model: &model);
277
278 m_proxy->setSourceModel(nullptr);
279}
280
281void tst_QIdentityProxyModel::moveColumns()
282{
283 // QStringListModel implements moveRows but not moveColumns
284 // so we have to use a QTransposeProxyModel inbetween to check if
285 // QIdentityProxyModel::moveColumns works as expected
286 QStringListModel model({"A", "B", "C", "D", "E", "F"});
287 QTransposeProxyModel tpm;
288 tpm.setSourceModel(&model);
289
290 m_proxy->setSourceModel(&tpm);
291
292 verifyIdentity(model: &tpm);
293
294 QSignalSpy modelBeforeSpy(&tpm, &QAbstractItemModel::columnsAboutToBeMoved);
295 QSignalSpy modelAfterSpy(&tpm, &QAbstractItemModel::columnsMoved);
296 QSignalSpy proxyBeforeSpy(m_proxy, &QAbstractItemModel::columnsAboutToBeMoved);
297 QSignalSpy proxyAfterSpy(m_proxy, &QAbstractItemModel::columnsMoved);
298
299 QVERIFY(m_proxy->moveColumns(QModelIndex(), 1, 2, QModelIndex(), 5));
300 QCOMPARE(model.stringList(), QStringList({"A", "D", "E", "B", "C", "F"}));
301
302 QCOMPARE(proxyBeforeSpy.size(), 1);
303 QCOMPARE(modelBeforeSpy.size(), 1);
304 QCOMPARE(modelAfterSpy.size(), 1);
305 QCOMPARE(proxyAfterSpy.size(), 1);
306
307 QCOMPARE(modelBeforeSpy.first().first().value<QModelIndex>(), m_proxy->mapToSource(proxyBeforeSpy.first().first().value<QModelIndex>()));
308 QCOMPARE(modelBeforeSpy.first().at(1), proxyBeforeSpy.first().at(1));
309 QCOMPARE(modelBeforeSpy.first().at(2), proxyBeforeSpy.first().at(2));
310 QCOMPARE(modelBeforeSpy.first().at(3).value<QModelIndex>(), m_proxy->mapToSource(proxyBeforeSpy.first().at(3).value<QModelIndex>()));
311 QCOMPARE(modelBeforeSpy.first().at(4), proxyBeforeSpy.first().at(4));
312
313 QCOMPARE(modelAfterSpy.first().first().value<QModelIndex>(), m_proxy->mapToSource(proxyAfterSpy.first().first().value<QModelIndex>()));
314 QCOMPARE(modelAfterSpy.first().at(1), proxyAfterSpy.first().at(1));
315 QCOMPARE(modelAfterSpy.first().at(2), proxyAfterSpy.first().at(2));
316 QCOMPARE(modelAfterSpy.first().at(3).value<QModelIndex>(), m_proxy->mapToSource(proxyAfterSpy.first().at(3).value<QModelIndex>()));
317 QCOMPARE(modelAfterSpy.first().at(4), proxyAfterSpy.first().at(4));
318
319 QVERIFY(m_proxy->moveColumns(QModelIndex(), 3, 2, QModelIndex(), 1));
320 QCOMPARE(model.stringList(), QStringList({"A", "B", "C", "D", "E", "F"}));
321 QVERIFY(m_proxy->moveColumns(QModelIndex(), 0, 3, QModelIndex(), 6));
322 QCOMPARE(model.stringList(), QStringList({"D", "E", "F", "A", "B", "C"}));
323
324 verifyIdentity(model: &tpm);
325
326 m_proxy->setSourceModel(nullptr);
327}
328
329void tst_QIdentityProxyModel::reset()
330{
331 DynamicTreeModel model;
332
333 {
334 ModelInsertCommand insertCommand(&model);
335 insertCommand.setStartRow(0);
336 insertCommand.setEndRow(9);
337 insertCommand.doCommand();
338 }
339 {
340 ModelInsertCommand insertCommand(&model);
341 insertCommand.setAncestorRowNumbers(QList<int>() << 5);
342 insertCommand.setStartRow(0);
343 insertCommand.setEndRow(9);
344 insertCommand.doCommand();
345 }
346
347 m_proxy->setSourceModel(&model);
348
349 verifyIdentity(model: &model);
350
351 QSignalSpy modelBeforeSpy(&model, &DynamicTreeModel::modelAboutToBeReset);
352 QSignalSpy modelAfterSpy(&model, &DynamicTreeModel::modelReset);
353 QSignalSpy proxyBeforeSpy(m_proxy, &QIdentityProxyModel::modelAboutToBeReset);
354 QSignalSpy proxyAfterSpy(m_proxy, &QIdentityProxyModel::modelReset);
355
356 QVERIFY(modelBeforeSpy.isValid());
357 QVERIFY(modelAfterSpy.isValid());
358 QVERIFY(proxyBeforeSpy.isValid());
359 QVERIFY(proxyAfterSpy.isValid());
360
361 {
362 ModelResetCommandFixed resetCommand(&model, 0);
363 resetCommand.setAncestorRowNumbers(QList<int>() << 5);
364 resetCommand.setStartRow(3);
365 resetCommand.setEndRow(4);
366 resetCommand.setDestRow(1);
367 resetCommand.doCommand();
368 }
369
370 QVERIFY(modelBeforeSpy.size() == 1 && 1 == proxyBeforeSpy.size());
371 QVERIFY(modelAfterSpy.size() == 1 && 1 == proxyAfterSpy.size());
372
373 verifyIdentity(model: &model);
374 m_proxy->setSourceModel(0);
375}
376
377void tst_QIdentityProxyModel::dataChanged()
378{
379 DataChangedModel model;
380 m_proxy->setSourceModel(&model);
381
382 verifyIdentity(model: &model);
383
384 QSignalSpy modelSpy(&model, &DataChangedModel::dataChanged);
385 QSignalSpy proxySpy(m_proxy, &QIdentityProxyModel::dataChanged);
386
387 QVERIFY(modelSpy.isValid());
388 QVERIFY(proxySpy.isValid());
389
390 model.changeData();
391
392 QCOMPARE(modelSpy.first().at(2).value<QVector<int> >(), QVector<int>() << 1);
393 QCOMPARE(modelSpy.first().at(2), proxySpy.first().at(2));
394
395 verifyIdentity(model: &model);
396 m_proxy->setSourceModel(0);
397}
398
399class AppendStringProxy : public QIdentityProxyModel
400{
401public:
402 QVariant data(const QModelIndex &index, int role) const
403 {
404 const QVariant result = QIdentityProxyModel::data(proxyIndex: index, role);
405 if (role != Qt::DisplayRole)
406 return result;
407 return result.toString() + "_appended";
408 }
409};
410
411void tst_QIdentityProxyModel::itemData()
412{
413 QStringListModel model(QStringList() << "Monday" << "Tuesday" << "Wednesday");
414 AppendStringProxy proxy;
415 proxy.setSourceModel(&model);
416
417 const QModelIndex topIndex = proxy.index(row: 0, column: 0);
418 QCOMPARE(topIndex.data(Qt::DisplayRole).toString(), QStringLiteral("Monday_appended"));
419 QCOMPARE(proxy.data(topIndex, Qt::DisplayRole).toString(), QStringLiteral("Monday_appended"));
420 QCOMPARE(proxy.itemData(topIndex).value(Qt::DisplayRole).toString(), QStringLiteral("Monday_appended"));
421}
422
423void dump(QAbstractItemModel* model, QString const& indent = " - ", QModelIndex const& parent = {})
424{
425 for (auto row = 0; row < model->rowCount(parent); ++row)
426 {
427 auto idx = model->index(row, column: 0, parent);
428 qCDebug(lcItemModels) << (indent + idx.data().toString());
429 dump(model, indent: indent + "- ", parent: idx);
430 }
431}
432
433void tst_QIdentityProxyModel::persistIndexOnLayoutChange()
434{
435 DynamicTreeModel model;
436
437 QList<int> ancestors;
438 for (auto i = 0; i < 3; ++i)
439 {
440 Q_UNUSED(i);
441 ModelInsertCommand insertCommand(&model);
442 insertCommand.setAncestorRowNumbers(ancestors);
443 insertCommand.setStartRow(0);
444 insertCommand.setEndRow(0);
445 insertCommand.doCommand();
446 ancestors.push_back(t: 0);
447 }
448 ModelInsertCommand insertCommand(&model);
449 insertCommand.setAncestorRowNumbers(ancestors);
450 insertCommand.setStartRow(0);
451 insertCommand.setEndRow(1);
452 insertCommand.doCommand();
453
454 // dump(&model);
455 // " - 1"
456 // " - - 2"
457 // " - - - 3"
458 // " - - - - 4"
459 // " - - - - 5"
460
461 QIdentityProxyModel proxy;
462 proxy.setSourceModel(&model);
463
464 QPersistentModelIndex persistentIndex;
465
466 QPersistentModelIndex sourcePersistentIndex = model.match(start: model.index(row: 0, column: 0), role: Qt::DisplayRole, value: "5", hits: 1, flags: Qt::MatchRecursive).first();
467
468 QCOMPARE(sourcePersistentIndex.data().toString(), QStringLiteral("5"));
469
470 bool gotLayoutAboutToBeChanged = false;
471 bool gotLayoutChanged = false;
472
473 QObject::connect(sender: &proxy, signal: &QAbstractItemModel::layoutAboutToBeChanged, context: &proxy, slot: [&proxy, &persistentIndex, &gotLayoutAboutToBeChanged]
474 {
475 gotLayoutAboutToBeChanged = true;
476 persistentIndex = proxy.match(start: proxy.index(row: 0, column: 0), role: Qt::DisplayRole, value: "5", hits: 1, flags: Qt::MatchRecursive).first();
477 });
478
479 QObject::connect(sender: &proxy, signal: &QAbstractItemModel::layoutChanged, context: &proxy, slot: [&proxy, &persistentIndex, &sourcePersistentIndex, &gotLayoutChanged]
480 {
481 gotLayoutChanged = true;
482 QCOMPARE(QModelIndex(persistentIndex), proxy.mapFromSource(sourcePersistentIndex));
483 });
484
485 ModelChangeChildrenLayoutsCommand layoutChangeCommand(&model, 0);
486
487 layoutChangeCommand.setAncestorRowNumbers(QList<int>{0, 0, 0});
488 layoutChangeCommand.setSecondAncestorRowNumbers(QList<int>{0, 0});
489
490 layoutChangeCommand.doCommand();
491
492 QVERIFY(gotLayoutAboutToBeChanged);
493 QVERIFY(gotLayoutChanged);
494 QVERIFY(persistentIndex.isValid());
495}
496
497QTEST_MAIN(tst_QIdentityProxyModel)
498#include "tst_qidentityproxymodel.moc"
499

source code of qtbase/tests/auto/corelib/itemmodels/qidentityproxymodel/tst_qidentityproxymodel.cpp