1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite 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 "tst_qsortfilterproxymodel.h"
30#include "dynamictreemodel.h"
31
32#include <QDebug>
33#include <QComboBox>
34#include <QSortFilterProxyModel>
35#include <QStandardItem>
36#include <QStringListModel>
37#include <QTableView>
38#include <QTreeView>
39#include <QtTest>
40
41Q_LOGGING_CATEGORY(lcItemModels, "qt.corelib.tests.itemmodels")
42
43using IntPair = QPair<int, int>;
44using IntList = QVector<int>;
45using IntPairList = QVector<IntPair>;
46
47// Testing get/set functions
48void tst_QSortFilterProxyModel::getSetCheck()
49{
50 QSortFilterProxyModel obj1;
51 QCOMPARE(obj1.sourceModel(), nullptr);
52 // int QSortFilterProxyModel::filterKeyColumn()
53 // void QSortFilterProxyModel::setFilterKeyColumn(int)
54 obj1.setFilterKeyColumn(0);
55 QCOMPARE(0, obj1.filterKeyColumn());
56 obj1.setFilterKeyColumn(INT_MIN);
57 QCOMPARE(INT_MIN, obj1.filterKeyColumn());
58 obj1.setFilterKeyColumn(INT_MAX);
59 QCOMPARE(INT_MAX, obj1.filterKeyColumn());
60}
61
62Q_DECLARE_METATYPE(Qt::MatchFlag)
63void tst_QSortFilterProxyModel::initTestCase()
64{
65 qRegisterMetaType<QAbstractItemModel::LayoutChangeHint>();
66 qRegisterMetaType<QList<QPersistentModelIndex> >();
67 qRegisterMetaType<Qt::MatchFlag>();
68 m_model = new QStandardItemModel(0, 1);
69 m_proxy = new QSortFilterProxyModel();
70 m_proxy->setSourceModel(m_model);
71}
72
73void tst_QSortFilterProxyModel::cleanupTestCase()
74{
75 delete m_proxy;
76 delete m_model;
77}
78
79void tst_QSortFilterProxyModel::cleanup()
80{
81 switch (m_filterType) {
82 case FilterType::RegExp:
83 m_proxy->setFilterRegExp(QRegExp());
84 break;
85 case FilterType::RegularExpression:
86 m_proxy->setFilterRegularExpression(QRegularExpression());
87 break;
88 }
89
90 m_proxy->sort(column: -1, order: Qt::AscendingOrder);
91 m_model->clear();
92 m_model->insertColumns(column: 0, count: 1);
93}
94
95/*
96 tests
97*/
98
99void tst_QSortFilterProxyModel::sort_data()
100{
101 QTest::addColumn<Qt::SortOrder>(name: "sortOrder");
102 QTest::addColumn<Qt::CaseSensitivity>(name: "sortCaseSensitivity");
103 QTest::addColumn<QStringList>(name: "initial");
104 QTest::addColumn<QStringList>(name: "expected");
105
106 QTest::newRow(dataTag: "flat descending") << Qt::DescendingOrder
107 << Qt::CaseSensitive
108 << (QStringList()
109 << "delta"
110 << "yankee"
111 << "bravo"
112 << "lima"
113 << "charlie"
114 << "juliet"
115 << "tango"
116 << "hotel"
117 << "uniform"
118 << "alpha"
119 << "echo"
120 << "golf"
121 << "quebec"
122 << "foxtrot"
123 << "india"
124 << "romeo"
125 << "november"
126 << "oskar"
127 << "zulu"
128 << "kilo"
129 << "whiskey"
130 << "mike"
131 << "papa"
132 << "sierra"
133 << "xray"
134 << "viktor")
135 << (QStringList()
136 << "zulu"
137 << "yankee"
138 << "xray"
139 << "whiskey"
140 << "viktor"
141 << "uniform"
142 << "tango"
143 << "sierra"
144 << "romeo"
145 << "quebec"
146 << "papa"
147 << "oskar"
148 << "november"
149 << "mike"
150 << "lima"
151 << "kilo"
152 << "juliet"
153 << "india"
154 << "hotel"
155 << "golf"
156 << "foxtrot"
157 << "echo"
158 << "delta"
159 << "charlie"
160 << "bravo"
161 << "alpha");
162 QTest::newRow(dataTag: "flat ascending") << Qt::AscendingOrder
163 << Qt::CaseSensitive
164 << (QStringList()
165 << "delta"
166 << "yankee"
167 << "bravo"
168 << "lima"
169 << "charlie"
170 << "juliet"
171 << "tango"
172 << "hotel"
173 << "uniform"
174 << "alpha"
175 << "echo"
176 << "golf"
177 << "quebec"
178 << "foxtrot"
179 << "india"
180 << "romeo"
181 << "november"
182 << "oskar"
183 << "zulu"
184 << "kilo"
185 << "whiskey"
186 << "mike"
187 << "papa"
188 << "sierra"
189 << "xray"
190 << "viktor")
191 << (QStringList()
192 << "alpha"
193 << "bravo"
194 << "charlie"
195 << "delta"
196 << "echo"
197 << "foxtrot"
198 << "golf"
199 << "hotel"
200 << "india"
201 << "juliet"
202 << "kilo"
203 << "lima"
204 << "mike"
205 << "november"
206 << "oskar"
207 << "papa"
208 << "quebec"
209 << "romeo"
210 << "sierra"
211 << "tango"
212 << "uniform"
213 << "viktor"
214 << "whiskey"
215 << "xray"
216 << "yankee"
217 << "zulu");
218 QTest::newRow(dataTag: "case insensitive") << Qt::AscendingOrder
219 << Qt::CaseInsensitive
220 << (QStringList()
221 << "alpha" << "BETA" << "Gamma" << "delta")
222 << (QStringList()
223 << "alpha" << "BETA" << "delta" << "Gamma");
224 QTest::newRow(dataTag: "case sensitive") << Qt::AscendingOrder
225 << Qt::CaseSensitive
226 << (QStringList()
227 << "alpha" << "BETA" << "Gamma" << "delta")
228 << (QStringList()
229 << "BETA" << "Gamma" << "alpha" << "delta");
230
231 QStringList list;
232 for (int i = 10000; i < 20000; ++i)
233 list.append(QStringLiteral("Number: ") + QString::number(i));
234 QTest::newRow(dataTag: "large set ascending") << Qt::AscendingOrder << Qt::CaseSensitive << list << list;
235}
236
237void tst_QSortFilterProxyModel::sort()
238{
239 QFETCH(Qt::SortOrder, sortOrder);
240 QFETCH(Qt::CaseSensitivity, sortCaseSensitivity);
241 QFETCH(QStringList, initial);
242 QFETCH(QStringList, expected);
243
244 // prepare model
245 QStandardItem *root = m_model->invisibleRootItem ();
246 QList<QStandardItem *> items;
247 for (int i = 0; i < initial.count(); ++i) {
248 items.append(t: new QStandardItem(initial.at(i)));
249 }
250 root->insertRows(row: 0, items);
251 QCOMPARE(m_model->rowCount(QModelIndex()), initial.count());
252 QCOMPARE(m_model->columnCount(QModelIndex()), 1);
253
254 // make sure the proxy is unsorted
255 QCOMPARE(m_proxy->columnCount(QModelIndex()), 1);
256 QCOMPARE(m_proxy->rowCount(QModelIndex()), initial.count());
257 for (int row = 0; row < m_proxy->rowCount(parent: QModelIndex()); ++row) {
258 QModelIndex index = m_proxy->index(row, column: 0, parent: QModelIndex());
259 QCOMPARE(m_proxy->data(index, Qt::DisplayRole).toString(), initial.at(row));
260 }
261
262 // sort
263 m_proxy->sort(column: 0, order: sortOrder);
264 m_proxy->setSortCaseSensitivity(sortCaseSensitivity);
265
266 // make sure the model is unchanged
267 for (int row = 0; row < m_model->rowCount(parent: QModelIndex()); ++row) {
268 QModelIndex index = m_model->index(row, column: 0, parent: QModelIndex());
269 QCOMPARE(m_model->data(index, Qt::DisplayRole).toString(), initial.at(row));
270 }
271 // make sure the proxy is sorted
272 for (int row = 0; row < m_proxy->rowCount(parent: QModelIndex()); ++row) {
273 QModelIndex index = m_proxy->index(row, column: 0, parent: QModelIndex());
274 QCOMPARE(m_proxy->data(index, Qt::DisplayRole).toString(), expected.at(row));
275 }
276
277 // restore the unsorted order
278 m_proxy->sort(column: -1);
279
280 // make sure the proxy is unsorted again
281 for (int row = 0; row < m_proxy->rowCount(parent: QModelIndex()); ++row) {
282 QModelIndex index = m_proxy->index(row, column: 0, parent: QModelIndex());
283 QCOMPARE(m_proxy->data(index, Qt::DisplayRole).toString(), initial.at(row));
284 }
285}
286
287void tst_QSortFilterProxyModel::sortHierarchy_data()
288{
289 QTest::addColumn<Qt::SortOrder>(name: "sortOrder");
290 QTest::addColumn<QStringList>(name: "initial");
291 QTest::addColumn<QStringList>(name: "expected");
292
293 QTest::newRow(dataTag: "flat ascending")
294 << Qt::AscendingOrder
295 << (QStringList()
296 << "c" << "f" << "d" << "e" << "a" << "b")
297 << (QStringList()
298 << "a" << "b" << "c" << "d" << "e" << "f");
299
300 QTest::newRow(dataTag: "simple hierarchy")
301 << Qt::AscendingOrder
302 << (QStringList() << "a" << "<" << "b" << "<" << "c" << ">" << ">")
303 << (QStringList() << "a" << "<" << "b" << "<" << "c" << ">" << ">");
304
305 QTest::newRow(dataTag: "hierarchical ascending")
306 << Qt::AscendingOrder
307 << (QStringList()
308 << "c"
309 << "<"
310 << "h"
311 << "<"
312 << "2"
313 << "0"
314 << "1"
315 << ">"
316 << "g"
317 << "i"
318 << ">"
319 << "b"
320 << "<"
321 << "l"
322 << "k"
323 << "<"
324 << "8"
325 << "7"
326 << "9"
327 << ">"
328 << "m"
329 << ">"
330 << "a"
331 << "<"
332 << "z"
333 << "y"
334 << "x"
335 << ">")
336 << (QStringList()
337 << "a"
338 << "<"
339 << "x"
340 << "y"
341 << "z"
342 << ">"
343 << "b"
344 << "<"
345 << "k"
346 << "<"
347 << "7"
348 << "8"
349 << "9"
350 << ">"
351 << "l"
352 << "m"
353 << ">"
354 << "c"
355 << "<"
356 << "g"
357 << "h"
358 << "<"
359 << "0"
360 << "1"
361 << "2"
362 << ">"
363 << "i"
364 << ">");
365}
366
367void tst_QSortFilterProxyModel::sortHierarchy()
368{
369 QFETCH(Qt::SortOrder, sortOrder);
370 QFETCH(QStringList, initial);
371 QFETCH(QStringList, expected);
372
373 buildHierarchy(data: initial, model: m_model);
374 checkHierarchy(data: initial, model: m_model);
375 checkHierarchy(data: initial, model: m_proxy);
376 m_proxy->sort(column: 0, order: sortOrder);
377 checkHierarchy(data: initial, model: m_model);
378 checkHierarchy(data: expected, model: m_proxy);
379}
380
381void tst_QSortFilterProxyModel::insertRows_data()
382{
383 QTest::addColumn<QStringList>(name: "initial");
384 QTest::addColumn<QStringList>(name: "expected");
385 QTest::addColumn<QStringList>(name: "insert");
386 QTest::addColumn<int>(name: "position");
387
388 QTest::newRow(dataTag: "insert one row in the middle")
389 << (QStringList()
390 << "One"
391 << "Two"
392 << "Four"
393 << "Five")
394 << (QStringList()
395 << "One"
396 << "Two"
397 << "Three"
398 << "Four"
399 << "Five")
400 << (QStringList()
401 << "Three")
402 << 2;
403
404 QTest::newRow(dataTag: "insert one row in the beginning")
405 << (QStringList()
406 << "Two"
407 << "Three"
408 << "Four"
409 << "Five")
410 << (QStringList()
411 << "One"
412 << "Two"
413 << "Three"
414 << "Four"
415 << "Five")
416 << (QStringList()
417 << "One")
418 << 0;
419
420 QTest::newRow(dataTag: "insert one row in the end")
421 << (QStringList()
422 << "One"
423 << "Two"
424 << "Three"
425 << "Four")
426 << (QStringList()
427 << "One"
428 << "Two"
429 << "Three"
430 << "Four"
431 << "Five")
432 << (QStringList()
433 <<"Five")
434 << 4;
435}
436
437void tst_QSortFilterProxyModel::insertRows()
438{
439 QFETCH(QStringList, initial);
440 QFETCH(QStringList, expected);
441 QFETCH(QStringList, insert);
442 QFETCH(int, position);
443 // prepare model
444 m_model->insertRows(row: 0, count: initial.count(), parent: QModelIndex());
445 //m_model->insertColumns(0, 1, QModelIndex());
446 QCOMPARE(m_model->columnCount(QModelIndex()), 1);
447 QCOMPARE(m_model->rowCount(QModelIndex()), initial.count());
448 for (int row = 0; row < m_model->rowCount(parent: QModelIndex()); ++row) {
449 QModelIndex index = m_model->index(row, column: 0, parent: QModelIndex());
450 m_model->setData(index, value: initial.at(i: row), role: Qt::DisplayRole);
451 }
452 // make sure the model correct before insert
453 for (int row = 0; row < m_model->rowCount(parent: QModelIndex()); ++row) {
454 QModelIndex index = m_model->index(row, column: 0, parent: QModelIndex());
455 QCOMPARE(m_model->data(index, Qt::DisplayRole).toString(), initial.at(row));
456 }
457 // make sure the proxy is correct before insert
458 for (int row = 0; row < m_proxy->rowCount(parent: QModelIndex()); ++row) {
459 QModelIndex index = m_proxy->index(row, column: 0, parent: QModelIndex());
460 QCOMPARE(m_proxy->data(index, Qt::DisplayRole).toString(), initial.at(row));
461 }
462
463 // insert the row
464 m_proxy->insertRows(row: position, count: insert.count(), parent: QModelIndex());
465 QCOMPARE(m_model->rowCount(QModelIndex()), expected.count());
466 QCOMPARE(m_proxy->rowCount(QModelIndex()), expected.count());
467
468 // set the data for the inserted row
469 for (int i = 0; i < insert.count(); ++i) {
470 QModelIndex index = m_proxy->index(row: position + i, column: 0, parent: QModelIndex());
471 m_proxy->setData(index, value: insert.at(i), role: Qt::DisplayRole);
472 }
473
474 // make sure the model correct after insert
475 for (int row = 0; row < m_model->rowCount(parent: QModelIndex()); ++row) {
476 QModelIndex index = m_model->index(row, column: 0, parent: QModelIndex());
477 QCOMPARE(m_model->data(index, Qt::DisplayRole).toString(), expected.at(row));
478 }
479
480 // make sure the proxy is correct after insert
481 for (int row = 0; row < m_proxy->rowCount(parent: QModelIndex()); ++row) {
482 QModelIndex index = m_proxy->index(row, column: 0, parent: QModelIndex());
483 QCOMPARE(m_proxy->data(index, Qt::DisplayRole).toString(), expected.at(row));
484 }
485}
486
487void tst_QSortFilterProxyModel::prependRow()
488{
489 //this tests that data is correctly handled by the sort filter when prepending a row
490 QStandardItemModel model;
491 QSortFilterProxyModel proxy;
492 proxy.setSourceModel(&model);
493
494 QStandardItem item("root");
495 model.appendRow(aitem: &item);
496
497 QStandardItem sub("sub");
498 item.appendRow(aitem: &sub);
499
500 sub.appendRow(aitem: new QStandardItem("test1"));
501 sub.appendRow(aitem: new QStandardItem("test2"));
502
503 QStandardItem sub2("sub2");
504 sub2.appendRow(aitem: new QStandardItem("sub3"));
505 item.insertRow(arow: 0, aitem: &sub2);
506
507 QModelIndex index_sub2 = proxy.mapFromSource(sourceIndex: model.indexFromItem(item: &sub2));
508
509 QCOMPARE(sub2.rowCount(), proxy.rowCount(index_sub2));
510 QCOMPARE(proxy.rowCount(QModelIndex()), 1); //only the "root" item is there
511}
512
513void tst_QSortFilterProxyModel::appendRowFromCombobox_data()
514{
515 QTest::addColumn<QString>(name: "pattern");
516 QTest::addColumn<QStringList>(name: "initial");
517 QTest::addColumn<QString>(name: "newitem");
518 QTest::addColumn<QStringList>(name: "expected");
519
520 QTest::newRow(dataTag: "filter_out_second_last_item")
521 << "^[0-9]*$"
522 << (QStringList() << "a" << "1")
523 << "2"
524 << (QStringList() << "a" << "1" << "2");
525
526 QTest::newRow(dataTag: "filter_out_everything")
527 << "^c*$"
528 << (QStringList() << "a" << "b")
529 << "c"
530 << (QStringList() << "a" << "b" << "c");
531
532 QTest::newRow(dataTag: "no_filter")
533 << ""
534 << (QStringList() << "0" << "1")
535 << "2"
536 << (QStringList() << "0" << "1" << "2");
537
538 QTest::newRow(dataTag: "filter_out_last_item")
539 << "^[a-z]*$"
540 << (QStringList() << "a" << "1")
541 << "b"
542 << (QStringList() << "a" << "1" << "b");
543}
544
545void tst_QSortFilterProxyModel::appendRowFromCombobox()
546{
547 QFETCH(QString, pattern);
548 QFETCH(QStringList, initial);
549 QFETCH(QString, newitem);
550 QFETCH(QStringList, expected);
551
552 QStringListModel model(initial);
553
554 QSortFilterProxyModel proxy;
555 proxy.setSourceModel(&model);
556 proxy.setFilterRegExp(pattern);
557
558 QComboBox comboBox;
559 comboBox.setModel(&proxy);
560 comboBox.addItem(atext: newitem);
561
562 QCOMPARE(model.stringList(), expected);
563}
564
565void tst_QSortFilterProxyModel::removeRows_data()
566{
567 QTest::addColumn<QStringList>(name: "initial");
568 QTest::addColumn<int>(name: "sortOrder");
569 QTest::addColumn<QString>(name: "filter");
570 QTest::addColumn<int>(name: "position");
571 QTest::addColumn<int>(name: "count");
572 QTest::addColumn<bool>(name: "success");
573 QTest::addColumn<QStringList>(name: "expectedProxy");
574 QTest::addColumn<QStringList>(name: "expectedSource");
575
576 QTest::newRow(dataTag: "remove one row in the middle [no sorting/filter]")
577 << (QStringList()
578 << "One"
579 << "Two"
580 << "Three"
581 << "Four"
582 << "Five")
583 << -1 // no sorting
584 << QString() // no filter
585 << 2 // position
586 << 1 // count
587 << true // success
588 << (QStringList() // expectedProxy
589 << "One"
590 << "Two"
591 << "Four"
592 << "Five")
593 << (QStringList() // expectedSource
594 << "One"
595 << "Two"
596 << "Four"
597 << "Five");
598
599 QTest::newRow(dataTag: "remove one row in the beginning [no sorting/filter]")
600 << (QStringList()
601 << "One"
602 << "Two"
603 << "Three"
604 << "Four"
605 << "Five")
606 << -1 // no sorting
607 << QString() // no filter
608 << 0 // position
609 << 1 // count
610 << true // success
611 << (QStringList() // expectedProxy
612 << "Two"
613 << "Three"
614 << "Four"
615 << "Five")
616 << (QStringList() // expectedSource
617 << "Two"
618 << "Three"
619 << "Four"
620 << "Five");
621
622 QTest::newRow(dataTag: "remove one row in the end [no sorting/filter]")
623 << (QStringList()
624 << "One"
625 << "Two"
626 << "Three"
627 << "Four"
628 << "Five")
629 << -1 // no sorting
630 << QString() // no filter
631 << 4 // position
632 << 1 // count
633 << true // success
634 << (QStringList() // expectedProxy
635 << "One"
636 << "Two"
637 << "Three"
638 << "Four")
639 << (QStringList() // expectedSource
640 << "One"
641 << "Two"
642 << "Three"
643 << "Four");
644
645 QTest::newRow(dataTag: "remove all [no sorting/filter]")
646 << (QStringList()
647 << "One"
648 << "Two"
649 << "Three"
650 << "Four"
651 << "Five")
652 << -1 // no sorting
653 << QString() // no filter
654 << 0 // position
655 << 5 // count
656 << true // success
657 << QStringList() // expectedProxy
658 << QStringList(); // expectedSource
659
660 QTest::newRow(dataTag: "remove one row past the end [no sorting/filter]")
661 << (QStringList()
662 << "One"
663 << "Two"
664 << "Three"
665 << "Four"
666 << "Five")
667 << -1 // no sorting
668 << QString() // no filter
669 << 5 // position
670 << 1 // count
671 << false // success
672 << (QStringList() // expectedProxy
673 << "One"
674 << "Two"
675 << "Three"
676 << "Four"
677 << "Five")
678 << (QStringList() // expectedSource
679 << "One"
680 << "Two"
681 << "Three"
682 << "Four"
683 << "Five");
684
685 QTest::newRow(dataTag: "remove row -1 [no sorting/filter]")
686 << (QStringList()
687 << "One"
688 << "Two"
689 << "Three"
690 << "Four"
691 << "Five")
692 << -1 // no sorting
693 << QString() // no filter
694 << -1 // position
695 << 1 // count
696 << false // success
697 << (QStringList() // expectedProxy
698 << "One"
699 << "Two"
700 << "Three"
701 << "Four"
702 << "Five")
703 << (QStringList() // expectedSource
704 << "One"
705 << "Two"
706 << "Three"
707 << "Four"
708 << "Five");
709
710 QTest::newRow(dataTag: "remove three rows in the middle [no sorting/filter]")
711 << (QStringList()
712 << "One"
713 << "Two"
714 << "Three"
715 << "Four"
716 << "Five")
717 << -1 // no sorting
718 << QString() // no filter
719 << 1 // position
720 << 3 // count
721 << true // success
722 << (QStringList() // expectedProxy
723 << "One"
724 << "Five")
725 << (QStringList() // expectedSource
726 << "One"
727 << "Five");
728
729 QTest::newRow(dataTag: "remove one row in the middle [ascending sorting, no filter]")
730 << (QStringList()
731 << "1"
732 << "5"
733 << "2"
734 << "4"
735 << "3")
736 << static_cast<int>(Qt::AscendingOrder)
737 << QString() // no filter
738 << 2 // position
739 << 1 // count
740 << true // success
741 << (QStringList() // expectedProxy
742 << "1"
743 << "2"
744 << "4"
745 << "5")
746 << (QStringList() // expectedSource
747 << "1"
748 << "5"
749 << "2"
750 << "4");
751
752 QTest::newRow(dataTag: "remove two rows in the middle [ascending sorting, no filter]")
753 << (QStringList()
754 << "1"
755 << "5"
756 << "2"
757 << "4"
758 << "3")
759 << static_cast<int>(Qt::AscendingOrder)
760 << QString() // no filter
761 << 2 // position
762 << 2 // count
763 << true // success
764 << (QStringList() // expectedProxy
765 << "1"
766 << "2"
767 << "5")
768 << (QStringList() // expectedSource
769 << "1"
770 << "5"
771 << "2");
772
773 QTest::newRow(dataTag: "remove two rows in the middle [descending sorting, no filter]")
774 << (QStringList()
775 << "1"
776 << "5"
777 << "2"
778 << "4"
779 << "3")
780 << static_cast<int>(Qt::DescendingOrder)
781 << QString() // no filter
782 << 2 // position
783 << 2 // count
784 << true // success
785 << (QStringList() // expectedProxy
786 << "5"
787 << "4"
788 << "1")
789 << (QStringList() // expectedSource
790 << "1"
791 << "5"
792 << "4");
793
794 QTest::newRow(dataTag: "remove one row in the middle [no sorting, filter=5|2|3]")
795 << (QStringList()
796 << "1"
797 << "5"
798 << "2"
799 << "4"
800 << "3")
801 << -1 // no sorting
802 << QString("5|2|3")
803 << 1 // position
804 << 1 // count
805 << true // success
806 << (QStringList() // expectedProxy
807 << "5"
808 << "3")
809 << (QStringList() // expectedSource
810 << "1"
811 << "5"
812 << "4"
813 << "3");
814
815 QTest::newRow(dataTag: "remove all [ascending sorting, no filter]")
816 << (QStringList()
817 << "1"
818 << "5"
819 << "2"
820 << "4"
821 << "3")
822 << static_cast<int>(Qt::AscendingOrder)
823 << QString() // no filter
824 << 0 // position
825 << 5 // count
826 << true // success
827 << QStringList() // expectedProxy
828 << QStringList(); // expectedSource
829}
830
831void tst_QSortFilterProxyModel::removeRows()
832{
833 QFETCH(const QStringList, initial);
834 QFETCH(int, sortOrder);
835 QFETCH(QString, filter);
836 QFETCH(int, position);
837 QFETCH(int, count);
838 QFETCH(bool, success);
839 QFETCH(QStringList, expectedProxy);
840 QFETCH(QStringList, expectedSource);
841
842 QStandardItemModel model;
843 QSortFilterProxyModel proxy;
844 proxy.setSourceModel(&model);
845
846 // prepare model
847 for (const auto &s : initial)
848 model.appendRow(aitem: new QStandardItem(s));
849
850 if (sortOrder != -1)
851 proxy.sort(column: 0, order: static_cast<Qt::SortOrder>(sortOrder));
852
853 if (!filter.isEmpty())
854 setupFilter(model: &proxy, pattern: filter);
855
856 // remove the rows
857 QCOMPARE(proxy.removeRows(position, count, QModelIndex()), success);
858 QCOMPARE(model.rowCount(QModelIndex()), expectedSource.count());
859 QCOMPARE(proxy.rowCount(QModelIndex()), expectedProxy.count());
860
861 // make sure the model is correct after remove
862 for (int row = 0; row < model.rowCount(parent: QModelIndex()); ++row)
863 QCOMPARE(model.item(row)->text(), expectedSource.at(row));
864
865 // make sure the proxy is correct after remove
866 for (int row = 0; row < proxy.rowCount(parent: QModelIndex()); ++row) {
867 QModelIndex index = proxy.index(row, column: 0, parent: QModelIndex());
868 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), expectedProxy.at(row));
869 }
870}
871
872class MyFilteredColumnProxyModel : public QSortFilterProxyModel
873{
874 Q_OBJECT
875public:
876 MyFilteredColumnProxyModel(FilterType filterType, QObject *parent = nullptr) :
877 QSortFilterProxyModel(parent),
878 m_filterType(filterType)
879 { }
880
881protected:
882 bool filterAcceptsColumn(int sourceColumn, const QModelIndex &) const override
883 {
884 QString key = sourceModel()->headerData(section: sourceColumn, orientation: Qt::Horizontal).toString();
885 bool result = false;
886 switch (m_filterType) {
887 case FilterType::RegExp:
888 result = key.contains(rx: filterRegExp());
889 break;
890 case FilterType::RegularExpression:
891 result = key.contains(re: filterRegularExpression());
892 break;
893 }
894 return result;
895 }
896
897private:
898 FilterType m_filterType;
899};
900
901void tst_QSortFilterProxyModel::removeColumns_data()
902{
903 QTest::addColumn<QStringList>(name: "initial");
904 QTest::addColumn<QString>(name: "filter");
905 QTest::addColumn<int>(name: "position");
906 QTest::addColumn<int>(name: "count");
907 QTest::addColumn<bool>(name: "success");
908 QTest::addColumn<QStringList>(name: "expectedProxy");
909 QTest::addColumn<QStringList>(name: "expectedSource");
910
911 QTest::newRow(dataTag: "remove one column in the middle [no filter]")
912 << (QStringList()
913 << "1"
914 << "2"
915 << "3"
916 << "4"
917 << "5")
918 << QString() // no filter
919 << 2 // position
920 << 1 // count
921 << true // success
922 << (QStringList() // expectedProxy
923 << "1"
924 << "2"
925 << "4"
926 << "5")
927 << (QStringList() // expectedSource
928 << "1"
929 << "2"
930 << "4"
931 << "5");
932
933 QTest::newRow(dataTag: "remove one column in the end [no filter]")
934 << (QStringList()
935 << "1"
936 << "2"
937 << "3"
938 << "4"
939 << "5")
940 << QString() // no filter
941 << 4 // position
942 << 1 // count
943 << true // success
944 << (QStringList() // expectedProxy
945 << "1"
946 << "2"
947 << "3"
948 << "4")
949 << (QStringList() // expectedSource
950 << "1"
951 << "2"
952 << "3"
953 << "4");
954
955 QTest::newRow(dataTag: "remove one column past the end [no filter]")
956 << (QStringList()
957 << "1"
958 << "2"
959 << "3"
960 << "4"
961 << "5")
962 << QString() // no filter
963 << 5 // position
964 << 1 // count
965 << false // success
966 << (QStringList() // expectedProxy
967 << "1"
968 << "2"
969 << "3"
970 << "4"
971 << "5")
972 << (QStringList() // expectedSource
973 << "1"
974 << "2"
975 << "3"
976 << "4"
977 << "5");
978
979 QTest::newRow(dataTag: "remove column -1 [no filter]")
980 << (QStringList()
981 << "1"
982 << "2"
983 << "3"
984 << "4"
985 << "5")
986 << QString() // no filter
987 << -1 // position
988 << 1 // count
989 << false // success
990 << (QStringList() // expectedProxy
991 << "1"
992 << "2"
993 << "3"
994 << "4"
995 << "5")
996 << (QStringList() // expectedSource
997 << "1"
998 << "2"
999 << "3"
1000 << "4"
1001 << "5");
1002
1003 QTest::newRow(dataTag: "remove all columns [no filter]")
1004 << (QStringList()
1005 << "1"
1006 << "2"
1007 << "3"
1008 << "4"
1009 << "5")
1010 << QString() // no filter
1011 << 0 // position
1012 << 5 // count
1013 << true // success
1014 << QStringList() // expectedProxy
1015 << QStringList(); // expectedSource
1016
1017 QTest::newRow(dataTag: "remove one column in the middle [filter=1|3|5]")
1018 << (QStringList()
1019 << "1"
1020 << "2"
1021 << "3"
1022 << "4"
1023 << "5")
1024 << QString("1|3|5")
1025 << 1 // position
1026 << 1 // count
1027 << true // success
1028 << (QStringList() // expectedProxy
1029 << "1"
1030 << "5")
1031 << (QStringList() // expectedSource
1032 << "1"
1033 << "2"
1034 << "4"
1035 << "5");
1036
1037 QTest::newRow(dataTag: "remove one column in the end [filter=1|3|5]")
1038 << (QStringList()
1039 << "1"
1040 << "2"
1041 << "3"
1042 << "4"
1043 << "5")
1044 << QString("1|3|5")
1045 << 2 // position
1046 << 1 // count
1047 << true // success
1048 << (QStringList() // expectedProxy
1049 << "1"
1050 << "3")
1051 << (QStringList() // expectedSource
1052 << "1"
1053 << "2"
1054 << "3"
1055 << "4");
1056
1057 QTest::newRow(dataTag: "remove one column past the end [filter=1|3|5]")
1058 << (QStringList()
1059 << "1"
1060 << "2"
1061 << "3"
1062 << "4"
1063 << "5")
1064 << QString("1|3|5")
1065 << 3 // position
1066 << 1 // count
1067 << false // success
1068 << (QStringList() // expectedProxy
1069 << "1"
1070 << "3"
1071 << "5")
1072 << (QStringList() // expectedSource
1073 << "1"
1074 << "2"
1075 << "3"
1076 << "4"
1077 << "5");
1078
1079 QTest::newRow(dataTag: "remove all columns [filter=1|3|5]")
1080 << (QStringList()
1081 << "1"
1082 << "2"
1083 << "3"
1084 << "4"
1085 << "5")
1086 << QString("1|3|5")
1087 << 0 // position
1088 << 3 // count
1089 << true // success
1090 << QStringList() // expectedProxy
1091 << (QStringList() // expectedSource
1092 << "2"
1093 << "4");
1094}
1095
1096void tst_QSortFilterProxyModel::removeColumns()
1097{
1098 QFETCH(QStringList, initial);
1099 QFETCH(QString, filter);
1100 QFETCH(int, position);
1101 QFETCH(int, count);
1102 QFETCH(bool, success);
1103 QFETCH(QStringList, expectedProxy);
1104 QFETCH(QStringList, expectedSource);
1105
1106 QStandardItemModel model;
1107 MyFilteredColumnProxyModel proxy(m_filterType);
1108 proxy.setSourceModel(&model);
1109 if (!filter.isEmpty())
1110 setupFilter(model: &proxy, pattern: filter);
1111
1112 // prepare model
1113 model.setHorizontalHeaderLabels(initial);
1114
1115 // remove the columns
1116 QCOMPARE(proxy.removeColumns(position, count, QModelIndex()), success);
1117 QCOMPARE(model.columnCount(QModelIndex()), expectedSource.count());
1118 QCOMPARE(proxy.columnCount(QModelIndex()), expectedProxy.count());
1119
1120 // make sure the model is correct after remove
1121 for (int col = 0; col < model.columnCount(parent: QModelIndex()); ++col)
1122 QCOMPARE(model.horizontalHeaderItem(col)->text(), expectedSource.at(col));
1123
1124 // make sure the proxy is correct after remove
1125 for (int col = 0; col < proxy.columnCount(parent: QModelIndex()); ++col) {
1126 QCOMPARE(proxy.headerData(col, Qt::Horizontal, Qt::DisplayRole).toString(),
1127 expectedProxy.at(col));
1128 }
1129}
1130
1131void tst_QSortFilterProxyModel::filterColumns_data()
1132{
1133 QTest::addColumn<QString>(name: "pattern");
1134 QTest::addColumn<QStringList>(name: "initial");
1135 QTest::addColumn<bool>(name: "data");
1136
1137 QTest::newRow(dataTag: "all") << "a"
1138 << (QStringList()
1139 << "delta"
1140 << "yankee"
1141 << "bravo"
1142 << "lima")
1143 << true;
1144
1145 QTest::newRow(dataTag: "some") << "lie"
1146 << (QStringList()
1147 << "charlie"
1148 << "juliet"
1149 << "tango"
1150 << "hotel")
1151 << true;
1152
1153 QTest::newRow(dataTag: "nothing") << "zoo"
1154 << (QStringList()
1155 << "foxtrot"
1156 << "uniform"
1157 << "alpha"
1158 << "golf")
1159 << false;
1160}
1161
1162void tst_QSortFilterProxyModel::filterColumns()
1163{
1164 QFETCH(QString, pattern);
1165 QFETCH(QStringList, initial);
1166 QFETCH(bool, data);
1167 // prepare model
1168 m_model->setColumnCount(initial.count());
1169 m_model->setRowCount(1);
1170 QCOMPARE(m_model->columnCount(QModelIndex()), initial.count());
1171 QCOMPARE(m_model->rowCount(QModelIndex()), 1);
1172 // set data
1173 QCOMPARE(m_model->rowCount(QModelIndex()), 1);
1174 for (int col = 0; col < m_model->columnCount(parent: QModelIndex()); ++col) {
1175 QModelIndex index = m_model->index(row: 0, column: col, parent: QModelIndex());
1176 m_model->setData(index, value: initial.at(i: col), role: Qt::DisplayRole);
1177 }
1178 setupFilter(model: m_proxy, pattern);
1179
1180 m_proxy->setFilterKeyColumn(-1);
1181 // make sure the model is unchanged
1182 for (int col = 0; col < m_model->columnCount(parent: QModelIndex()); ++col) {
1183 QModelIndex index = m_model->index(row: 0, column: col, parent: QModelIndex());
1184 QCOMPARE(m_model->data(index, Qt::DisplayRole).toString(), initial.at(col));
1185 }
1186 // make sure the proxy is filtered
1187 QModelIndex index = m_proxy->index(row: 0, column: 0, parent: QModelIndex());
1188 QCOMPARE(index.isValid(), data);
1189}
1190
1191void tst_QSortFilterProxyModel::filter_data()
1192{
1193 QTest::addColumn<QString>(name: "pattern");
1194 QTest::addColumn<QStringList>(name: "initial");
1195 QTest::addColumn<QStringList>(name: "expected");
1196
1197 QTest::newRow(dataTag: "flat") << "e"
1198 << (QStringList()
1199 << "delta"
1200 << "yankee"
1201 << "bravo"
1202 << "lima"
1203 << "charlie"
1204 << "juliet"
1205 << "tango"
1206 << "hotel"
1207 << "uniform"
1208 << "alpha"
1209 << "echo"
1210 << "golf"
1211 << "quebec"
1212 << "foxtrot"
1213 << "india"
1214 << "romeo"
1215 << "november"
1216 << "oskar"
1217 << "zulu"
1218 << "kilo"
1219 << "whiskey"
1220 << "mike"
1221 << "papa"
1222 << "sierra"
1223 << "xray"
1224 << "viktor")
1225 << (QStringList()
1226 << "delta"
1227 << "yankee"
1228 << "charlie"
1229 << "juliet"
1230 << "hotel"
1231 << "echo"
1232 << "quebec"
1233 << "romeo"
1234 << "november"
1235 << "whiskey"
1236 << "mike"
1237 << "sierra");
1238}
1239
1240void tst_QSortFilterProxyModel::filter()
1241{
1242 QFETCH(QString, pattern);
1243 QFETCH(QStringList, initial);
1244 QFETCH(QStringList, expected);
1245
1246 // prepare model
1247 QVERIFY(m_model->insertRows(0, initial.count(), QModelIndex()));
1248 QCOMPARE(m_model->rowCount(QModelIndex()), initial.count());
1249 // set data
1250 QCOMPARE(m_model->columnCount(QModelIndex()), 1);
1251 for (int row = 0; row < m_model->rowCount(parent: QModelIndex()); ++row) {
1252 QModelIndex index = m_model->index(row, column: 0, parent: QModelIndex());
1253 m_model->setData(index, value: initial.at(i: row), role: Qt::DisplayRole);
1254 }
1255 setupFilter(model: m_proxy, pattern);
1256 // make sure the proxy is unfiltered
1257 QCOMPARE(m_proxy->columnCount(QModelIndex()), 1);
1258 QCOMPARE(m_proxy->rowCount(QModelIndex()), expected.count());
1259 // make sure the model is unchanged
1260 for (int row = 0; row < m_model->rowCount(parent: QModelIndex()); ++row) {
1261 QModelIndex index = m_model->index(row, column: 0, parent: QModelIndex());
1262 QCOMPARE(m_model->data(index, Qt::DisplayRole).toString(), initial.at(row));
1263 }
1264 // make sure the proxy is filtered
1265 for (int row = 0; row < m_proxy->rowCount(parent: QModelIndex()); ++row) {
1266 QModelIndex index = m_proxy->index(row, column: 0, parent: QModelIndex());
1267 QCOMPARE(m_proxy->data(index, Qt::DisplayRole).toString(), expected.at(row));
1268 }
1269}
1270
1271void tst_QSortFilterProxyModel::filterHierarchy_data()
1272{
1273 QTest::addColumn<QString>(name: "pattern");
1274 QTest::addColumn<QStringList>(name: "initial");
1275 QTest::addColumn<QStringList>(name: "expected");
1276
1277 QTest::newRow(dataTag: "flat") << ".*oo"
1278 << (QStringList()
1279 << "foo" << "boo" << "baz" << "moo" << "laa" << "haa")
1280 << (QStringList()
1281 << "foo" << "boo" << "moo");
1282
1283 QTest::newRow(dataTag: "simple hierarchy") << "b.*z"
1284 << (QStringList() << "baz" << "<" << "boz" << "<" << "moo" << ">" << ">")
1285 << (QStringList() << "baz" << "<" << "boz" << ">");
1286}
1287
1288void tst_QSortFilterProxyModel::filterHierarchy()
1289{
1290 QFETCH(QString, pattern);
1291 QFETCH(QStringList, initial);
1292 QFETCH(QStringList, expected);
1293 buildHierarchy(data: initial, model: m_model);
1294 setupFilter(model: m_proxy, pattern);
1295 checkHierarchy(data: initial, model: m_model);
1296 checkHierarchy(data: expected, model: m_proxy);
1297}
1298
1299void tst_QSortFilterProxyModel::buildHierarchy(const QStringList &l, QAbstractItemModel *m)
1300{
1301 int ind = 0;
1302 int row = 0;
1303 QStack<int> row_stack;
1304 QModelIndex parent;
1305 QStack<QModelIndex> parent_stack;
1306 for (int i = 0; i < l.count(); ++i) {
1307 QString token = l.at(i);
1308 if (token == QLatin1String("<")) { // start table
1309 ++ind;
1310 parent_stack.push(t: parent);
1311 row_stack.push(t: row);
1312 parent = m->index(row: row - 1, column: 0, parent);
1313 row = 0;
1314 QVERIFY(m->insertColumns(0, 1, parent)); // add column
1315 } else if (token == QLatin1String(">")) { // end table
1316 --ind;
1317 parent = parent_stack.pop();
1318 row = row_stack.pop();
1319 } else { // append row
1320 QVERIFY(m->insertRows(row, 1, parent));
1321 QModelIndex index = m->index(row, column: 0, parent);
1322 QVERIFY(index.isValid());
1323 m->setData(index, value: token, role: Qt::DisplayRole);
1324 ++row;
1325 }
1326 }
1327}
1328
1329void tst_QSortFilterProxyModel::checkHierarchy(const QStringList &l, const QAbstractItemModel *m)
1330{
1331 int row = 0;
1332 int indent = 0;
1333 QStack<int> row_stack;
1334 QModelIndex parent;
1335 QStack<QModelIndex> parent_stack;
1336 for (int i = 0; i < l.count(); ++i) {
1337 QString token = l.at(i);
1338 if (token == QLatin1String("<")) { // start table
1339 ++indent;
1340 parent_stack.push(t: parent);
1341 row_stack.push(t: row);
1342 parent = m->index(row: row - 1, column: 0, parent);
1343 QVERIFY(parent.isValid());
1344 row = 0;
1345 } else if (token == QLatin1String(">")) { // end table
1346 --indent;
1347 parent = parent_stack.pop();
1348 row = row_stack.pop();
1349 } else { // compare row
1350 QModelIndex index = m->index(row, column: 0, parent);
1351 QVERIFY(index.isValid());
1352 QString str = m->data(index, role: Qt::DisplayRole).toString();
1353 QCOMPARE(str, token);
1354 ++row;
1355 }
1356 }
1357}
1358
1359void tst_QSortFilterProxyModel::setupFilter(QSortFilterProxyModel *model, const QString& pattern)
1360{
1361 switch (m_filterType) {
1362 case FilterType::RegExp:
1363 model->setFilterRegExp(pattern);
1364 break;
1365 case FilterType::RegularExpression:
1366 model->setFilterRegularExpression(pattern);
1367 break;
1368 }
1369}
1370
1371class TestModel: public QAbstractTableModel
1372{
1373 Q_OBJECT
1374public:
1375 int rowCount(const QModelIndex &) const override { return 10000; }
1376 int columnCount(const QModelIndex &) const override { return 1; }
1377 QVariant data(const QModelIndex &index, int role) const override
1378 {
1379 if (role != Qt::DisplayRole)
1380 return QVariant();
1381 return QString::number(index.row());
1382 }
1383};
1384
1385void tst_QSortFilterProxyModel::filterTable()
1386{
1387 TestModel model;
1388 QSortFilterProxyModel filter;
1389 filter.setSourceModel(&model);
1390 setupFilter(model: &filter, pattern: QLatin1String("9"));
1391
1392 for (int i = 0; i < filter.rowCount(); ++i)
1393 QVERIFY(filter.data(filter.index(i, 0)).toString().contains(QLatin1Char('9')));
1394}
1395
1396void tst_QSortFilterProxyModel::insertAfterSelect()
1397{
1398 QStandardItemModel model(10, 2);
1399 for (int i = 0; i<10;i++)
1400 model.setData(index: model.index(row: i, column: 0), value: QVariant(i));
1401 QSortFilterProxyModel filter;
1402 filter.setSourceModel(&model);
1403 QTreeView view;
1404 view.setModel(&filter);
1405 view.show();
1406 QModelIndex firstIndex = filter.mapFromSource(sourceIndex: model.index(row: 0, column: 0, parent: QModelIndex()));
1407 QCOMPARE(firstIndex.model(), view.model());
1408 QVERIFY(firstIndex.isValid());
1409 int itemOffset = view.visualRect(index: firstIndex).width() / 2;
1410 QPoint p(itemOffset, 1);
1411 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
1412 QVERIFY(view.selectionModel()->selectedIndexes().size() > 0);
1413 model.insertRows(row: 5, count: 1, parent: QModelIndex());
1414 QVERIFY(view.selectionModel()->selectedIndexes().size() > 0); // Should still have a selection
1415}
1416
1417void tst_QSortFilterProxyModel::removeAfterSelect()
1418{
1419 QStandardItemModel model(10, 2);
1420 for (int i = 0; i<10;i++)
1421 model.setData(index: model.index(row: i, column: 0), value: QVariant(i));
1422 QSortFilterProxyModel filter;
1423 filter.setSourceModel(&model);
1424 QTreeView view;
1425 view.setModel(&filter);
1426 view.show();
1427 QModelIndex firstIndex = filter.mapFromSource(sourceIndex: model.index(row: 0, column: 0, parent: QModelIndex()));
1428 QCOMPARE(firstIndex.model(), view.model());
1429 QVERIFY(firstIndex.isValid());
1430 int itemOffset = view.visualRect(index: firstIndex).width() / 2;
1431 QPoint p(itemOffset, 1);
1432 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
1433 QVERIFY(view.selectionModel()->selectedIndexes().size() > 0);
1434 model.removeRows(row: 5, count: 1, parent: QModelIndex());
1435 QVERIFY(view.selectionModel()->selectedIndexes().size() > 0); // Should still have a selection
1436}
1437
1438void tst_QSortFilterProxyModel::filterCurrent()
1439{
1440 QStandardItemModel model(2, 1);
1441 model.setData(index: model.index(row: 0, column: 0), value: QString("AAA"));
1442 model.setData(index: model.index(row: 1, column: 0), value: QString("BBB"));
1443 QSortFilterProxyModel proxy;
1444 proxy.setSourceModel(&model);
1445 QTreeView view;
1446
1447 view.show();
1448 view.setModel(&proxy);
1449 QSignalSpy spy(view.selectionModel(), &QItemSelectionModel::currentChanged);
1450 QVERIFY(spy.isValid());
1451
1452 view.setCurrentIndex(proxy.index(row: 0, column: 0));
1453 QCOMPARE(spy.count(), 1);
1454 setupFilter(model: &proxy, pattern: QLatin1String("^B"));
1455 QCOMPARE(spy.count(), 2);
1456}
1457
1458void tst_QSortFilterProxyModel::filter_qtbug30662()
1459{
1460 QStringListModel model;
1461 QSortFilterProxyModel proxy;
1462 proxy.setSourceModel(&model);
1463
1464 // make sure the filter does not match any entry
1465 setupFilter(model: &proxy, pattern: QLatin1String("[0-9]+"));
1466
1467 QStringList slSource;
1468 slSource << "z" << "x" << "a" << "b";
1469
1470 proxy.setDynamicSortFilter(true);
1471 proxy.sort(column: 0);
1472 model.setStringList(slSource);
1473
1474 // without fix for QTBUG-30662 this will make all entries visible - but unsorted
1475 setupFilter(model: &proxy, pattern: QLatin1String("[a-z]+"));
1476
1477 QStringList slResult;
1478 for (int i = 0; i < proxy.rowCount(); ++i)
1479 slResult.append(t: proxy.index(row: i, column: 0).data().toString());
1480
1481 slSource.sort();
1482 QCOMPARE(slResult, slSource);
1483}
1484
1485void tst_QSortFilterProxyModel::changeSourceLayout()
1486{
1487 QStandardItemModel model(2, 1);
1488 model.setData(index: model.index(row: 0, column: 0), value: QString("b"));
1489 model.setData(index: model.index(row: 1, column: 0), value: QString("a"));
1490 QSortFilterProxyModel proxy;
1491 proxy.setSourceModel(&model);
1492
1493 QVector<QPersistentModelIndex> persistentSourceIndexes;
1494 QVector<QPersistentModelIndex> persistentProxyIndexes;
1495 for (int row = 0; row < model.rowCount(); ++row) {
1496 persistentSourceIndexes.append(t: model.index(row, column: 0));
1497 persistentProxyIndexes.append(t: proxy.index(row, column: 0));
1498 }
1499
1500 // change layout of source model
1501 model.sort(column: 0, order: Qt::AscendingOrder);
1502
1503 for (int row = 0; row < model.rowCount(); ++row) {
1504 QCOMPARE(persistentProxyIndexes.at(row).row(),
1505 persistentSourceIndexes.at(row).row());
1506 }
1507}
1508
1509void tst_QSortFilterProxyModel::changeSourceLayoutFilteredOut()
1510{
1511 QStandardItemModel model(2, 1);
1512 model.setData(index: model.index(row: 0, column: 0), value: QString("b"));
1513 model.setData(index: model.index(row: 1, column: 0), value: QString("a"));
1514 QSortFilterProxyModel proxy;
1515 proxy.setSourceModel(&model);
1516
1517 int beforeSortFilter = proxy.rowCount();
1518
1519 QSignalSpy removeSpy(&proxy, &QSortFilterProxyModel::rowsRemoved);
1520 // Filter everything out
1521 setupFilter(model: &proxy, pattern: QLatin1String("c"));
1522
1523 QCOMPARE(removeSpy.count(), 1);
1524 QCOMPARE(0, proxy.rowCount());
1525
1526 // change layout of source model
1527 model.sort(column: 0, order: Qt::AscendingOrder);
1528
1529 QSignalSpy insertSpy(&proxy, &QSortFilterProxyModel::rowsInserted);
1530 // Remove filter; we expect an insert
1531 setupFilter(model: &proxy, pattern: "");
1532
1533 QCOMPARE(insertSpy.count(), 1);
1534 QCOMPARE(beforeSortFilter, proxy.rowCount());
1535}
1536
1537void tst_QSortFilterProxyModel::removeSourceRows_data()
1538{
1539 QTest::addColumn<QStringList>(name: "sourceItems");
1540 QTest::addColumn<int>(name: "start");
1541 QTest::addColumn<int>(name: "count");
1542 QTest::addColumn<int>(name: "sortOrder");
1543 QTest::addColumn<IntPairList>(name: "expectedRemovedProxyIntervals");
1544 QTest::addColumn<QStringList>(name: "expectedProxyItems");
1545
1546 QTest::newRow(dataTag: "remove one, no sorting")
1547 << (QStringList() << "a" << "b") // sourceItems
1548 << 0 // start
1549 << 1 // count
1550 << -1 // sortOrder (no sorting)
1551 << (IntPairList() << IntPair(0, 0)) // expectedRemovedIntervals
1552 << (QStringList() << "b") // expectedProxyItems
1553 ;
1554 QTest::newRow(dataTag: "remove one, ascending sort (same order)")
1555 << (QStringList() << "a" << "b") // sourceItems
1556 << 0 // start
1557 << 1 // count
1558 << static_cast<int>(Qt::AscendingOrder) // sortOrder
1559 << (IntPairList() << IntPair(0, 0)) // expectedRemovedIntervals
1560 << (QStringList() << "b") // expectedProxyItems
1561 ;
1562 QTest::newRow(dataTag: "remove one, ascending sort (reverse order)")
1563 << (QStringList() << "b" << "a") // sourceItems
1564 << 0 // start
1565 << 1 // count
1566 << static_cast<int>(Qt::AscendingOrder) // sortOrder
1567 << (IntPairList() << IntPair(1, 1)) // expectedRemovedIntervals
1568 << (QStringList() << "a") // expectedProxyItems
1569 ;
1570 QTest::newRow(dataTag: "remove two, multiple proxy intervals")
1571 << (QStringList() << "c" << "d" << "a" << "b") // sourceItems
1572 << 1 // start
1573 << 2 // count
1574 << static_cast<int>(Qt::AscendingOrder) // sortOrder
1575 << (IntPairList() << IntPair(3, 3) << IntPair(0, 0)) // expectedRemovedIntervals
1576 << (QStringList() << "b" << "c") // expectedProxyItems
1577 ;
1578 QTest::newRow(dataTag: "remove three, multiple proxy intervals")
1579 << (QStringList() << "b" << "d" << "f" << "a" << "c" << "e") // sourceItems
1580 << 3 // start
1581 << 3 // count
1582 << static_cast<int>(Qt::AscendingOrder) // sortOrder
1583 << (IntPairList() << IntPair(4, 4) << IntPair(2, 2) << IntPair(0, 0)) // expectedRemovedIntervals
1584 << (QStringList() << "b" << "d" << "f") // expectedProxyItems
1585 ;
1586 QTest::newRow(dataTag: "remove all, single proxy intervals")
1587 << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f") // sourceItems
1588 << 0 // start
1589 << 6 // count
1590 << static_cast<int>(Qt::DescendingOrder) // sortOrder
1591 << (IntPairList() << IntPair(0, 5)) // expectedRemovedIntervals
1592 << QStringList() // expectedProxyItems
1593 ;
1594}
1595
1596// Check that correct proxy model rows are removed when rows are removed
1597// from the source model
1598void tst_QSortFilterProxyModel::removeSourceRows()
1599{
1600 QFETCH(QStringList, sourceItems);
1601 QFETCH(int, start);
1602 QFETCH(int, count);
1603 QFETCH(int, sortOrder);
1604 QFETCH(IntPairList, expectedRemovedProxyIntervals);
1605 QFETCH(QStringList, expectedProxyItems);
1606
1607 QStandardItemModel model;
1608 QSortFilterProxyModel proxy;
1609
1610 proxy.setSourceModel(&model);
1611 model.insertColumns(column: 0, count: 1);
1612 model.insertRows(row: 0, count: sourceItems.count());
1613
1614 for (int i = 0; i < sourceItems.count(); ++i) {
1615 QModelIndex sindex = model.index(row: i, column: 0, parent: QModelIndex());
1616 model.setData(index: sindex, value: sourceItems.at(i), role: Qt::DisplayRole);
1617 QModelIndex pindex = proxy.index(row: i, column: 0, parent: QModelIndex());
1618 QCOMPARE(proxy.data(pindex, Qt::DisplayRole), model.data(sindex, Qt::DisplayRole));
1619 }
1620
1621 if (sortOrder != -1)
1622 proxy.sort(column: 0, order: static_cast<Qt::SortOrder>(sortOrder));
1623 (void)proxy.rowCount(parent: QModelIndex()); // force mapping
1624
1625 QSignalSpy removeSpy(&proxy, &QSortFilterProxyModel::rowsRemoved);
1626 QSignalSpy insertSpy(&proxy, &QSortFilterProxyModel::rowsInserted);
1627 QSignalSpy aboutToRemoveSpy(&proxy, &QSortFilterProxyModel::rowsAboutToBeRemoved);
1628 QSignalSpy aboutToInsertSpy(&proxy, &QSortFilterProxyModel::rowsAboutToBeInserted);
1629
1630 QVERIFY(removeSpy.isValid());
1631 QVERIFY(insertSpy.isValid());
1632 QVERIFY(aboutToRemoveSpy.isValid());
1633 QVERIFY(aboutToInsertSpy.isValid());
1634
1635 model.removeRows(row: start, count, parent: QModelIndex());
1636
1637 QCOMPARE(aboutToRemoveSpy.count(), expectedRemovedProxyIntervals.count());
1638 for (int i = 0; i < aboutToRemoveSpy.count(); ++i) {
1639 const auto &args = aboutToRemoveSpy.at(i);
1640 QCOMPARE(args.at(1).type(), QVariant::Int);
1641 QCOMPARE(args.at(2).type(), QVariant::Int);
1642 QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first);
1643 QCOMPARE(args.at(2).toInt(), expectedRemovedProxyIntervals.at(i).second);
1644 }
1645 QCOMPARE(removeSpy.count(), expectedRemovedProxyIntervals.count());
1646 for (int i = 0; i < removeSpy.count(); ++i) {
1647 const auto &args = removeSpy.at(i);
1648 QCOMPARE(args.at(1).type(), QVariant::Int);
1649 QCOMPARE(args.at(2).type(), QVariant::Int);
1650 QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first);
1651 QCOMPARE(args.at(2).toInt(), expectedRemovedProxyIntervals.at(i).second);
1652 }
1653
1654 QCOMPARE(insertSpy.count(), 0);
1655 QCOMPARE(aboutToInsertSpy.count(), 0);
1656
1657 QCOMPARE(proxy.rowCount(QModelIndex()), expectedProxyItems.count());
1658 for (int i = 0; i < expectedProxyItems.count(); ++i) {
1659 QModelIndex pindex = proxy.index(row: i, column: 0, parent: QModelIndex());
1660 QCOMPARE(proxy.data(pindex, Qt::DisplayRole).toString(), expectedProxyItems.at(i));
1661 }
1662}
1663
1664void tst_QSortFilterProxyModel::insertSourceRows_data()
1665{
1666 QTest::addColumn<QStringList>(name: "sourceItems");
1667 QTest::addColumn<int>(name: "start");
1668 QTest::addColumn<QStringList>(name: "newItems");
1669 QTest::addColumn<Qt::SortOrder>(name: "sortOrder");
1670 QTest::addColumn<QStringList>(name: "proxyItems");
1671
1672 QTest::newRow(dataTag: "insert (1)")
1673 << (QStringList() << "c" << "b") // sourceItems
1674 << 1 // start
1675 << (QStringList() << "a") // newItems
1676 << Qt::AscendingOrder // sortOrder
1677 << (QStringList() << "a" << "b" << "c") // proxyItems
1678 ;
1679
1680 QTest::newRow(dataTag: "insert (2)")
1681 << (QStringList() << "d" << "b" << "c") // sourceItems
1682 << 3 // start
1683 << (QStringList() << "a") // newItems
1684 << Qt::DescendingOrder // sortOrder
1685 << (QStringList() << "d" << "c" << "b" << "a") // proxyItems
1686 ;
1687}
1688
1689// Check that rows are inserted at correct position in proxy model when
1690// rows are inserted into the source model
1691void tst_QSortFilterProxyModel::insertSourceRows()
1692{
1693 QFETCH(QStringList, sourceItems);
1694 QFETCH(int, start);
1695 QFETCH(QStringList, newItems);
1696 QFETCH(Qt::SortOrder, sortOrder);
1697 QFETCH(QStringList, proxyItems);
1698
1699 QStandardItemModel model;
1700 QSortFilterProxyModel proxy;
1701 proxy.setDynamicSortFilter(true);
1702
1703 proxy.setSourceModel(&model);
1704 model.insertColumns(column: 0, count: 1);
1705 model.insertRows(row: 0, count: sourceItems.count());
1706
1707 for (int i = 0; i < sourceItems.count(); ++i) {
1708 QModelIndex index = model.index(row: i, column: 0, parent: QModelIndex());
1709 model.setData(index, value: sourceItems.at(i), role: Qt::DisplayRole);
1710 }
1711
1712 proxy.sort(column: 0, order: sortOrder);
1713 (void)proxy.rowCount(parent: QModelIndex()); // force mapping
1714
1715 model.insertRows(row: start, count: newItems.size(), parent: QModelIndex());
1716
1717 QCOMPARE(proxy.rowCount(QModelIndex()), proxyItems.count());
1718 for (int i = 0; i < newItems.count(); ++i) {
1719 QModelIndex index = model.index(row: start + i, column: 0, parent: QModelIndex());
1720 model.setData(index, value: newItems.at(i), role: Qt::DisplayRole);
1721 }
1722
1723 for (int i = 0; i < proxyItems.count(); ++i) {
1724 QModelIndex index = proxy.index(row: i, column: 0, parent: QModelIndex());
1725 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), proxyItems.at(i));
1726 }
1727}
1728
1729void tst_QSortFilterProxyModel::changeFilter_data()
1730{
1731 QTest::addColumn<QStringList>(name: "sourceItems");
1732 QTest::addColumn<Qt::SortOrder>(name: "sortOrder");
1733 QTest::addColumn<QString>(name: "initialFilter");
1734 QTest::addColumn<IntPairList>(name: "initialRemoveIntervals");
1735 QTest::addColumn<QStringList>(name: "initialProxyItems");
1736 QTest::addColumn<QString>(name: "finalFilter");
1737 QTest::addColumn<IntPairList>(name: "finalRemoveIntervals");
1738 QTest::addColumn<IntPairList>(name: "insertIntervals");
1739 QTest::addColumn<QStringList>(name: "finalProxyItems");
1740
1741 QTest::newRow(dataTag: "filter (1)")
1742 << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f") // sourceItems
1743 << Qt::AscendingOrder // sortOrder
1744 << "a|b|c" // initialFilter
1745 << (IntPairList() << IntPair(3, 5)) // initialRemoveIntervals
1746 << (QStringList() << "a" << "b" << "c") // initialProxyItems
1747 << "b|d|f" // finalFilter
1748 << (IntPairList() << IntPair(2, 2) << IntPair(0, 0)) // finalRemoveIntervals
1749 << (IntPairList() << IntPair(1, 2)) // insertIntervals
1750 << (QStringList() << "b" << "d" << "f") // finalProxyItems
1751 ;
1752
1753 QTest::newRow(dataTag: "filter (2)")
1754 << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f") // sourceItems
1755 << Qt::AscendingOrder // sortOrder
1756 << "a|c|e" // initialFilter
1757 << (IntPairList() << IntPair(5, 5) << IntPair(3, 3) << IntPair(1, 1)) // initialRemoveIntervals
1758 << (QStringList() << "a" << "c" << "e") // initialProxyItems
1759 << "" // finalFilter
1760 << IntPairList() // finalRemoveIntervals
1761 << (IntPairList() << IntPair(3, 3) << IntPair(2, 2) << IntPair(1, 1)) // insertIntervals
1762 << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f") // finalProxyItems
1763 ;
1764
1765 QTest::newRow(dataTag: "filter (3)")
1766 << (QStringList() << "a" << "b" << "c") // sourceItems
1767 << Qt::AscendingOrder // sortOrder
1768 << "a" // initialFilter
1769 << (IntPairList() << IntPair(1, 2)) // initialRemoveIntervals
1770 << (QStringList() << "a") // initialProxyItems
1771 << "a" // finalFilter
1772 << IntPairList() // finalRemoveIntervals
1773 << IntPairList() // insertIntervals
1774 << (QStringList() << "a") // finalProxyItems
1775 ;
1776}
1777
1778// Check that rows are added/removed when filter changes
1779void tst_QSortFilterProxyModel::changeFilter()
1780{
1781 QFETCH(QStringList, sourceItems);
1782 QFETCH(Qt::SortOrder, sortOrder);
1783 QFETCH(QString, initialFilter);
1784 QFETCH(IntPairList, initialRemoveIntervals);
1785 QFETCH(QStringList, initialProxyItems);
1786 QFETCH(QString, finalFilter);
1787 QFETCH(IntPairList, finalRemoveIntervals);
1788 QFETCH(IntPairList, insertIntervals);
1789 QFETCH(QStringList, finalProxyItems);
1790
1791 QStandardItemModel model;
1792 QSortFilterProxyModel proxy;
1793
1794 proxy.setSourceModel(&model);
1795 model.insertColumns(column: 0, count: 1);
1796 model.insertRows(row: 0, count: sourceItems.count());
1797
1798 for (int i = 0; i < sourceItems.count(); ++i) {
1799 QModelIndex index = model.index(row: i, column: 0, parent: QModelIndex());
1800 model.setData(index, value: sourceItems.at(i), role: Qt::DisplayRole);
1801 }
1802
1803 proxy.sort(column: 0, order: sortOrder);
1804 (void)proxy.rowCount(parent: QModelIndex()); // force mapping
1805
1806 QSignalSpy initialRemoveSpy(&proxy, &QSortFilterProxyModel::rowsRemoved);
1807 QSignalSpy initialInsertSpy(&proxy, &QSortFilterProxyModel::rowsInserted);
1808
1809 QVERIFY(initialRemoveSpy.isValid());
1810 QVERIFY(initialInsertSpy.isValid());
1811
1812 setupFilter(model: &proxy, pattern: initialFilter);
1813
1814 QCOMPARE(initialRemoveSpy.count(), initialRemoveIntervals.count());
1815 QCOMPARE(initialInsertSpy.count(), 0);
1816 for (int i = 0; i < initialRemoveSpy.count(); ++i) {
1817 const auto &args = initialRemoveSpy.at(i);
1818 QCOMPARE(args.at(1).type(), QVariant::Int);
1819 QCOMPARE(args.at(2).type(), QVariant::Int);
1820 QCOMPARE(args.at(1).toInt(), initialRemoveIntervals.at(i).first);
1821 QCOMPARE(args.at(2).toInt(), initialRemoveIntervals.at(i).second);
1822 }
1823
1824 QCOMPARE(proxy.rowCount(QModelIndex()), initialProxyItems.count());
1825 for (int i = 0; i < initialProxyItems.count(); ++i) {
1826 QModelIndex index = proxy.index(row: i, column: 0, parent: QModelIndex());
1827 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), initialProxyItems.at(i));
1828 }
1829
1830 QSignalSpy finalRemoveSpy(&proxy, &QSortFilterProxyModel::rowsRemoved);
1831 QSignalSpy finalInsertSpy(&proxy, &QSortFilterProxyModel::rowsInserted);
1832
1833 QVERIFY(finalRemoveSpy.isValid());
1834 QVERIFY(finalInsertSpy.isValid());
1835
1836 setupFilter(model: &proxy, pattern: finalFilter);
1837
1838 QCOMPARE(finalRemoveSpy.count(), finalRemoveIntervals.count());
1839 for (int i = 0; i < finalRemoveSpy.count(); ++i) {
1840 const auto &args = finalRemoveSpy.at(i);
1841 QCOMPARE(args.at(1).type(), QVariant::Int);
1842 QCOMPARE(args.at(2).type(), QVariant::Int);
1843 QCOMPARE(args.at(1).toInt(), finalRemoveIntervals.at(i).first);
1844 QCOMPARE(args.at(2).toInt(), finalRemoveIntervals.at(i).second);
1845 }
1846
1847 QCOMPARE(finalInsertSpy.count(), insertIntervals.count());
1848 for (int i = 0; i < finalInsertSpy.count(); ++i) {
1849 const auto &args = finalInsertSpy.at(i);
1850 QCOMPARE(args.at(1).type(), QVariant::Int);
1851 QCOMPARE(args.at(2).type(), QVariant::Int);
1852 QCOMPARE(args.at(1).toInt(), insertIntervals.at(i).first);
1853 QCOMPARE(args.at(2).toInt(), insertIntervals.at(i).second);
1854 }
1855
1856 QCOMPARE(proxy.rowCount(QModelIndex()), finalProxyItems.count());
1857 for (int i = 0; i < finalProxyItems.count(); ++i) {
1858 QModelIndex index = proxy.index(row: i, column: 0, parent: QModelIndex());
1859 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), finalProxyItems.at(i));
1860 }
1861}
1862
1863void tst_QSortFilterProxyModel::changeSourceData_data()
1864{
1865 QTest::addColumn<QStringList>(name: "sourceItems");
1866 QTest::addColumn<Qt::SortOrder>(name: "sortOrder");
1867 QTest::addColumn<QString>(name: "filter");
1868 QTest::addColumn<QStringList>(name: "expectedInitialProxyItems");
1869 QTest::addColumn<bool>(name: "dynamic");
1870 QTest::addColumn<int>(name: "row");
1871 QTest::addColumn<QString>(name: "newValue");
1872 QTest::addColumn<IntPairList>(name: "removeIntervals");
1873 QTest::addColumn<IntPairList>(name: "insertIntervals");
1874 QTest::addColumn<int>(name: "expectedDataChangedRow"); // -1 if no dataChanged signal expected
1875 QTest::addColumn<bool>(name: "expectedLayoutChanged");
1876 QTest::addColumn<QStringList>(name: "proxyItems");
1877
1878 QTest::newRow(dataTag: "move_to_end_ascending")
1879 << (QStringList() << "c" << "b" << "a") // sourceItems
1880 << Qt::AscendingOrder // sortOrder
1881 << "" // filter
1882 << (QStringList() << "a" << "b" << "c") // expectedInitialProxyItems
1883 << true // dynamic
1884 << 2 // row
1885 << "z" // newValue
1886 << IntPairList() // removeIntervals
1887 << IntPairList() // insertIntervals
1888 << 2 // dataChanged(row 2) is emitted, see comment "Make sure we also emit dataChanged for the rows" in the source code (unclear why, though)
1889 << true // layoutChanged
1890 << (QStringList() << "b" << "c" << "z") // proxyItems
1891 ;
1892
1893 QTest::newRow(dataTag: "move_to_end_descending")
1894 << (QStringList() << "b" << "c" << "z") // sourceItems
1895 << Qt::DescendingOrder // sortOrder
1896 << "" // filter
1897 << (QStringList() << "z" << "c" << "b") // expectedInitialProxyItems
1898 << true // dynamic
1899 << 1 // row
1900 << "a" // newValue
1901 << IntPairList() // removeIntervals
1902 << IntPairList() // insertIntervals
1903 << 2 // dataChanged(row 2) is emitted, see comment "Make sure we also emit dataChanged for the rows" in the source code (unclear why, though)
1904 << true // layoutChanged
1905 << (QStringList() << "z" << "b" << "a") // proxyItems
1906 ;
1907
1908 QTest::newRow(dataTag: "no_op_change")
1909 << (QStringList() << "a" << "b") // sourceItems
1910 << Qt::DescendingOrder // sortOrder
1911 << "" // filter
1912 << (QStringList() << "b" << "a") // expectedInitialProxyItems
1913 << true // dynamic
1914 << 0 // row
1915 << "a" // newValue
1916 << IntPairList() // removeIntervals
1917 << IntPairList() // insertIntervals
1918 << -1 // no dataChanged signal
1919 << false // layoutChanged
1920 << (QStringList() << "b" << "a") // proxyItems
1921 ;
1922
1923 QTest::newRow(dataTag: "no_effect_on_filtering")
1924 << (QStringList() << "a" << "b") // sourceItems
1925 << Qt::AscendingOrder // sortOrder
1926 << "" // filter
1927 << (QStringList() << "a" << "b") // expectedInitialProxyItems
1928 << true // dynamic
1929 << 1 // row
1930 << "z" // newValue
1931 << IntPairList() // removeIntervals
1932 << IntPairList() // insertIntervals
1933 << 1 // expectedDataChangedRow
1934 << false // layoutChanged
1935 << (QStringList() << "a" << "z") // proxyItems
1936 ;
1937
1938 QTest::newRow(dataTag: "filtered_out_value_stays_out")
1939 << (QStringList() << "a" << "b" << "c" << "d") // sourceItems
1940 << Qt::AscendingOrder // sortOrder
1941 << "a|c" // filter
1942 << (QStringList() << "a" << "c") // expectedInitialProxyItems
1943 << true // dynamic
1944 << 1 // row
1945 << "x" // newValue
1946 << IntPairList() // removeIntervals
1947 << IntPairList() // insertIntervals
1948 << -1 // no dataChanged signal
1949 << false // layoutChanged
1950 << (QStringList() << "a" << "c") // proxyItems
1951 ;
1952
1953 QTest::newRow(dataTag: "filtered_out_now_matches")
1954 << (QStringList() << "a" << "b" << "c" << "d") // sourceItems
1955 << Qt::AscendingOrder // sortOrder
1956 << "a|c|x" // filter
1957 << (QStringList() << "a" << "c") // expectedInitialProxyItems
1958 << true // dynamic
1959 << 1 // row
1960 << "x" // newValue
1961 << IntPairList() // removeIntervals
1962 << (IntPairList() << IntPair(2, 2)) // insertIntervals
1963 << -1 // no dataChanged signal
1964 << false // layoutChanged
1965 << (QStringList() << "a" << "c" << "x") // proxyItems
1966 ;
1967
1968 QTest::newRow(dataTag: "value_is_now_filtered_out")
1969 << (QStringList() << "a" << "b" << "c" << "d") // sourceItems
1970 << Qt::AscendingOrder // sortOrder
1971 << "a|c" // filter
1972 << (QStringList() << "a" << "c") // expectedInitialProxyItems
1973 << true // dynamic
1974 << 2 // row
1975 << "x" // newValue
1976 << (IntPairList() << IntPair(1, 1)) // removeIntervals
1977 << IntPairList() // insertIntervals
1978 << -1 // no dataChanged signal
1979 << false // layoutChanged
1980 << (QStringList() << "a") // proxyItems
1981 ;
1982
1983 QTest::newRow(dataTag: "non_dynamic_filter_does_not_update_sort")
1984 << (QStringList() << "c" << "b" << "a") // sourceItems
1985 << Qt::AscendingOrder // sortOrder
1986 << "" // filter
1987 << (QStringList() << "a" << "b" << "c") // expectedInitialProxyItems
1988 << false // dynamic
1989 << 2 // row
1990 << "x" // newValue
1991 << IntPairList() // removeIntervals
1992 << IntPairList() // insertIntervals
1993 << 0 // expectedDataChangedRow
1994 << false // layoutChanged
1995 << (QStringList() << "x" << "b" << "c") // proxyItems
1996 ;
1997}
1998
1999void tst_QSortFilterProxyModel::changeSourceData()
2000{
2001 QFETCH(QStringList, sourceItems);
2002 QFETCH(Qt::SortOrder, sortOrder);
2003 QFETCH(QString, filter);
2004 QFETCH(QStringList, expectedInitialProxyItems);
2005 QFETCH(bool, dynamic);
2006 QFETCH(int, row);
2007 QFETCH(QString, newValue);
2008 QFETCH(IntPairList, removeIntervals);
2009 QFETCH(IntPairList, insertIntervals);
2010 QFETCH(int, expectedDataChangedRow);
2011 QFETCH(bool, expectedLayoutChanged);
2012 QFETCH(QStringList, proxyItems);
2013
2014 QStandardItemModel model;
2015 QSortFilterProxyModel proxy;
2016
2017 proxy.setDynamicSortFilter(dynamic);
2018 proxy.setSourceModel(&model);
2019 model.insertColumns(column: 0, count: 1);
2020 model.insertRows(row: 0, count: sourceItems.count());
2021
2022 for (int i = 0; i < sourceItems.count(); ++i) {
2023 QModelIndex index = model.index(row: i, column: 0, parent: QModelIndex());
2024 model.setData(index, value: sourceItems.at(i), role: Qt::DisplayRole);
2025 }
2026
2027 proxy.sort(column: 0, order: sortOrder);
2028 (void)proxy.rowCount(parent: QModelIndex()); // force mapping
2029
2030 setupFilter(model: &proxy, pattern: filter);
2031
2032 QCOMPARE(proxy.rowCount(), expectedInitialProxyItems.count());
2033 for (int i = 0; i < expectedInitialProxyItems.count(); ++i) {
2034 const QModelIndex index = proxy.index(row: i, column: 0, parent: QModelIndex());
2035 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), expectedInitialProxyItems.at(i));
2036 }
2037
2038 QSignalSpy removeSpy(&proxy, &QSortFilterProxyModel::rowsRemoved);
2039 QSignalSpy insertSpy(&proxy, &QSortFilterProxyModel::rowsInserted);
2040 QSignalSpy dataChangedSpy(&proxy, &QSortFilterProxyModel::dataChanged);
2041 QSignalSpy layoutChangedSpy(&proxy, &QSortFilterProxyModel::layoutChanged);
2042
2043 QVERIFY(removeSpy.isValid());
2044 QVERIFY(insertSpy.isValid());
2045 QVERIFY(dataChangedSpy.isValid());
2046 QVERIFY(layoutChangedSpy.isValid());
2047
2048 {
2049 QModelIndex index = model.index(row, column: 0, parent: QModelIndex());
2050 model.setData(index, value: newValue, role: Qt::DisplayRole);
2051 }
2052
2053 QCOMPARE(removeSpy.count(), removeIntervals.count());
2054 for (int i = 0; i < removeSpy.count(); ++i) {
2055 const auto &args = removeSpy.at(i);
2056 QCOMPARE(args.at(1).type(), QVariant::Int);
2057 QCOMPARE(args.at(2).type(), QVariant::Int);
2058 QCOMPARE(args.at(1).toInt(), removeIntervals.at(i).first);
2059 QCOMPARE(args.at(2).toInt(), removeIntervals.at(i).second);
2060 }
2061
2062 QCOMPARE(insertSpy.count(), insertIntervals.count());
2063 for (int i = 0; i < insertSpy.count(); ++i) {
2064 const auto &args = insertSpy.at(i);
2065 QCOMPARE(args.at(1).type(), QVariant::Int);
2066 QCOMPARE(args.at(2).type(), QVariant::Int);
2067 QCOMPARE(args.at(1).toInt(), insertIntervals.at(i).first);
2068 QCOMPARE(args.at(2).toInt(), insertIntervals.at(i).second);
2069 }
2070
2071 QCOMPARE(proxy.rowCount(QModelIndex()), proxyItems.count());
2072 for (int i = 0; i < proxyItems.count(); ++i) {
2073 QModelIndex index = proxy.index(row: i, column: 0, parent: QModelIndex());
2074 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), proxyItems.at(i));
2075 }
2076
2077 if (expectedDataChangedRow == -1) {
2078 QCOMPARE(dataChangedSpy.count(), 0);
2079 } else {
2080 QCOMPARE(dataChangedSpy.count(), 1);
2081 const QModelIndex idx = dataChangedSpy.at(i: 0).at(i: 0).value<QModelIndex>();
2082 QCOMPARE(idx.row(), expectedDataChangedRow);
2083 QCOMPARE(idx.column(), 0);
2084 }
2085
2086 QCOMPARE(layoutChangedSpy.count(), expectedLayoutChanged ? 1 : 0);
2087}
2088
2089// Checks that the model is a table, and that each and every row is like this:
2090// i-th row: ( rows.at(i), i )
2091static void checkSortedTableModel(const QAbstractItemModel *model, const QStringList &rows)
2092{
2093 QCOMPARE(model->rowCount(), rows.length());
2094 QCOMPARE(model->columnCount(), 2);
2095
2096 for (int row = 0; row < model->rowCount(); ++row) {
2097 const QString column0 = model->index(row, column: 0).data().toString();
2098 const int column1 = model->index(row, column: 1).data().toString().toInt();
2099
2100 QCOMPARE(column0, rows.at(row));
2101 QCOMPARE(column1, row);
2102 }
2103}
2104
2105void tst_QSortFilterProxyModel::changeSourceDataKeepsStableSorting_qtbug1548()
2106{
2107 // Check that emitting dataChanged from the source model
2108 // for a change of a role which is not the sorting role
2109 // doesn't alter the sorting. In this case, we sort on the DisplayRole,
2110 // and play with other roles.
2111
2112 const QStringList rows({"a", "b", "b", "b", "c", "c", "x"});
2113
2114 // Build a table of pairs (string, #row) in each row
2115 QStandardItemModel model(0, 2);
2116
2117 for (int rowNumber = 0; rowNumber < rows.length(); ++rowNumber) {
2118 QStandardItem *column0 = new QStandardItem(rows.at(i: rowNumber));
2119 column0->setCheckable(true);
2120 column0->setCheckState(Qt::Unchecked);
2121
2122 QStandardItem *column1 = new QStandardItem(QString::number(rowNumber));
2123 model.appendRow(items: {column0, column1});
2124 }
2125
2126 checkSortedTableModel(model: &model, rows);
2127
2128 // Build the proxy model
2129 QSortFilterProxyModel proxy;
2130 proxy.setSourceModel(&model);
2131 proxy.setDynamicSortFilter(true);
2132 proxy.sort(column: 0);
2133
2134 // The proxy is now sorted by the first column, check that the sorting
2135 // * is correct (the input is already sorted, so it must not have changed)
2136 // * was stable (by looking at the second column)
2137 checkSortedTableModel(model: &model, rows);
2138
2139 // Change the check status of an item. That must not break the stable sorting
2140 // changes the middle "b"
2141 model.item(row: 2)->setCheckState(Qt::Checked);
2142 checkSortedTableModel(model: &model, rows);
2143
2144 // changes the starting "a"
2145 model.item(row: 0)->setCheckState(Qt::Checked);
2146 checkSortedTableModel(model: &model, rows);
2147
2148 // change the background color of the first "c"
2149 model.item(row: 4)->setBackground(Qt::red);
2150 checkSortedTableModel(model: &model, rows);
2151
2152 // change the background color of the second "c"
2153 model.item(row: 5)->setBackground(Qt::red);
2154 checkSortedTableModel(model: &model, rows);
2155}
2156
2157void tst_QSortFilterProxyModel::changeSourceDataForwardsRoles_qtbug35440()
2158{
2159 QStringList strings;
2160 for (int i = 0; i < 100; ++i)
2161 strings << QString::number(i);
2162
2163 QStringListModel model(strings);
2164
2165 QSortFilterProxyModel proxy;
2166 proxy.setSourceModel(&model);
2167 proxy.sort(column: 0, order: Qt::AscendingOrder);
2168
2169 QSignalSpy spy(&proxy, &QAbstractItemModel::dataChanged);
2170 QVERIFY(spy.isValid());
2171 QCOMPARE(spy.length(), 0);
2172
2173 QModelIndex index;
2174
2175 // QStringListModel doesn't distinguish between edit and display roles,
2176 // so changing one always changes the other, too.
2177 QVector<int> expectedChangedRoles;
2178 expectedChangedRoles.append(t: Qt::DisplayRole);
2179 expectedChangedRoles.append(t: Qt::EditRole);
2180
2181 index = model.index(row: 0, column: 0);
2182 QVERIFY(index.isValid());
2183 model.setData(index, QStringLiteral("teststring"), role: Qt::DisplayRole);
2184 QCOMPARE(spy.length(), 1);
2185 QCOMPARE(spy.at(0).at(2).value<QVector<int> >(), expectedChangedRoles);
2186
2187 index = model.index(row: 1, column: 0);
2188 QVERIFY(index.isValid());
2189 model.setData(index, QStringLiteral("teststring2"), role: Qt::EditRole);
2190 QCOMPARE(spy.length(), 2);
2191 QCOMPARE(spy.at(1).at(2).value<QVector<int> >(), expectedChangedRoles);
2192}
2193
2194void tst_QSortFilterProxyModel::changeSourceDataProxySendDataChanged_qtbug87781()
2195{
2196 QStandardItemModel baseModel;
2197 QSortFilterProxyModel proxyModelBefore;
2198 QSortFilterProxyModel proxyModelAfter;
2199
2200 QSignalSpy baseDataChangedSpy(&baseModel, &QStandardItemModel::dataChanged);
2201 QSignalSpy beforeDataChangedSpy(&proxyModelBefore, &QSortFilterProxyModel::dataChanged);
2202 QSignalSpy afterDataChangedSpy(&proxyModelAfter, &QSortFilterProxyModel::dataChanged);
2203
2204 QVERIFY(baseDataChangedSpy.isValid());
2205 QVERIFY(beforeDataChangedSpy.isValid());
2206 QVERIFY(afterDataChangedSpy.isValid());
2207
2208 proxyModelBefore.setSourceModel(&baseModel);
2209 baseModel.insertRows(row: 0, count: 1);
2210 baseModel.insertColumns(column: 0, count: 1);
2211 proxyModelAfter.setSourceModel(&baseModel);
2212
2213 QCOMPARE(baseDataChangedSpy.size(), 0);
2214 QCOMPARE(beforeDataChangedSpy.size(), 0);
2215 QCOMPARE(afterDataChangedSpy.size(), 0);
2216
2217 baseModel.setData(index: baseModel.index(row: 0, column: 0), QStringLiteral("new data"), role: Qt::DisplayRole);
2218 QCOMPARE(baseDataChangedSpy.size(), 1);
2219 QCOMPARE(beforeDataChangedSpy.size(), 1);
2220 QCOMPARE(afterDataChangedSpy.size(), 1);
2221}
2222
2223void tst_QSortFilterProxyModel::changeSourceDataTreeModel()
2224{
2225 QStandardItemModel treeModel;
2226 QSortFilterProxyModel treeProxyModelBefore;
2227 QSortFilterProxyModel treeProxyModelAfter;
2228
2229 QSignalSpy treeBaseDataChangedSpy(&treeModel, &QStandardItemModel::dataChanged);
2230 QSignalSpy treeBeforeDataChangedSpy(&treeProxyModelBefore, &QSortFilterProxyModel::dataChanged);
2231 QSignalSpy treeAfterDataChangedSpy(&treeProxyModelAfter, &QSortFilterProxyModel::dataChanged);
2232
2233 QVERIFY(treeBaseDataChangedSpy.isValid());
2234 QVERIFY(treeBeforeDataChangedSpy.isValid());
2235 QVERIFY(treeAfterDataChangedSpy.isValid());
2236
2237 treeProxyModelBefore.setSourceModel(&treeModel);
2238 QStandardItem treeNode1("data1");
2239 QStandardItem treeNode11("data11");
2240 QStandardItem treeNode111("data111");
2241
2242 treeNode1.appendRow(aitem: &treeNode11);
2243 treeNode11.appendRow(aitem: &treeNode111);
2244 treeModel.appendRow(aitem: &treeNode1);
2245 treeProxyModelAfter.setSourceModel(&treeModel);
2246
2247 QCOMPARE(treeBaseDataChangedSpy.size(), 0);
2248 QCOMPARE(treeBeforeDataChangedSpy.size(), 0);
2249 QCOMPARE(treeAfterDataChangedSpy.size(), 0);
2250
2251 treeNode111.setData(QStringLiteral("new data"), role: Qt::DisplayRole);
2252 QCOMPARE(treeBaseDataChangedSpy.size(), 1);
2253 QCOMPARE(treeBeforeDataChangedSpy.size(), 1);
2254 QCOMPARE(treeAfterDataChangedSpy.size(), 1);
2255}
2256
2257void tst_QSortFilterProxyModel::changeSourceDataProxyFilterSingleColumn()
2258{
2259 enum modelRow { Row0, Row1, RowCount };
2260 enum modelColumn { Column0, Column1, Column2, Column3, Column4, Column5, ColumnCount };
2261
2262 class FilterProxyModel : public QSortFilterProxyModel
2263 {
2264 public:
2265 bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override {
2266 Q_UNUSED(source_parent);
2267 switch (source_column) {
2268 case Column2:
2269 case Column4:
2270 return true;
2271 default:
2272 return false;
2273 }
2274 }
2275 };
2276
2277 QStandardItemModel model;
2278 FilterProxyModel proxy;
2279 proxy.setSourceModel(&model);
2280 model.insertRows(row: 0, count: RowCount);
2281 model.insertColumns(column: 0, count: ColumnCount);
2282
2283 QSignalSpy modelDataChangedSpy(&model, &QSortFilterProxyModel::dataChanged);
2284 QSignalSpy proxyDataChangedSpy(&proxy, &FilterProxyModel::dataChanged);
2285
2286 QVERIFY(modelDataChangedSpy.isValid());
2287 QVERIFY(proxyDataChangedSpy.isValid());
2288
2289 modelDataChangedSpy.clear();
2290 proxyDataChangedSpy.clear();
2291 model.setData(index: model.index(row: Row0, column: Column1), QStringLiteral("new data"), role: Qt::DisplayRole);
2292 QCOMPARE(modelDataChangedSpy.size(), 1);
2293 QCOMPARE(proxyDataChangedSpy.size(), 0);
2294
2295 modelDataChangedSpy.clear();
2296 proxyDataChangedSpy.clear();
2297 model.setData(index: model.index(row: Row0, column: Column2), QStringLiteral("new data"), role: Qt::DisplayRole);
2298 QCOMPARE(modelDataChangedSpy.size(), 1);
2299 QCOMPARE(proxyDataChangedSpy.size(), 1);
2300
2301 modelDataChangedSpy.clear();
2302 proxyDataChangedSpy.clear();
2303 model.setData(index: model.index(row: Row0, column: Column3), QStringLiteral("new data"), role: Qt::DisplayRole);
2304 QCOMPARE(modelDataChangedSpy.size(), 1);
2305 QCOMPARE(proxyDataChangedSpy.size(), 0);
2306
2307 modelDataChangedSpy.clear();
2308 proxyDataChangedSpy.clear();
2309 model.setData(index: model.index(row: Row0, column: Column4), QStringLiteral("new data"), role: Qt::DisplayRole);
2310 QCOMPARE(modelDataChangedSpy.size(), 1);
2311 QCOMPARE(proxyDataChangedSpy.size(), 1);
2312
2313 modelDataChangedSpy.clear();
2314 proxyDataChangedSpy.clear();
2315 model.setData(index: model.index(row: Row0, column: Column5), QStringLiteral("new data"), role: Qt::DisplayRole);
2316 QCOMPARE(modelDataChangedSpy.size(), 1);
2317 QCOMPARE(proxyDataChangedSpy.size(), 0);
2318}
2319
2320void tst_QSortFilterProxyModel::changeSourceDataProxyFilterMultipleColumns()
2321{
2322 class FilterProxyModel : public QSortFilterProxyModel
2323 {
2324 public:
2325 bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override {
2326 Q_UNUSED(source_parent);
2327 switch (source_column) {
2328 case 2:
2329 case 4:
2330 return true;
2331 default:
2332 return false;
2333 }
2334 }
2335 };
2336
2337 class MyTableModel : public QAbstractTableModel
2338 {
2339 public:
2340 explicit MyTableModel() = default;
2341 int rowCount(const QModelIndex &parent = QModelIndex()) const override {
2342 Q_UNUSED(parent)
2343 return 10;
2344 }
2345 int columnCount(const QModelIndex &parent = QModelIndex()) const override {
2346 Q_UNUSED(parent)
2347 return 10;
2348 }
2349 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
2350 Q_UNUSED(index)
2351 Q_UNUSED(role)
2352 return QString("testData");
2353 }
2354
2355 void testDataChanged(const int topLeftRow, const int topLeftColumn, const int bottomRightRow, const int bottomRightColumn) {
2356 QModelIndex topLeft = index(row: topLeftRow, column: topLeftColumn);
2357 QModelIndex bottomRight = index(row: bottomRightRow, column: bottomRightColumn);
2358 QVERIFY(topLeft.isValid());
2359 QVERIFY(bottomRight.isValid());
2360 emit dataChanged(topLeft, bottomRight);
2361 }
2362 };
2363
2364 MyTableModel baseModel;
2365 FilterProxyModel proxyModel;
2366
2367 proxyModel.setSourceModel(&baseModel);
2368
2369 QSignalSpy baseModelDataChangedSpy(&baseModel, &MyTableModel::dataChanged);
2370 QSignalSpy proxyModelDataChangedSpy(&proxyModel, &FilterProxyModel::dataChanged);
2371
2372 connect(sender: &proxyModel, signal: &FilterProxyModel::dataChanged, slot: [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
2373 QVERIFY(topLeft.isValid());
2374 QVERIFY(bottomRight.isValid());
2375
2376 //make sure every element is valid
2377 int topLeftRow = topLeft.row();
2378 int topLeftColumn = topLeft.column();
2379 int bottomRightRow = bottomRight.row();
2380 int bottomRightColumn = bottomRight.column();
2381 for (int row = topLeftRow; row <= bottomRightRow; ++row) {
2382 for (int column = topLeftColumn; column <= bottomRightColumn; ++column) {
2383 QModelIndex index = topLeft.model()->index(row, column);
2384 QVERIFY(index.isValid());
2385 }
2386 }
2387 });
2388
2389 QVERIFY(baseModelDataChangedSpy.isValid());
2390 QVERIFY(proxyModelDataChangedSpy.isValid());
2391
2392 baseModelDataChangedSpy.clear();
2393 proxyModelDataChangedSpy.clear();
2394 baseModel.testDataChanged(topLeftRow: 0, topLeftColumn: 0, bottomRightRow: 1, bottomRightColumn: 1);
2395 QCOMPARE(baseModelDataChangedSpy.size(), 1);
2396 QCOMPARE(proxyModelDataChangedSpy.size(), 0);
2397
2398 baseModelDataChangedSpy.clear();
2399 proxyModelDataChangedSpy.clear();
2400 baseModel.testDataChanged(topLeftRow: 0, topLeftColumn: 0, bottomRightRow: 1, bottomRightColumn: 2);
2401 QCOMPARE(baseModelDataChangedSpy.size(), 1);
2402 QCOMPARE(proxyModelDataChangedSpy.size(), 1);
2403
2404 baseModelDataChangedSpy.clear();
2405 proxyModelDataChangedSpy.clear();
2406 baseModel.testDataChanged(topLeftRow: 0, topLeftColumn: 3, bottomRightRow: 1, bottomRightColumn: 3);
2407 QCOMPARE(baseModelDataChangedSpy.size(), 1);
2408 QCOMPARE(proxyModelDataChangedSpy.size(), 0);
2409
2410 baseModelDataChangedSpy.clear();
2411 proxyModelDataChangedSpy.clear();
2412 baseModel.testDataChanged(topLeftRow: 0, topLeftColumn: 3, bottomRightRow: 1, bottomRightColumn: 5);
2413 QCOMPARE(baseModelDataChangedSpy.size(), 1);
2414 QCOMPARE(proxyModelDataChangedSpy.size(), 1);
2415
2416 baseModelDataChangedSpy.clear();
2417 proxyModelDataChangedSpy.clear();
2418 baseModel.testDataChanged(topLeftRow: 0, topLeftColumn: 0, bottomRightRow: 1, bottomRightColumn: 5);
2419 QCOMPARE(baseModelDataChangedSpy.size(), 1);
2420 QCOMPARE(proxyModelDataChangedSpy.size(), 1);
2421}
2422
2423void tst_QSortFilterProxyModel::sortFilterRole()
2424{
2425 QStandardItemModel model;
2426 QSortFilterProxyModel proxy;
2427 proxy.setSourceModel(&model);
2428 model.insertColumns(column: 0, count: 1);
2429
2430 const QVector<QPair<QVariant, QVariant>>
2431 sourceItems({QPair<QVariant, QVariant>("b", 3),
2432 QPair<QVariant, QVariant>("c", 2),
2433 QPair<QVariant, QVariant>("a", 1)});
2434
2435 const QVector<int> orderedItems({2, 1});
2436
2437 model.insertRows(row: 0, count: sourceItems.count());
2438 for (int i = 0; i < sourceItems.count(); ++i) {
2439 QModelIndex index = model.index(row: i, column: 0, parent: QModelIndex());
2440 model.setData(index, value: sourceItems.at(i).first, role: Qt::DisplayRole);
2441 model.setData(index, value: sourceItems.at(i).second, role: Qt::UserRole);
2442 }
2443
2444 setupFilter(model: &proxy, pattern: QLatin1String("2"));
2445
2446 QCOMPARE(proxy.rowCount(), 0); // Qt::DisplayRole is default role
2447
2448 proxy.setFilterRole(Qt::UserRole);
2449 QCOMPARE(proxy.rowCount(), 1);
2450
2451 proxy.setFilterRole(Qt::DisplayRole);
2452 QCOMPARE(proxy.rowCount(), 0);
2453
2454 setupFilter(model: &proxy, pattern: QLatin1String("1|2|3"));
2455
2456 QCOMPARE(proxy.rowCount(), 0);
2457
2458 proxy.setFilterRole(Qt::UserRole);
2459 QCOMPARE(proxy.rowCount(), 3);
2460
2461 proxy.sort(column: 0, order: Qt::AscendingOrder);
2462 QCOMPARE(proxy.rowCount(), 3);
2463
2464 proxy.setSortRole(Qt::UserRole);
2465 proxy.setFilterRole(Qt::DisplayRole);
2466 setupFilter(model: &proxy, pattern: QLatin1String("a|c"));
2467
2468 QCOMPARE(proxy.rowCount(), orderedItems.count());
2469 for (int i = 0; i < proxy.rowCount(); ++i) {
2470 QModelIndex index = proxy.index(row: i, column: 0, parent: QModelIndex());
2471 QCOMPARE(proxy.data(index, Qt::DisplayRole), sourceItems.at(orderedItems.at(i)).first);
2472 }
2473}
2474
2475void tst_QSortFilterProxyModel::selectionFilteredOut()
2476{
2477 QStandardItemModel model(2, 1);
2478 model.setData(index: model.index(row: 0, column: 0), value: QString("AAA"));
2479 model.setData(index: model.index(row: 1, column: 0), value: QString("BBB"));
2480 QSortFilterProxyModel proxy;
2481 proxy.setSourceModel(&model);
2482 QTreeView view;
2483
2484 view.show();
2485 view.setModel(&proxy);
2486 QSignalSpy spy(view.selectionModel(), &QItemSelectionModel::currentChanged);
2487 QVERIFY(spy.isValid());
2488
2489 view.setCurrentIndex(proxy.index(row: 0, column: 0));
2490 QCOMPARE(spy.count(), 1);
2491
2492 setupFilter(model: &proxy, pattern: QLatin1String("^B"));
2493 QCOMPARE(spy.count(), 2);
2494}
2495
2496void tst_QSortFilterProxyModel::match_data()
2497{
2498 QTest::addColumn<QStringList>(name: "sourceItems");
2499 QTest::addColumn<Qt::SortOrder>(name: "sortOrder");
2500 QTest::addColumn<QString>(name: "filter");
2501 QTest::addColumn<int>(name: "proxyStartRow");
2502 QTest::addColumn<QString>(name: "what");
2503 QTest::addColumn<Qt::MatchFlag>(name: "matchFlags");
2504 QTest::addColumn<IntList>(name: "expectedProxyItems");
2505 QTest::newRow(dataTag: "1")
2506 << (QStringList() << "a") // sourceItems
2507 << Qt::AscendingOrder // sortOrder
2508 << "" // filter
2509 << 0 // proxyStartRow
2510 << "a" // what
2511 << Qt::MatchExactly // matchFlags
2512 << (IntList() << 0); // expectedProxyItems
2513 QTest::newRow(dataTag: "2")
2514 << (QStringList() << "a" << "b") // sourceItems
2515 << Qt::AscendingOrder // sortOrder
2516 << "" // filter
2517 << 0 // proxyStartRow
2518 << "b" // what
2519 << Qt::MatchExactly // matchFlags
2520 << (IntList() << 1); // expectedProxyItems
2521 QTest::newRow(dataTag: "3")
2522 << (QStringList() << "a" << "b") // sourceItems
2523 << Qt::DescendingOrder // sortOrder
2524 << "" // filter
2525 << 0 // proxyStartRow
2526 << "a" // what
2527 << Qt::MatchExactly // matchFlags
2528 << (IntList() << 1); // expectedProxyItems
2529 QTest::newRow(dataTag: "4")
2530 << (QStringList() << "b" << "d" << "a" << "c") // sourceItems
2531 << Qt::AscendingOrder // sortOrder
2532 << "" // filter
2533 << 1 // proxyStartRow
2534 << "a" // what
2535 << Qt::MatchExactly // matchFlags
2536 << IntList(); // expectedProxyItems
2537 QTest::newRow(dataTag: "5")
2538 << (QStringList() << "b" << "d" << "a" << "c") // sourceItems
2539 << Qt::AscendingOrder // sortOrder
2540 << "a|b" // filter
2541 << 0 // proxyStartRow
2542 << "c" // what
2543 << Qt::MatchExactly // matchFlags
2544 << IntList(); // expectedProxyItems
2545 QTest::newRow(dataTag: "6")
2546 << (QStringList() << "b" << "d" << "a" << "c") // sourceItems
2547 << Qt::DescendingOrder // sortOrder
2548 << "a|b" // filter
2549 << 0 // proxyStartRow
2550 << "b" // what
2551 << Qt::MatchExactly // matchFlags
2552 << (IntList() << 0); // expectedProxyItems
2553}
2554
2555void tst_QSortFilterProxyModel::match()
2556{
2557 QFETCH(QStringList, sourceItems);
2558 QFETCH(Qt::SortOrder, sortOrder);
2559 QFETCH(QString, filter);
2560 QFETCH(int, proxyStartRow);
2561 QFETCH(QString, what);
2562 QFETCH(Qt::MatchFlag, matchFlags);
2563 QFETCH(IntList, expectedProxyItems);
2564
2565 QStandardItemModel model;
2566 QSortFilterProxyModel proxy;
2567
2568 proxy.setSourceModel(&model);
2569 model.insertColumns(column: 0, count: 1);
2570 model.insertRows(row: 0, count: sourceItems.count());
2571
2572 for (int i = 0; i < sourceItems.count(); ++i) {
2573 QModelIndex index = model.index(row: i, column: 0, parent: QModelIndex());
2574 model.setData(index, value: sourceItems.at(i), role: Qt::DisplayRole);
2575 }
2576
2577 proxy.sort(column: 0, order: sortOrder);
2578 setupFilter(model: &proxy, pattern: filter);
2579
2580 QModelIndex startIndex = proxy.index(row: proxyStartRow, column: 0);
2581 QModelIndexList indexes = proxy.match(start: startIndex, role: Qt::DisplayRole, value: what,
2582 hits: expectedProxyItems.count(),
2583 flags: matchFlags);
2584 QCOMPARE(indexes.count(), expectedProxyItems.count());
2585 for (int i = 0; i < indexes.count(); ++i)
2586 QCOMPARE(indexes.at(i).row(), expectedProxyItems.at(i));
2587}
2588
2589QList<QStandardItem *> createStandardItemList(const QString &prefix, int n)
2590{
2591 QList<QStandardItem *> result;
2592 for (int i = 0; i < n; ++i)
2593 result.append(t: new QStandardItem(prefix + QString::number(i)));
2594 return result;
2595}
2596
2597// QTBUG-73864, recursive search in a tree model.
2598
2599void tst_QSortFilterProxyModel::matchTree()
2600{
2601 QStandardItemModel model(0, 2);
2602 // Header00 Header01
2603 // Header10 Header11
2604 // Item00 Item01
2605 // Item10 Item11
2606 model.appendRow(items: createStandardItemList(prefix: QLatin1String("Header0"), n: 2));
2607 auto headerRow = createStandardItemList(prefix: QLatin1String("Header1"), n: 2);
2608 model.appendRow(items: headerRow);
2609 headerRow.first()->appendRow(aitems: createStandardItemList(prefix: QLatin1String("Item0"), n: 2));
2610 headerRow.first()->appendRow(aitems: createStandardItemList(prefix: QLatin1String("Item1"), n: 2));
2611
2612 auto item11 = model.match(start: model.index(row: 1, column: 1), role: Qt::DisplayRole, value: QLatin1String("Item11"), hits: 20,
2613 flags: Qt::MatchRecursive).value(i: 0);
2614 QVERIFY(item11.isValid());
2615 QCOMPARE(item11.data().toString(), QLatin1String("Item11"));
2616
2617 // Repeat in proxy model
2618 QSortFilterProxyModel proxy;
2619 proxy.setSourceModel(&model);
2620 auto proxyItem11 = proxy.match(start: proxy.index(row: 1, column: 1), role: Qt::DisplayRole, value: QLatin1String("Item11"), hits: 20,
2621 flags: Qt::MatchRecursive).value(i: 0);
2622 QVERIFY(proxyItem11.isValid());
2623 QCOMPARE(proxyItem11.data().toString(), QLatin1String("Item11"));
2624
2625 QCOMPARE(proxy.mapToSource(proxyItem11).internalId(), item11.internalId());
2626}
2627
2628void tst_QSortFilterProxyModel::insertIntoChildrenlessItem()
2629{
2630 QStandardItemModel model;
2631 QStandardItem *itemA = new QStandardItem("a");
2632 model.appendRow(aitem: itemA);
2633 QStandardItem *itemB = new QStandardItem("b");
2634 model.appendRow(aitem: itemB);
2635 QStandardItem *itemC = new QStandardItem("c");
2636 model.appendRow(aitem: itemC);
2637
2638 QSortFilterProxyModel proxy;
2639 proxy.setSourceModel(&model);
2640
2641 QSignalSpy colsInsertedSpy(&proxy, &QSortFilterProxyModel::columnsInserted);
2642 QSignalSpy rowsInsertedSpy(&proxy, &QSortFilterProxyModel::rowsInserted);
2643
2644 QVERIFY(colsInsertedSpy.isValid());
2645 QVERIFY(rowsInsertedSpy.isValid());
2646
2647 (void)proxy.rowCount(parent: QModelIndex()); // force mapping of "a", "b", "c"
2648 QCOMPARE(colsInsertedSpy.count(), 0);
2649 QCOMPARE(rowsInsertedSpy.count(), 0);
2650
2651 // now add a child to itemB ==> should get insert notification from the proxy
2652 itemB->appendRow(aitem: new QStandardItem("a.0"));
2653 QCOMPARE(colsInsertedSpy.count(), 1);
2654 QCOMPARE(rowsInsertedSpy.count(), 1);
2655
2656 QVariantList args = colsInsertedSpy.takeFirst();
2657 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), proxy.mapFromSource(itemB->index()));
2658 QCOMPARE(qvariant_cast<int>(args.at(1)), 0);
2659 QCOMPARE(qvariant_cast<int>(args.at(2)), 0);
2660
2661 args = rowsInsertedSpy.takeFirst();
2662 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), proxy.mapFromSource(itemB->index()));
2663 QCOMPARE(qvariant_cast<int>(args.at(1)), 0);
2664 QCOMPARE(qvariant_cast<int>(args.at(2)), 0);
2665}
2666
2667void tst_QSortFilterProxyModel::invalidateMappedChildren()
2668{
2669 QStandardItemModel model;
2670
2671 QSortFilterProxyModel proxy;
2672 proxy.setSourceModel(&model);
2673
2674 QStandardItem *itemA = new QStandardItem("a");
2675 model.appendRow(aitem: itemA);
2676 QStandardItem *itemB = new QStandardItem("b");
2677 itemA->appendRow(aitem: itemB);
2678
2679 QStandardItem *itemC = new QStandardItem("c");
2680 itemB->appendRow(aitem: itemC);
2681 itemC->appendRow(aitem: new QStandardItem("d"));
2682
2683 // force mappings
2684 (void)proxy.hasChildren(parent: QModelIndex());
2685 (void)proxy.hasChildren(parent: proxy.mapFromSource(sourceIndex: itemA->index()));
2686 (void)proxy.hasChildren(parent: proxy.mapFromSource(sourceIndex: itemB->index()));
2687 (void)proxy.hasChildren(parent: proxy.mapFromSource(sourceIndex: itemC->index()));
2688
2689 itemB->removeRow(row: 0); // should invalidate mapping of itemC
2690 itemC = new QStandardItem("c");
2691 itemA->appendRow(aitem: itemC);
2692 itemC->appendRow(aitem: new QStandardItem("d"));
2693
2694 itemA->removeRow(row: 1); // should invalidate mapping of itemC
2695 itemC = new QStandardItem("c");
2696 itemB->appendRow(aitem: itemC);
2697 itemC->appendRow(aitem: new QStandardItem("d"));
2698
2699 QCOMPARE(proxy.rowCount(proxy.mapFromSource(itemA->index())), 1);
2700 QCOMPARE(proxy.rowCount(proxy.mapFromSource(itemB->index())), 1);
2701 QCOMPARE(proxy.rowCount(proxy.mapFromSource(itemC->index())), 1);
2702}
2703
2704class EvenOddFilterModel : public QSortFilterProxyModel
2705{
2706 Q_OBJECT
2707public:
2708 bool filterAcceptsRow(int srcRow, const QModelIndex &srcParent) const override
2709 {
2710 if (srcParent.isValid())
2711 return (srcParent.row() % 2) ^ !(srcRow % 2);
2712 return (srcRow % 2);
2713 }
2714};
2715
2716void tst_QSortFilterProxyModel::insertRowIntoFilteredParent()
2717{
2718 QStandardItemModel model;
2719 EvenOddFilterModel proxy;
2720 proxy.setSourceModel(&model);
2721
2722 QSignalSpy spy(&proxy, &EvenOddFilterModel::rowsInserted);
2723 QVERIFY(spy.isValid());
2724
2725 QStandardItem *itemA = new QStandardItem();
2726 model.appendRow(aitem: itemA); // A will be filtered
2727 QStandardItem *itemB = new QStandardItem();
2728 itemA->appendRow(aitem: itemB);
2729
2730 QCOMPARE(spy.count(), 0);
2731
2732 itemA->removeRow(row: 0);
2733
2734 QCOMPARE(spy.count(), 0);
2735}
2736
2737void tst_QSortFilterProxyModel::filterOutParentAndFilterInChild()
2738{
2739 QStandardItemModel model;
2740 QSortFilterProxyModel proxy;
2741 proxy.setSourceModel(&model);
2742
2743 setupFilter(model: &proxy, pattern: QLatin1String("A|B"));
2744
2745 QStandardItem *itemA = new QStandardItem("A");
2746 model.appendRow(aitem: itemA); // not filtered
2747 QStandardItem *itemB = new QStandardItem("B");
2748 itemA->appendRow(aitem: itemB); // not filtered
2749 QStandardItem *itemC = new QStandardItem("C");
2750 itemA->appendRow(aitem: itemC); // filtered
2751
2752 QSignalSpy removedSpy(&proxy, &QSortFilterProxyModel::rowsRemoved);
2753 QSignalSpy insertedSpy(&proxy, &QSortFilterProxyModel::rowsInserted);
2754
2755 QVERIFY(removedSpy.isValid());
2756 QVERIFY(insertedSpy.isValid());
2757
2758 setupFilter(model: &proxy, pattern: QLatin1String("C")); // A and B will be filtered out, C filtered in
2759
2760 // we should now have been notified that the subtree represented by itemA has been removed
2761 QCOMPARE(removedSpy.count(), 1);
2762 // we should NOT get any inserts; itemC should be filtered because its parent (itemA) is
2763 QCOMPARE(insertedSpy.count(), 0);
2764}
2765
2766void tst_QSortFilterProxyModel::sourceInsertRows()
2767{
2768 QStandardItemModel model;
2769 QSortFilterProxyModel proxyModel;
2770 proxyModel.setSourceModel(&model);
2771
2772 model.insertColumns(column: 0, count: 1, parent: QModelIndex());
2773 model.insertRows(row: 0, count: 2, parent: QModelIndex());
2774
2775 {
2776 QModelIndex parent = model.index(row: 0, column: 0, parent: QModelIndex());
2777 model.insertColumns(column: 0, count: 1, parent);
2778 model.insertRows(row: 0, count: 1, parent);
2779 }
2780
2781 {
2782 QModelIndex parent = model.index(row: 1, column: 0, parent: QModelIndex());
2783 model.insertColumns(column: 0, count: 1, parent);
2784 model.insertRows(row: 0, count: 1, parent);
2785 }
2786
2787 model.insertRows(row: 0, count: 1, parent: QModelIndex());
2788 model.insertRows(row: 0, count: 1, parent: QModelIndex());
2789
2790 QVERIFY(true); // if you got here without asserting, it's all good
2791}
2792
2793void tst_QSortFilterProxyModel::sourceModelDeletion()
2794{
2795 QSortFilterProxyModel proxyModel;
2796 {
2797 QStandardItemModel model;
2798 proxyModel.setSourceModel(&model);
2799 QCOMPARE(proxyModel.sourceModel(), &model);
2800 }
2801 QCOMPARE(proxyModel.sourceModel(), nullptr);
2802}
2803
2804void tst_QSortFilterProxyModel::sortColumnTracking1()
2805{
2806 QStandardItemModel model;
2807 QSortFilterProxyModel proxyModel;
2808 proxyModel.setSourceModel(&model);
2809
2810 model.insertColumns(column: 0, count: 10);
2811 model.insertRows(row: 0, count: 10);
2812
2813 proxyModel.sort(column: 1);
2814 QCOMPARE(proxyModel.sortColumn(), 1);
2815
2816 model.insertColumn(acolumn: 8);
2817 QCOMPARE(proxyModel.sortColumn(), 1);
2818
2819 model.removeColumn(acolumn: 8);
2820 QCOMPARE(proxyModel.sortColumn(), 1);
2821
2822 model.insertColumn(acolumn: 2);
2823 QCOMPARE(proxyModel.sortColumn(), 1);
2824
2825 model.removeColumn(acolumn: 2);
2826 QCOMPARE(proxyModel.sortColumn(), 1);
2827
2828 model.insertColumn(acolumn: 1);
2829 QCOMPARE(proxyModel.sortColumn(), 2);
2830
2831 model.removeColumn(acolumn: 1);
2832 QCOMPARE(proxyModel.sortColumn(), 1);
2833
2834 model.removeColumn(acolumn: 1);
2835 QCOMPARE(proxyModel.sortColumn(), -1);
2836}
2837
2838void tst_QSortFilterProxyModel::sortColumnTracking2()
2839{
2840 QStandardItemModel model;
2841 QSortFilterProxyModel proxyModel;
2842 proxyModel.setDynamicSortFilter(true);
2843 proxyModel.setSourceModel(&model);
2844
2845 proxyModel.sort(column: 0);
2846 QCOMPARE(proxyModel.sortColumn(), 0);
2847
2848 QList<QStandardItem *> items; // Stable sorting: Items with invalid data should move to the end
2849 items << new QStandardItem << new QStandardItem("foo") << new QStandardItem("bar")
2850 << new QStandardItem("some") << new QStandardItem("others") << new QStandardItem("item")
2851 << new QStandardItem("aa") << new QStandardItem("zz") << new QStandardItem;
2852
2853 model.insertColumn(column: 0,items);
2854 QCOMPARE(proxyModel.sortColumn(), 0);
2855 QCOMPARE(proxyModel.data(proxyModel.index(0,0)).toString(),QString::fromLatin1("aa"));
2856 const int zzIndex = items.count() - 3; // 2 invalid at end.
2857 QCOMPARE(proxyModel.data(proxyModel.index(zzIndex,0)).toString(),QString::fromLatin1("zz"));
2858}
2859
2860void tst_QSortFilterProxyModel::sortStable()
2861{
2862 QStandardItemModel* model = new QStandardItemModel(5, 2);
2863 for (int r = 0; r < 5; r++) {
2864 const QString prefix = QLatin1String("Row:") + QString::number(r) + QLatin1String(", Column:");
2865 for (int c = 0; c < 2; c++) {
2866 QStandardItem* item = new QStandardItem(prefix + QString::number(c));
2867 for (int i = 0; i < 3; i++) {
2868 QStandardItem* child = new QStandardItem(QLatin1String("Item ") + QString::number(i));
2869 item->appendRow( aitem: child );
2870 }
2871 model->setItem(row: r, column: c, item);
2872 }
2873 }
2874 model->setHorizontalHeaderItem( column: 0, item: new QStandardItem( "Name" ));
2875 model->setHorizontalHeaderItem( column: 1, item: new QStandardItem( "Value" ));
2876
2877 QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(model);
2878 filterModel->setSourceModel(model);
2879
2880 QTreeView *view = new QTreeView;
2881 view->setModel(filterModel);
2882 QModelIndex firstRoot = filterModel->index(row: 0,column: 0);
2883 view->expand(index: firstRoot);
2884 view->setSortingEnabled(true);
2885
2886 view->model()->sort(column: 1, order: Qt::DescendingOrder);
2887 QVariant lastItemData =filterModel->index(row: 2,column: 0, parent: firstRoot).data();
2888 view->model()->sort(column: 1, order: Qt::DescendingOrder);
2889 QCOMPARE(lastItemData, filterModel->index(2,0, firstRoot).data());
2890}
2891
2892void tst_QSortFilterProxyModel::hiddenColumns()
2893{
2894 class MyStandardItemModel : public QStandardItemModel
2895 {
2896 public:
2897 MyStandardItemModel() : QStandardItemModel(0,5) {}
2898 void reset()
2899 { beginResetModel(); endResetModel(); }
2900 friend class tst_QSortFilterProxyModel;
2901 } model;
2902 QSortFilterProxyModel proxy;
2903 proxy.setSourceModel(&model);
2904
2905 QTableView view;
2906 view.setModel(&proxy);
2907
2908 view.hideColumn(column: 0);
2909
2910 QVERIFY(view.isColumnHidden(0));
2911 model.blockSignals(b: true);
2912 model.setRowCount(1);
2913 model.blockSignals(b: false);
2914 model.reset();
2915
2916 // In the initial bug report that spawned this test, this would be false
2917 // because resetting model would also reset the hidden columns.
2918 QVERIFY(view.isColumnHidden(0));
2919}
2920
2921void tst_QSortFilterProxyModel::insertRowsSort()
2922{
2923 QStandardItemModel model(2,2);
2924 QSortFilterProxyModel proxyModel;
2925 proxyModel.setSourceModel(&model);
2926
2927 proxyModel.sort(column: 0);
2928 QCOMPARE(proxyModel.sortColumn(), 0);
2929
2930 model.insertColumns(column: 0, count: 3, parent: model.index(row: 0,column: 0));
2931 QCOMPARE(proxyModel.sortColumn(), 0);
2932
2933 model.removeColumns(column: 0, count: 3, parent: model.index(row: 0,column: 0));
2934 QCOMPARE(proxyModel.sortColumn(), 0);
2935}
2936
2937void tst_QSortFilterProxyModel::staticSorting()
2938{
2939 QStandardItemModel model(0, 1);
2940 QSortFilterProxyModel proxy;
2941 proxy.setSourceModel(&model);
2942 proxy.setDynamicSortFilter(false);
2943 QStringList initial = QString("bateau avion dragon hirondelle flamme camion elephant").split(sep: QLatin1Char(' '));
2944
2945 // prepare model
2946 QStandardItem *root = model.invisibleRootItem ();
2947 QList<QStandardItem *> items;
2948 for (int i = 0; i < initial.count(); ++i) {
2949 items.append(t: new QStandardItem(initial.at(i)));
2950 }
2951 root->insertRows(row: 0, items);
2952 QCOMPARE(model.rowCount(QModelIndex()), initial.count());
2953 QCOMPARE(model.columnCount(QModelIndex()), 1);
2954
2955 // make sure the proxy is unsorted
2956 QCOMPARE(proxy.columnCount(QModelIndex()), 1);
2957 QCOMPARE(proxy.rowCount(QModelIndex()), initial.count());
2958 for (int row = 0; row < proxy.rowCount(parent: QModelIndex()); ++row) {
2959 QModelIndex index = proxy.index(row, column: 0, parent: QModelIndex());
2960 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), initial.at(row));
2961 }
2962
2963 // sort
2964 proxy.sort(column: 0);
2965
2966 QStringList expected = initial;
2967 expected.sort();
2968 // make sure the proxy is sorted
2969 for (int row = 0; row < proxy.rowCount(parent: QModelIndex()); ++row) {
2970 QModelIndex index = proxy.index(row, column: 0, parent: QModelIndex());
2971 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), expected.at(row));
2972 }
2973
2974 //update one item.
2975 items.first()->setData(value: "girafe", role: Qt::DisplayRole);
2976
2977 // make sure the proxy is updated but not sorted
2978 expected.replaceInStrings(before: "bateau", after: "girafe");
2979 for (int row = 0; row < proxy.rowCount(parent: QModelIndex()); ++row) {
2980 QModelIndex index = proxy.index(row, column: 0, parent: QModelIndex());
2981 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), expected.at(row));
2982 }
2983
2984 // sort again
2985 proxy.sort(column: 0);
2986 expected.sort();
2987
2988 // make sure the proxy is sorted
2989 for (int row = 0; row < proxy.rowCount(parent: QModelIndex()); ++row) {
2990 QModelIndex index = proxy.index(row, column: 0, parent: QModelIndex());
2991 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), expected.at(row));
2992 }
2993}
2994
2995void tst_QSortFilterProxyModel::dynamicSorting()
2996{
2997 QStringListModel model1;
2998 const QStringList initial = QString("bateau avion dragon hirondelle flamme camion elephant").split(sep: QLatin1Char(' '));
2999 model1.setStringList(initial);
3000 QSortFilterProxyModel proxy1;
3001 proxy1.setDynamicSortFilter(false);
3002 proxy1.sort(column: 0);
3003 proxy1.setSourceModel(&model1);
3004
3005 QCOMPARE(proxy1.columnCount(QModelIndex()), 1);
3006 //the model should not be sorted because sorting has not been set to dynamic yet.
3007 QCOMPARE(proxy1.rowCount(QModelIndex()), initial.count());
3008 for (int row = 0; row < proxy1.rowCount(parent: QModelIndex()); ++row) {
3009 QModelIndex index = proxy1.index(row, column: 0, parent: QModelIndex());
3010 QCOMPARE(proxy1.data(index, Qt::DisplayRole).toString(), initial.at(row));
3011 }
3012
3013 proxy1.setDynamicSortFilter(true);
3014
3015 //now the model should be sorted.
3016 QStringList expected = initial;
3017 expected.sort();
3018 for (int row = 0; row < proxy1.rowCount(parent: QModelIndex()); ++row) {
3019 QModelIndex index = proxy1.index(row, column: 0, parent: QModelIndex());
3020 QCOMPARE(proxy1.data(index, Qt::DisplayRole).toString(), expected.at(row));
3021 }
3022
3023 QStringList initial2 = initial;
3024 initial2.replaceInStrings(before: "bateau", after: "girafe");
3025 model1.setStringList(initial2); //this will cause a reset
3026
3027 QStringList expected2 = initial2;
3028 expected2.sort();
3029
3030 //now the model should still be sorted.
3031 for (int row = 0; row < proxy1.rowCount(parent: QModelIndex()); ++row) {
3032 QModelIndex index = proxy1.index(row, column: 0, parent: QModelIndex());
3033 QCOMPARE(proxy1.data(index, Qt::DisplayRole).toString(), expected2.at(row));
3034 }
3035
3036 QStringListModel model2(initial);
3037 proxy1.setSourceModel(&model2);
3038
3039 //the model should again be sorted
3040 for (int row = 0; row < proxy1.rowCount(parent: QModelIndex()); ++row) {
3041 QModelIndex index = proxy1.index(row, column: 0, parent: QModelIndex());
3042 QCOMPARE(proxy1.data(index, Qt::DisplayRole).toString(), expected.at(row));
3043 }
3044
3045 //set up the sorting before seting the model up
3046 QSortFilterProxyModel proxy2;
3047 proxy2.sort(column: 0);
3048 proxy2.setSourceModel(&model2);
3049 for (int row = 0; row < proxy2.rowCount(parent: QModelIndex()); ++row) {
3050 QModelIndex index = proxy2.index(row, column: 0, parent: QModelIndex());
3051 QCOMPARE(proxy2.data(index, Qt::DisplayRole).toString(), expected.at(row));
3052 }
3053}
3054
3055class QtTestModel: public QAbstractItemModel
3056{
3057 Q_OBJECT
3058public:
3059 QtTestModel(int _rows, int _cols, QObject *parent = nullptr)
3060 : QAbstractItemModel(parent)
3061 , rows(_rows)
3062 , cols(_cols)
3063 , wrongIndex(false)
3064 {
3065 }
3066
3067 bool canFetchMore(const QModelIndex &idx) const override
3068 {
3069 return !fetched.contains(value: idx);
3070 }
3071
3072 void fetchMore(const QModelIndex &idx) override
3073 {
3074 if (fetched.contains(value: idx))
3075 return;
3076 beginInsertRows(parent: idx, first: 0, last: rows-1);
3077 fetched.insert(value: idx);
3078 endInsertRows();
3079 }
3080
3081 bool hasChildren(const QModelIndex &parent = QModelIndex()) const override
3082 {
3083 Q_UNUSED(parent)
3084 return true;
3085 }
3086
3087 int rowCount(const QModelIndex& parent = QModelIndex()) const override
3088 {
3089 return fetched.contains(value: parent) ? rows : 0;
3090 }
3091
3092 int columnCount(const QModelIndex& parent = QModelIndex()) const override
3093 {
3094 Q_UNUSED(parent)
3095 return cols;
3096 }
3097
3098 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
3099 {
3100 if (row < 0 || column < 0 || column >= cols || row >= rows) {
3101 return QModelIndex();
3102 }
3103 QModelIndex i = createIndex(arow: row, acolumn: column, aid: int(parent.internalId() + 1));
3104 parentHash[i] = parent;
3105 return i;
3106 }
3107
3108 QModelIndex parent(const QModelIndex &index) const override
3109 {
3110 if (!parentHash.contains(key: index))
3111 return QModelIndex();
3112 return parentHash[index];
3113 }
3114
3115 QVariant data(const QModelIndex &idx, int role) const override
3116 {
3117 if (!idx.isValid())
3118 return QVariant();
3119
3120 if (role == Qt::DisplayRole) {
3121 if (idx.row() < 0 || idx.column() < 0 || idx.column() >= cols || idx.row() >= rows) {
3122 wrongIndex = true;
3123 qWarning(msg: "Invalid modelIndex [%d,%d,%p]", idx.row(), idx.column(),
3124 idx.internalPointer());
3125 }
3126 return QLatin1Char('[') + QString::number(idx.row()) + QLatin1Char(',')
3127 + QString::number(idx.column()) + QLatin1Char(']');
3128 }
3129 return QVariant();
3130 }
3131
3132 QSet<QModelIndex> fetched;
3133 int rows, cols;
3134 mutable bool wrongIndex;
3135 mutable QMap<QModelIndex,QModelIndex> parentHash;
3136};
3137
3138void tst_QSortFilterProxyModel::fetchMore()
3139{
3140 QtTestModel model(10,10);
3141 QSortFilterProxyModel proxy;
3142 proxy.setSourceModel(&model);
3143 QVERIFY(proxy.canFetchMore(QModelIndex()));
3144 QVERIFY(proxy.hasChildren());
3145 while (proxy.canFetchMore(parent: QModelIndex()))
3146 proxy.fetchMore(parent: QModelIndex());
3147 QCOMPARE(proxy.rowCount(), 10);
3148 QCOMPARE(proxy.columnCount(), 10);
3149
3150 QModelIndex idx = proxy.index(row: 1,column: 1);
3151 QVERIFY(idx.isValid());
3152 QVERIFY(proxy.canFetchMore(idx));
3153 QVERIFY(proxy.hasChildren(idx));
3154 while (proxy.canFetchMore(parent: idx))
3155 proxy.fetchMore(parent: idx);
3156 QCOMPARE(proxy.rowCount(idx), 10);
3157 QCOMPARE(proxy.columnCount(idx), 10);
3158}
3159
3160void tst_QSortFilterProxyModel::hiddenChildren()
3161{
3162 QStandardItemModel model;
3163 QSortFilterProxyModel proxy;
3164 proxy.setSourceModel(&model);
3165 proxy.setDynamicSortFilter(true);
3166
3167 QStandardItem *itemA = new QStandardItem("A VISIBLE");
3168 model.appendRow(aitem: itemA);
3169 QStandardItem *itemB = new QStandardItem("B VISIBLE");
3170 itemA->appendRow(aitem: itemB);
3171 QStandardItem *itemC = new QStandardItem("C");
3172 itemA->appendRow(aitem: itemC);
3173 setupFilter(model: &proxy, pattern: QLatin1String("VISIBLE"));
3174
3175 QCOMPARE(proxy.rowCount(QModelIndex()) , 1);
3176 QPersistentModelIndex indexA = proxy.index(row: 0,column: 0);
3177 QCOMPARE(proxy.data(indexA).toString(), QString::fromLatin1("A VISIBLE"));
3178
3179 QCOMPARE(proxy.rowCount(indexA) , 1);
3180 QPersistentModelIndex indexB = proxy.index(row: 0, column: 0, parent: indexA);
3181 QCOMPARE(proxy.data(indexB).toString(), QString::fromLatin1("B VISIBLE"));
3182
3183 itemA->setText("A");
3184 QCOMPARE(proxy.rowCount(QModelIndex()), 0);
3185 QVERIFY(!indexA.isValid());
3186 QVERIFY(!indexB.isValid());
3187
3188 itemB->setText("B");
3189 itemA->setText("A VISIBLE");
3190 itemC->setText("C VISIBLE");
3191
3192 QCOMPARE(proxy.rowCount(QModelIndex()), 1);
3193 indexA = proxy.index(row: 0,column: 0);
3194 QCOMPARE(proxy.data(indexA).toString(), QString::fromLatin1("A VISIBLE"));
3195
3196 QCOMPARE(proxy.rowCount(indexA) , 1);
3197 QModelIndex indexC = proxy.index(row: 0, column: 0, parent: indexA);
3198 QCOMPARE(proxy.data(indexC).toString(), QString::fromLatin1("C VISIBLE"));
3199
3200 setupFilter(model: &proxy, pattern: QLatin1String("C"));
3201
3202 QCOMPARE(proxy.rowCount(QModelIndex()), 0);
3203 itemC->setText("invisible");
3204 itemA->setText("AC");
3205
3206 QCOMPARE(proxy.rowCount(QModelIndex()), 1);
3207 indexA = proxy.index(row: 0,column: 0);
3208 QCOMPARE(proxy.data(indexA).toString(), QString::fromLatin1("AC"));
3209 QCOMPARE(proxy.rowCount(indexA) , 0);
3210}
3211
3212void tst_QSortFilterProxyModel::mapFromToSource()
3213{
3214 QtTestModel source(10,10);
3215 source.fetchMore(idx: QModelIndex());
3216 QSortFilterProxyModel proxy;
3217 proxy.setSourceModel(&source);
3218 QCOMPARE(proxy.mapFromSource(source.index(5, 4)), proxy.index(5, 4));
3219 QCOMPARE(proxy.mapToSource(proxy.index(3, 2)), source.index(3, 2));
3220 QCOMPARE(proxy.mapFromSource(QModelIndex()), QModelIndex());
3221 QCOMPARE(proxy.mapToSource(QModelIndex()), QModelIndex());
3222
3223#ifdef QT_NO_DEBUG //if Qt is compiled in debug mode, this will assert
3224 QTest::ignoreMessage(QtWarningMsg, "QSortFilterProxyModel: index from wrong model passed to mapToSource");
3225 QCOMPARE(proxy.mapToSource(source.index(2, 3)), QModelIndex());
3226 QTest::ignoreMessage(QtWarningMsg, "QSortFilterProxyModel: index from wrong model passed to mapFromSource");
3227 QCOMPARE(proxy.mapFromSource(proxy.index(6, 2)), QModelIndex());
3228#endif
3229}
3230
3231static QStandardItem *addEntry(QStandardItem* pParent, const QString &description)
3232{
3233 QStandardItem* pItem = new QStandardItem(description);
3234 pParent->appendRow(aitem: pItem);
3235 return pItem;
3236}
3237
3238void tst_QSortFilterProxyModel::removeRowsRecursive()
3239{
3240 QStandardItemModel pModel;
3241 QStandardItem *pItem1 = new QStandardItem("root");
3242 pModel.appendRow(aitem: pItem1);
3243 QList<QStandardItem *> items;
3244
3245 QStandardItem *pItem11 = addEntry(pParent: pItem1,description: "Sub-heading");
3246 items << pItem11;
3247 QStandardItem *pItem111 = addEntry(pParent: pItem11,description: "A");
3248 items << pItem111;
3249 items << addEntry(pParent: pItem111,description: "A1");
3250 items << addEntry(pParent: pItem111,description: "A2");
3251 QStandardItem *pItem112 = addEntry(pParent: pItem11,description: "B");
3252 items << pItem112;
3253 items << addEntry(pParent: pItem112,description: "B1");
3254 items << addEntry(pParent: pItem112,description: "B2");
3255 QStandardItem *pItem1123 = addEntry(pParent: pItem112,description: "B3");
3256 items << pItem1123;
3257 items << addEntry(pParent: pItem1123,description: "B3-");
3258
3259 QSortFilterProxyModel proxy;
3260 proxy.setSourceModel(&pModel);
3261
3262 QList<QPersistentModelIndex> sourceIndexes;
3263 QList<QPersistentModelIndex> proxyIndexes;
3264 for (const auto item : qAsConst(t&: items)) {
3265 QModelIndex idx = item->index();
3266 sourceIndexes << idx;
3267 proxyIndexes << proxy.mapFromSource(sourceIndex: idx);
3268 }
3269
3270 for (const auto &pidx : qAsConst(t&: sourceIndexes))
3271 QVERIFY(pidx.isValid());
3272 for (const auto &pidx : qAsConst(t&: proxyIndexes))
3273 QVERIFY(pidx.isValid());
3274
3275 QList<QStandardItem*> itemRow = pItem1->takeRow(row: 0);
3276
3277 QCOMPARE(itemRow.count(), 1);
3278 QCOMPARE(itemRow.first(), pItem11);
3279
3280 for (const auto &pidx : qAsConst(t&: sourceIndexes))
3281 QVERIFY(!pidx.isValid());
3282 for (const auto &pidx : qAsConst(t&: proxyIndexes))
3283 QVERIFY(!pidx.isValid());
3284
3285 delete pItem11;
3286}
3287
3288void tst_QSortFilterProxyModel::doubleProxySelectionSetSourceModel()
3289{
3290 QStandardItemModel *model1 = new QStandardItemModel;
3291 QStandardItem *parentItem = model1->invisibleRootItem();
3292 for (int i = 0; i < 4; ++i) {
3293 QStandardItem *item = new QStandardItem(QLatin1String("model1 item ") + QString::number(i));
3294 parentItem->appendRow(aitem: item);
3295 parentItem = item;
3296 }
3297
3298 QStandardItemModel *model2 = new QStandardItemModel;
3299 QStandardItem *parentItem2 = model2->invisibleRootItem();
3300 for (int i = 0; i < 4; ++i) {
3301 QStandardItem *item = new QStandardItem(QLatin1String("model2 item ") + QString::number(i));
3302 parentItem2->appendRow(aitem: item);
3303 parentItem2 = item;
3304 }
3305
3306 QSortFilterProxyModel *toggleProxy = new QSortFilterProxyModel;
3307 toggleProxy->setSourceModel(model1);
3308
3309 QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel;
3310 proxyModel->setSourceModel(toggleProxy);
3311
3312 QModelIndex mi = proxyModel->index(row: 0, column: 0, parent: proxyModel->index(row: 0, column: 0, parent: proxyModel->index(row: 0, column: 0)));
3313 QItemSelectionModel ism(proxyModel);
3314 ism.select(index: mi, command: QItemSelectionModel::Select);
3315 QModelIndexList mil = ism.selectedIndexes();
3316 QCOMPARE(mil.count(), 1);
3317 QCOMPARE(mil.first(), mi);
3318
3319 toggleProxy->setSourceModel(model2);
3320 // No crash, it's good news!
3321 QVERIFY(ism.selection().isEmpty());
3322}
3323
3324void tst_QSortFilterProxyModel::appearsAndSort()
3325{
3326 class PModel : public QSortFilterProxyModel
3327 {
3328 protected:
3329 bool filterAcceptsRow(int, const QModelIndex &) const override
3330 {
3331 return mVisible;
3332 }
3333
3334 public:
3335 void updateXX()
3336 {
3337 mVisible = true;
3338 invalidate();
3339 }
3340 private:
3341 bool mVisible = false;
3342 } proxyModel;
3343
3344 QStringListModel sourceModel;
3345 sourceModel.setStringList({"b", "a", "c"});
3346
3347 proxyModel.setSourceModel(&sourceModel);
3348 proxyModel.setDynamicSortFilter(true);
3349 proxyModel.sort(column: 0, order: Qt::AscendingOrder);
3350
3351 QApplication::processEvents();
3352 QCOMPARE(sourceModel.rowCount(), 3);
3353 QCOMPARE(proxyModel.rowCount(), 0); //all rows are hidden at first;
3354
3355 QSignalSpy spyAbout1(&proxyModel, &PModel::layoutAboutToBeChanged);
3356 QSignalSpy spyChanged1(&proxyModel, &PModel::layoutChanged);
3357
3358 QVERIFY(spyAbout1.isValid());
3359 QVERIFY(spyChanged1.isValid());
3360
3361 //introducing secondProxyModel to test the layoutChange when many items appears at once
3362 QSortFilterProxyModel secondProxyModel;
3363 secondProxyModel.setSourceModel(&proxyModel);
3364 secondProxyModel.setDynamicSortFilter(true);
3365 secondProxyModel.sort(column: 0, order: Qt::DescendingOrder);
3366 QCOMPARE(secondProxyModel.rowCount(), 0); //all rows are hidden at first;
3367 QSignalSpy spyAbout2(&secondProxyModel, &QSortFilterProxyModel::layoutAboutToBeChanged);
3368 QSignalSpy spyChanged2(&secondProxyModel, &QSortFilterProxyModel::layoutChanged);
3369
3370 QVERIFY(spyAbout2.isValid());
3371 QVERIFY(spyChanged2.isValid());
3372
3373 proxyModel.updateXX();
3374 QApplication::processEvents();
3375 //now rows should be visible, and sorted
3376 QCOMPARE(proxyModel.rowCount(), 3);
3377 QCOMPARE(proxyModel.data(proxyModel.index(0,0), Qt::DisplayRole).toString(), QString::fromLatin1("a"));
3378 QCOMPARE(proxyModel.data(proxyModel.index(1,0), Qt::DisplayRole).toString(), QString::fromLatin1("b"));
3379 QCOMPARE(proxyModel.data(proxyModel.index(2,0), Qt::DisplayRole).toString(), QString::fromLatin1("c"));
3380
3381 //now rows should be visible, and sorted
3382 QCOMPARE(secondProxyModel.rowCount(), 3);
3383 QCOMPARE(secondProxyModel.data(secondProxyModel.index(0,0), Qt::DisplayRole).toString(), QString::fromLatin1("c"));
3384 QCOMPARE(secondProxyModel.data(secondProxyModel.index(1,0), Qt::DisplayRole).toString(), QString::fromLatin1("b"));
3385 QCOMPARE(secondProxyModel.data(secondProxyModel.index(2,0), Qt::DisplayRole).toString(), QString::fromLatin1("a"));
3386
3387 QCOMPARE(spyAbout1.count(), 1);
3388 QCOMPARE(spyChanged1.count(), 1);
3389 QCOMPARE(spyAbout2.count(), 1);
3390 QCOMPARE(spyChanged2.count(), 1);
3391}
3392
3393void tst_QSortFilterProxyModel::unnecessaryDynamicSorting()
3394{
3395 QStringListModel model;
3396 const QStringList initial = QString("bravo charlie delta echo").split(sep: QLatin1Char(' '));
3397 model.setStringList(initial);
3398 QSortFilterProxyModel proxy;
3399 proxy.setDynamicSortFilter(false);
3400 proxy.setSourceModel(&model);
3401 proxy.sort(column: Qt::AscendingOrder);
3402
3403 //append two rows
3404 int maxrows = proxy.rowCount(parent: QModelIndex());
3405 model.insertRows(row: maxrows, count: 2);
3406 model.setData(index: model.index(row: maxrows, column: 0), value: QString("alpha"));
3407 model.setData(index: model.index(row: maxrows + 1, column: 0), value: QString("fondue"));
3408
3409 //append new items to the initial string list and compare with model
3410 QStringList expected = initial;
3411 expected << QString("alpha") << QString("fondue");
3412
3413 //if bug 7716 is present, new rows were prepended, when they should have been appended
3414 for (int row = 0; row < proxy.rowCount(parent: QModelIndex()); ++row) {
3415 QModelIndex index = proxy.index(row, column: 0, parent: QModelIndex());
3416 QCOMPARE(proxy.data(index, Qt::DisplayRole).toString(), expected.at(row));
3417 }
3418}
3419
3420class SelectionProxyModel : public QAbstractProxyModel
3421{
3422 Q_OBJECT
3423public:
3424 QModelIndex mapFromSource(QModelIndex const&) const override
3425 { return QModelIndex(); }
3426
3427 QModelIndex mapToSource(QModelIndex const&) const override
3428 { return QModelIndex(); }
3429
3430 QModelIndex index(int, int, const QModelIndex&) const override
3431 { return QModelIndex(); }
3432
3433 QModelIndex parent(const QModelIndex&) const override
3434 { return QModelIndex(); }
3435
3436 int rowCount(const QModelIndex&) const override
3437 { return 0; }
3438
3439 int columnCount(const QModelIndex&) const override
3440 { return 0; }
3441
3442 void setSourceModel(QAbstractItemModel *sourceModel) override
3443 {
3444 beginResetModel();
3445 disconnect(sender: sourceModel, signal: &QAbstractItemModel::modelAboutToBeReset,
3446 receiver: this, slot: &SelectionProxyModel::sourceModelAboutToBeReset);
3447 QAbstractProxyModel::setSourceModel( sourceModel );
3448 connect(sender: sourceModel, signal: &QAbstractItemModel::modelAboutToBeReset,
3449 receiver: this, slot: &SelectionProxyModel::sourceModelAboutToBeReset);
3450 endResetModel();
3451 }
3452
3453 void setSelectionModel(QItemSelectionModel *_selectionModel)
3454 {
3455 selectionModel = _selectionModel;
3456 }
3457
3458private slots:
3459 void sourceModelAboutToBeReset()
3460 {
3461 QVERIFY( selectionModel->selectedIndexes().size() == 1 );
3462 beginResetModel();
3463 }
3464
3465 void sourceModelReset()
3466 {
3467 endResetModel();
3468 }
3469
3470private:
3471 QItemSelectionModel *selectionModel = nullptr;
3472};
3473
3474void tst_QSortFilterProxyModel::testMultipleProxiesWithSelection()
3475{
3476 QStringListModel model;
3477 const QStringList initial = QString("bravo charlie delta echo").split(sep: QLatin1Char(' '));
3478 model.setStringList(initial);
3479
3480 QSortFilterProxyModel proxy;
3481 proxy.setSourceModel( &model );
3482
3483 SelectionProxyModel proxy1;
3484 QSortFilterProxyModel proxy2;
3485
3486 // Note that the order here matters. The order of the sourceAboutToBeReset
3487 // exposes the bug in QSortFilterProxyModel.
3488 proxy2.setSourceModel( &proxy );
3489 proxy1.setSourceModel( &proxy );
3490
3491 QItemSelectionModel selectionModel(&proxy2);
3492 proxy1.setSelectionModel( &selectionModel );
3493
3494 selectionModel.select( index: proxy2.index( row: 0, column: 0 ), command: QItemSelectionModel::Select );
3495
3496 // trick the proxy into emitting begin/end reset signals.
3497 proxy.setSourceModel(nullptr);
3498}
3499
3500static bool isValid(const QItemSelection &selection)
3501{
3502 return std::all_of(first: selection.begin(), last: selection.end(),
3503 pred: [](const QItemSelectionRange &range) { return range.isValid(); });
3504}
3505
3506void tst_QSortFilterProxyModel::mapSelectionFromSource()
3507{
3508 QStringListModel model;
3509 const QStringList initial = QString("bravo charlie delta echo").split(sep: QLatin1Char(' '));
3510 model.setStringList(initial);
3511
3512 QSortFilterProxyModel proxy;
3513 proxy.setDynamicSortFilter(true);
3514 setupFilter(model: &proxy, pattern: QLatin1String("d.*"));
3515
3516 proxy.setSourceModel(&model);
3517
3518 // Only "delta" remains.
3519 QCOMPARE(proxy.rowCount(), 1);
3520
3521 QItemSelection selection;
3522 QModelIndex charlie = model.index(row: 1, column: 0);
3523 selection.append(t: QItemSelectionRange(charlie, charlie));
3524 QModelIndex delta = model.index(row: 2, column: 0);
3525 selection.append(t: QItemSelectionRange(delta, delta));
3526 QModelIndex echo = model.index(row: 3, column: 0);
3527 selection.append(t: QItemSelectionRange(echo, echo));
3528
3529 QVERIFY(isValid(selection));
3530
3531 QItemSelection proxiedSelection = proxy.mapSelectionFromSource(sourceSelection: selection);
3532
3533 // Only "delta" is in the mapped result.
3534 QCOMPARE(proxiedSelection.size(), 1);
3535 QVERIFY(isValid(proxiedSelection));
3536}
3537
3538class Model10287 : public QStandardItemModel
3539{
3540 Q_OBJECT
3541
3542public:
3543 Model10287(QObject *parent = nullptr)
3544 : QStandardItemModel(0, 1, parent)
3545 {
3546 parentItem = new QStandardItem("parent");
3547 parentItem->setData(value: false, role: Qt::UserRole);
3548 appendRow(aitem: parentItem);
3549
3550 childItem = new QStandardItem("child");
3551 childItem->setData(value: true, role: Qt::UserRole);
3552 parentItem->appendRow(aitem: childItem);
3553
3554 childItem2 = new QStandardItem("child2");
3555 childItem2->setData(value: true, role: Qt::UserRole);
3556 parentItem->appendRow(aitem: childItem2);
3557 }
3558
3559 void removeChild()
3560 {
3561 childItem2->setData(value: false, role: Qt::UserRole);
3562 parentItem->removeRow(row: 0);
3563 }
3564
3565private:
3566 QStandardItem *parentItem, *childItem, *childItem2;
3567};
3568
3569class Proxy10287 : public QSortFilterProxyModel
3570{
3571 Q_OBJECT
3572
3573public:
3574 Proxy10287(QAbstractItemModel *model, QObject *parent = nullptr)
3575 : QSortFilterProxyModel(parent)
3576 {
3577 setSourceModel(model);
3578 setDynamicSortFilter(true);
3579 }
3580
3581protected:
3582 bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
3583 {
3584 // Filter based on UserRole in model
3585 QModelIndex i = sourceModel()->index(row: source_row, column: 0, parent: source_parent);
3586 return i.data(arole: Qt::UserRole).toBool();
3587 }
3588};
3589
3590void tst_QSortFilterProxyModel::unnecessaryMapCreation()
3591{
3592 Model10287 m;
3593 Proxy10287 p(&m);
3594 m.removeChild();
3595 // No assert failure, it passes.
3596}
3597
3598class FilteredColumnProxyModel : public QSortFilterProxyModel
3599{
3600 Q_OBJECT
3601public:
3602 using QSortFilterProxyModel::QSortFilterProxyModel;
3603protected:
3604 bool filterAcceptsColumn(int column, const QModelIndex &) const override
3605 {
3606 return column % 2 != 0;
3607 }
3608};
3609
3610void tst_QSortFilterProxyModel::filteredColumns()
3611{
3612 DynamicTreeModel *model = new DynamicTreeModel(this);
3613
3614 FilteredColumnProxyModel *proxy = new FilteredColumnProxyModel(this);
3615 proxy->setSourceModel(model);
3616
3617 new QAbstractItemModelTester(proxy, this);
3618
3619 ModelInsertCommand *insertCommand = new ModelInsertCommand(model, this);
3620 insertCommand->setNumCols(2);
3621 insertCommand->setStartRow(0);
3622 insertCommand->setEndRow(0);
3623 // Parent is QModelIndex()
3624 insertCommand->doCommand();
3625}
3626
3627class ChangableHeaderData : public QStringListModel
3628{
3629 Q_OBJECT
3630public:
3631 using QStringListModel::QStringListModel;
3632 void emitHeaderDataChanged()
3633 {
3634 headerDataChanged(orientation: Qt::Vertical, first: 0, last: rowCount() - 1);
3635 }
3636};
3637
3638
3639void tst_QSortFilterProxyModel::headerDataChanged()
3640{
3641 ChangableHeaderData *model = new ChangableHeaderData(this);
3642
3643 QStringList numbers;
3644 for (int i = 0; i < 10; ++i)
3645 numbers.append(t: QString::number(i));
3646 model->setStringList(numbers);
3647
3648 QSortFilterProxyModel *proxy = new QSortFilterProxyModel(this);
3649 proxy->setSourceModel(model);
3650
3651 new QAbstractItemModelTester(proxy, this);
3652
3653 model->emitHeaderDataChanged();
3654}
3655
3656void tst_QSortFilterProxyModel::resetInvalidate_data()
3657{
3658 QTest::addColumn<int>(name: "test");
3659 QTest::addColumn<bool>(name: "works");
3660
3661 QTest::newRow(dataTag: "nothing") << 0 << false;
3662 QTest::newRow(dataTag: "reset") << 1 << true;
3663 QTest::newRow(dataTag: "invalidate") << 2 << true;
3664 QTest::newRow(dataTag: "invalidate_filter") << 3 << true;
3665}
3666
3667void tst_QSortFilterProxyModel::resetInvalidate()
3668{
3669 QFETCH(int, test);
3670 QFETCH(bool, works);
3671
3672 struct Proxy : QSortFilterProxyModel {
3673 QString pattern;
3674 bool filterAcceptsRow(int source_row, const QModelIndex&) const override
3675 {
3676 return sourceModel()->data(index: sourceModel()->index(row: source_row, column: 0)).toString().contains(s: pattern);
3677 }
3678 void notifyChange(int test)
3679 {
3680 switch (test) {
3681 case 0: break;
3682 case 1:
3683 beginResetModel();
3684 endResetModel();
3685 break;
3686 case 2: invalidate(); break;
3687 case 3: invalidateFilter(); break;
3688 }
3689 }
3690 };
3691
3692 QStringListModel sourceModel({"Poisson", "Vache", "Brebis",
3693 "Elephant", "Cochon", "Serpent",
3694 "Mouton", "Ecureuil", "Mouche"});
3695 Proxy proxy;
3696 proxy.pattern = QString::fromLatin1(str: "n");
3697 proxy.setSourceModel(&sourceModel);
3698
3699 QCOMPARE(proxy.rowCount(), 5);
3700 for (int i = 0; i < proxy.rowCount(); i++)
3701 QVERIFY(proxy.data(proxy.index(i,0)).toString().contains('n'));
3702
3703 proxy.pattern = QString::fromLatin1(str: "o");
3704 proxy.notifyChange(test);
3705
3706 QCOMPARE(proxy.rowCount(), works ? 4 : 5);
3707 bool ok = true;
3708 for (int i = 0; i < proxy.rowCount(); i++) {
3709 if (!proxy.data(index: proxy.index(row: i,column: 0)).toString().contains(c: 'o'))
3710 ok = false;
3711 }
3712 QCOMPARE(ok, works);
3713}
3714
3715/**
3716 * A proxy which changes the background color for items ending in 'y' or 'r'
3717 */
3718class CustomDataProxy : public QSortFilterProxyModel
3719{
3720 Q_OBJECT
3721
3722public:
3723 CustomDataProxy(QObject *parent = nullptr)
3724 : QSortFilterProxyModel(parent)
3725 {
3726 setDynamicSortFilter(true);
3727 }
3728
3729 void setSourceModel(QAbstractItemModel *sourceModel) override
3730 {
3731 // It would be possible to use only the modelReset signal of the source model to clear
3732 // the data in *this, however, this requires that the slot is connected
3733 // before QSortFilterProxyModel::setSourceModel is called, and even then depends
3734 // on the order of invocation of slots being the same as the order of connection.
3735 // ie, not reliable.
3736// connect(sourceModel, SIGNAL(modelReset()), SLOT(resetInternalData()));
3737 QSortFilterProxyModel::setSourceModel(sourceModel);
3738 // Making the connect after the setSourceModel call clears the data too late.
3739// connect(sourceModel, SIGNAL(modelReset()), SLOT(resetInternalData()));
3740
3741 // This could be done in data(), but the point is to need to cache something in the proxy
3742 // which needs to be cleared on reset.
3743 for (int i = 0; i < sourceModel->rowCount(); ++i)
3744 {
3745 if (sourceModel->index(row: i, column: 0).data().toString().endsWith(c: QLatin1Char('y')))
3746 m_backgroundColours.insert(key: i, value: Qt::blue);
3747 else if (sourceModel->index(row: i, column: 0).data().toString().endsWith(c: QLatin1Char('r')))
3748 m_backgroundColours.insert(key: i, value: Qt::red);
3749 }
3750 }
3751
3752 QVariant data(const QModelIndex &index, int role) const override
3753 {
3754 if (role != Qt::BackgroundRole)
3755 return QSortFilterProxyModel::data(index, role);
3756 return m_backgroundColours.value(key: index.row());
3757 }
3758
3759private slots:
3760 void resetInternalData()
3761 {
3762 m_backgroundColours.clear();
3763 }
3764
3765private:
3766 QHash<int, QColor> m_backgroundColours;
3767};
3768
3769class ModelObserver : public QObject
3770{
3771 Q_OBJECT
3772public:
3773 ModelObserver(QAbstractItemModel *model, QObject *parent = nullptr)
3774 : QObject(parent), m_model(model)
3775 {
3776 connect(sender: m_model, signal: &QAbstractItemModel::modelAboutToBeReset,
3777 receiver: this, slot: &ModelObserver::modelAboutToBeReset);
3778 connect(sender: m_model, signal: &QAbstractItemModel::modelReset,
3779 receiver: this, slot: &ModelObserver::modelReset);
3780 }
3781
3782public slots:
3783 void modelAboutToBeReset()
3784 {
3785 int reds = 0, blues = 0;
3786 for (int i = 0; i < m_model->rowCount(); ++i)
3787 {
3788 QColor color = m_model->index(row: i, column: 0).data(arole: Qt::BackgroundRole).value<QColor>();
3789 if (color == Qt::blue)
3790 ++blues;
3791 else if (color == Qt::red)
3792 ++reds;
3793 }
3794 QCOMPARE(blues, 11);
3795 QCOMPARE(reds, 4);
3796 }
3797
3798 void modelReset()
3799 {
3800 int reds = 0, blues = 0;
3801 for (int i = 0; i < m_model->rowCount(); ++i)
3802 {
3803 QColor color = m_model->index(row: i, column: 0).data(arole: Qt::BackgroundRole).value<QColor>();
3804 if (color == Qt::blue)
3805 ++blues;
3806 else if (color == Qt::red)
3807 ++reds;
3808 }
3809 QCOMPARE(reds, 0);
3810 QCOMPARE(blues, 0);
3811 }
3812
3813private:
3814 QAbstractItemModel * const m_model;
3815
3816};
3817
3818void tst_QSortFilterProxyModel::testResetInternalData()
3819{
3820 QStringListModel model({"Monday",
3821 "Tuesday",
3822 "Wednesday",
3823 "Thursday",
3824 "Friday",
3825 "January",
3826 "February",
3827 "March",
3828 "April",
3829 "May",
3830 "Saturday",
3831 "June",
3832 "Sunday",
3833 "July",
3834 "August",
3835 "September",
3836 "October",
3837 "November",
3838 "December"});
3839
3840 CustomDataProxy proxy;
3841 proxy.setSourceModel(&model);
3842
3843 ModelObserver observer(&proxy);
3844
3845 // Cause the source model to reset.
3846 model.setStringList({"Spam", "Eggs"});
3847
3848}
3849
3850void tst_QSortFilterProxyModel::testParentLayoutChanged()
3851{
3852 QStandardItemModel model;
3853 QStandardItem *parentItem = model.invisibleRootItem();
3854 for (int i = 0; i < 4; ++i) {
3855 {
3856 QStandardItem *item = new QStandardItem(QLatin1String("item ") + QString::number(i));
3857 parentItem->appendRow(aitem: item);
3858 }
3859 {
3860 QStandardItem *item = new QStandardItem(QLatin1String("item 1") + QString::number(i));
3861 parentItem->appendRow(aitem: item);
3862 parentItem = item;
3863 }
3864 }
3865 // item 0
3866 // item 10
3867 // - item 1
3868 // - item 11
3869 // - item 2
3870 // - item 12
3871 // ...
3872
3873 QSortFilterProxyModel proxy;
3874 proxy.sort(column: 0, order: Qt::AscendingOrder);
3875 proxy.setDynamicSortFilter(true);
3876
3877 proxy.setSourceModel(&model);
3878 proxy.setObjectName("proxy");
3879
3880 // When Proxy1 emits layoutChanged(QList<QPersistentModelIndex>) this
3881 // one will too, with mapped indexes.
3882 QSortFilterProxyModel proxy2;
3883 proxy2.sort(column: 0, order: Qt::AscendingOrder);
3884 proxy2.setDynamicSortFilter(true);
3885
3886 proxy2.setSourceModel(&proxy);
3887 proxy2.setObjectName("proxy2");
3888
3889 QSignalSpy dataChangedSpy(&model, &QSortFilterProxyModel::dataChanged);
3890
3891 QVERIFY(dataChangedSpy.isValid());
3892
3893 // Verify that the no-arg signal is still emitted.
3894 QSignalSpy layoutAboutToBeChangedSpy(&proxy, &QSortFilterProxyModel::layoutAboutToBeChanged);
3895 QSignalSpy layoutChangedSpy(&proxy, &QSortFilterProxyModel::layoutChanged);
3896
3897 QVERIFY(layoutAboutToBeChangedSpy.isValid());
3898 QVERIFY(layoutChangedSpy.isValid());
3899
3900 QSignalSpy parentsAboutToBeChangedSpy(&proxy, &QSortFilterProxyModel::layoutAboutToBeChanged);
3901 QSignalSpy parentsChangedSpy(&proxy, &QSortFilterProxyModel::layoutChanged);
3902
3903 QVERIFY(parentsAboutToBeChangedSpy.isValid());
3904 QVERIFY(parentsChangedSpy.isValid());
3905
3906 QSignalSpy proxy2ParentsAboutToBeChangedSpy(&proxy2, &QSortFilterProxyModel::layoutAboutToBeChanged);
3907 QSignalSpy proxy2ParentsChangedSpy(&proxy2, &QSortFilterProxyModel::layoutChanged);
3908
3909 QVERIFY(proxy2ParentsAboutToBeChangedSpy.isValid());
3910 QVERIFY(proxy2ParentsChangedSpy.isValid());
3911
3912 QStandardItem *item = model.invisibleRootItem()->child(row: 1)->child(row: 1);
3913 QCOMPARE(item->text(), QStringLiteral("item 11"));
3914
3915 // Ensure mapped:
3916 proxy.mapFromSource(sourceIndex: model.indexFromItem(item));
3917
3918 item->setText("Changed");
3919
3920 QCOMPARE(dataChangedSpy.size(), 1);
3921 QCOMPARE(layoutAboutToBeChangedSpy.size(), 1);
3922 QCOMPARE(layoutChangedSpy.size(), 1);
3923 QCOMPARE(parentsAboutToBeChangedSpy.size(), 1);
3924 QCOMPARE(parentsChangedSpy.size(), 1);
3925 QCOMPARE(proxy2ParentsAboutToBeChangedSpy.size(), 1);
3926 QCOMPARE(proxy2ParentsChangedSpy.size(), 1);
3927
3928 QVariantList beforeSignal = parentsAboutToBeChangedSpy.first();
3929 QVariantList afterSignal = parentsChangedSpy.first();
3930
3931 QCOMPARE(beforeSignal.size(), 2);
3932 QCOMPARE(afterSignal.size(), 2);
3933
3934 QList<QPersistentModelIndex> beforeParents = beforeSignal.first().value<QList<QPersistentModelIndex> >();
3935 QList<QPersistentModelIndex> afterParents = afterSignal.first().value<QList<QPersistentModelIndex> >();
3936
3937 QCOMPARE(beforeParents.size(), 1);
3938 QCOMPARE(afterParents.size(), 1);
3939
3940 QVERIFY(beforeParents.first().isValid());
3941 QVERIFY(beforeParents.first() == afterParents.first());
3942
3943 QVERIFY(beforeParents.first() == proxy.mapFromSource(model.indexFromItem(model.invisibleRootItem()->child(1))));
3944
3945 const QList<QPersistentModelIndex> proxy2BeforeList =
3946 proxy2ParentsAboutToBeChangedSpy.first().first().value<QList<QPersistentModelIndex> >();
3947 const QList<QPersistentModelIndex> proxy2AfterList =
3948 proxy2ParentsChangedSpy.first().first().value<QList<QPersistentModelIndex> >();
3949
3950 QCOMPARE(proxy2BeforeList.size(), beforeParents.size());
3951 QCOMPARE(proxy2AfterList.size(), afterParents.size());
3952 for (const QPersistentModelIndex &idx : proxy2BeforeList)
3953 QVERIFY(beforeParents.contains(proxy2.mapToSource(idx)));
3954 for (const QPersistentModelIndex &idx : proxy2AfterList)
3955 QVERIFY(afterParents.contains(proxy2.mapToSource(idx)));
3956}
3957
3958class SignalArgumentChecker : public QObject
3959{
3960 Q_OBJECT
3961public:
3962 SignalArgumentChecker(QAbstractItemModel *model, QAbstractProxyModel *proxy, QObject *parent = nullptr)
3963 : QObject(parent), m_proxy(proxy)
3964 {
3965 connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeMoved,
3966 receiver: this, slot: &SignalArgumentChecker::rowsAboutToBeMoved);
3967 connect(sender: model, signal: &QAbstractItemModel::rowsMoved,
3968 receiver: this, slot: &SignalArgumentChecker::rowsMoved);
3969 connect(sender: proxy, signal: &QAbstractProxyModel::layoutAboutToBeChanged,
3970 receiver: this, slot: &SignalArgumentChecker::layoutAboutToBeChanged);
3971 connect(sender: proxy, signal: &QAbstractProxyModel::layoutChanged,
3972 receiver: this, slot: &SignalArgumentChecker::layoutChanged);
3973 }
3974
3975private slots:
3976 void rowsAboutToBeMoved(const QModelIndex &source, int, int, const QModelIndex &destination, int)
3977 {
3978 m_p1PersistentBefore = source;
3979 m_p2PersistentBefore = destination;
3980 m_p2FirstProxyChild = m_proxy->index(row: 0, column: 0, parent: m_proxy->mapFromSource(sourceIndex: destination));
3981 }
3982
3983 void rowsMoved(const QModelIndex &source, int, int, const QModelIndex &destination, int)
3984 {
3985 m_p1PersistentAfter = source;
3986 m_p2PersistentAfter = destination;
3987 }
3988
3989 void layoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents)
3990 {
3991 QVERIFY(m_p1PersistentBefore.isValid());
3992 QVERIFY(m_p2PersistentBefore.isValid());
3993 QCOMPARE(parents.size(), 2);
3994 QVERIFY(parents.first() != parents.at(1));
3995 QVERIFY(parents.contains(m_proxy->mapFromSource(m_p1PersistentBefore)));
3996 QVERIFY(parents.contains(m_proxy->mapFromSource(m_p2PersistentBefore)));
3997 }
3998
3999 void layoutChanged(const QList<QPersistentModelIndex> &parents)
4000 {
4001 QVERIFY(m_p1PersistentAfter.isValid());
4002 QVERIFY(m_p2PersistentAfter.isValid());
4003 QCOMPARE(parents.size(), 2);
4004 QVERIFY(parents.first() != parents.at(1));
4005 QVERIFY(parents.contains(m_proxy->mapFromSource(m_p1PersistentAfter)));
4006 QVERIFY(parents.contains(m_proxy->mapFromSource(m_p2PersistentAfter)));
4007
4008 // In the source model, the rows were moved to row 1 in the parent.
4009 // m_p2FirstProxyChild was created with row 0 in the proxy.
4010 // The moved rows in the proxy do not appear at row 1 because of sorting.
4011 // Sorting causes them to appear at row 0 instead, pushing what used to
4012 // be row 0 in the proxy down by two rows.
4013 QCOMPARE(m_p2FirstProxyChild.row(), 2);
4014 }
4015
4016private:
4017 QAbstractProxyModel *m_proxy;
4018 QPersistentModelIndex m_p1PersistentBefore;
4019 QPersistentModelIndex m_p2PersistentBefore;
4020 QPersistentModelIndex m_p1PersistentAfter;
4021 QPersistentModelIndex m_p2PersistentAfter;
4022
4023 QPersistentModelIndex m_p2FirstProxyChild;
4024};
4025
4026void tst_QSortFilterProxyModel::moveSourceRows()
4027{
4028 DynamicTreeModel model;
4029
4030 {
4031 ModelInsertCommand insertCommand(&model);
4032 insertCommand.setStartRow(0);
4033 insertCommand.setEndRow(9);
4034 insertCommand.doCommand();
4035 }
4036 {
4037 ModelInsertCommand insertCommand(&model);
4038 insertCommand.setAncestorRowNumbers(QList<int>() << 2);
4039 insertCommand.setStartRow(0);
4040 insertCommand.setEndRow(9);
4041 insertCommand.doCommand();
4042 }
4043 {
4044 ModelInsertCommand insertCommand(&model);
4045 insertCommand.setAncestorRowNumbers(QList<int>() << 5);
4046 insertCommand.setStartRow(0);
4047 insertCommand.setEndRow(9);
4048 insertCommand.doCommand();
4049 }
4050
4051 QSortFilterProxyModel proxy;
4052 proxy.setDynamicSortFilter(true);
4053 proxy.sort(column: 0, order: Qt::AscendingOrder);
4054
4055 // We need to check the arguments at emission time
4056 SignalArgumentChecker checker(&model, &proxy);
4057
4058 proxy.setSourceModel(&model);
4059
4060 QSortFilterProxyModel filterProxy;
4061 filterProxy.setDynamicSortFilter(true);
4062 filterProxy.sort(column: 0, order: Qt::AscendingOrder);
4063 filterProxy.setSourceModel(&proxy);
4064 setupFilter(model: &filterProxy, pattern: QLatin1String("6")); // One of the parents
4065
4066 QSortFilterProxyModel filterBothProxy;
4067 filterBothProxy.setDynamicSortFilter(true);
4068 filterBothProxy.sort(column: 0, order: Qt::AscendingOrder);
4069 filterBothProxy.setSourceModel(&proxy);
4070 setupFilter(model: &filterBothProxy, pattern: QLatin1String("5")); // The parents are 6 and 3. This filters both out.
4071
4072 QSignalSpy modelBeforeSpy(&model, &DynamicTreeModel::rowsAboutToBeMoved);
4073 QSignalSpy modelAfterSpy(&model, &DynamicTreeModel::rowsMoved);
4074 QSignalSpy proxyBeforeMoveSpy(m_proxy, &QSortFilterProxyModel::rowsAboutToBeMoved);
4075 QSignalSpy proxyAfterMoveSpy(m_proxy, &QSortFilterProxyModel::rowsMoved);
4076 QSignalSpy proxyBeforeParentLayoutSpy(&proxy, &QSortFilterProxyModel::layoutAboutToBeChanged);
4077 QSignalSpy proxyAfterParentLayoutSpy(&proxy, &QSortFilterProxyModel::layoutChanged);
4078 QSignalSpy filterBeforeParentLayoutSpy(&filterProxy, &QSortFilterProxyModel::layoutAboutToBeChanged);
4079 QSignalSpy filterAfterParentLayoutSpy(&filterProxy, &QSortFilterProxyModel::layoutChanged);
4080 QSignalSpy filterBothBeforeParentLayoutSpy(&filterBothProxy, &QSortFilterProxyModel::layoutAboutToBeChanged);
4081 QSignalSpy filterBothAfterParentLayoutSpy(&filterBothProxy, &QSortFilterProxyModel::layoutChanged);
4082
4083 QVERIFY(modelBeforeSpy.isValid());
4084 QVERIFY(modelAfterSpy.isValid());
4085 QVERIFY(proxyBeforeMoveSpy.isValid());
4086 QVERIFY(proxyAfterMoveSpy.isValid());
4087 QVERIFY(proxyBeforeParentLayoutSpy.isValid());
4088 QVERIFY(proxyAfterParentLayoutSpy.isValid());
4089 QVERIFY(filterBeforeParentLayoutSpy.isValid());
4090 QVERIFY(filterAfterParentLayoutSpy.isValid());
4091 QVERIFY(filterBothBeforeParentLayoutSpy.isValid());
4092 QVERIFY(filterBothAfterParentLayoutSpy.isValid());
4093
4094 {
4095 ModelMoveCommand moveCommand(&model, nullptr);
4096 moveCommand.setAncestorRowNumbers(QList<int>() << 2);
4097 moveCommand.setDestAncestors(QList<int>() << 5);
4098 moveCommand.setStartRow(3);
4099 moveCommand.setEndRow(4);
4100 moveCommand.setDestRow(1);
4101 moveCommand.doCommand();
4102 }
4103
4104 // Proxy notifies layout change
4105 QCOMPARE(modelBeforeSpy.size(), 1);
4106 QCOMPARE(proxyBeforeParentLayoutSpy.size(), 1);
4107 QCOMPARE(modelAfterSpy.size(), 1);
4108 QCOMPARE(proxyAfterParentLayoutSpy.size(), 1);
4109
4110 // But it doesn't notify a move.
4111 QCOMPARE(proxyBeforeMoveSpy.size(), 0);
4112 QCOMPARE(proxyAfterMoveSpy.size(), 0);
4113
4114 QCOMPARE(filterBeforeParentLayoutSpy.size(), 1);
4115 QCOMPARE(filterAfterParentLayoutSpy.size(), 1);
4116
4117 QList<QPersistentModelIndex> filterBeforeParents = filterBeforeParentLayoutSpy.first().first().value<QList<QPersistentModelIndex> >();
4118 QList<QPersistentModelIndex> filterAfterParents = filterAfterParentLayoutSpy.first().first().value<QList<QPersistentModelIndex> >();
4119
4120 QCOMPARE(filterBeforeParents.size(), 1);
4121 QCOMPARE(filterAfterParents.size(), 1);
4122
4123 QCOMPARE(
4124 filterBeforeParentLayoutSpy.first().at(1).value<QAbstractItemModel::LayoutChangeHint>(),
4125 QAbstractItemModel::NoLayoutChangeHint);
4126 QCOMPARE(filterAfterParentLayoutSpy.first().at(1).value<QAbstractItemModel::LayoutChangeHint>(),
4127 QAbstractItemModel::NoLayoutChangeHint);
4128
4129 QCOMPARE(filterBothBeforeParentLayoutSpy.size(), 0);
4130 QCOMPARE(filterBothAfterParentLayoutSpy.size(), 0);
4131}
4132
4133class FilterProxy : public QSortFilterProxyModel
4134{
4135 Q_OBJECT
4136public:
4137 using QSortFilterProxyModel::QSortFilterProxyModel;
4138public slots:
4139 void setMode(bool on)
4140 {
4141 mode = on;
4142 invalidateFilter();
4143 }
4144
4145protected:
4146 bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
4147 {
4148 if (mode) {
4149 if (!source_parent.isValid())
4150 return true;
4151 else
4152 return (source_row % 2) != 0;
4153 } else {
4154 if (!source_parent.isValid())
4155 return source_row >= 2 && source_row < 10;
4156 else
4157 return true;
4158 }
4159 }
4160
4161private:
4162 bool mode = false;
4163};
4164
4165void tst_QSortFilterProxyModel::hierarchyFilterInvalidation()
4166{
4167 QStandardItemModel model;
4168 for (int i = 0; i < 10; ++i) {
4169 const QString rowText = QLatin1String("Row ") + QString::number(i);
4170 QStandardItem *child = new QStandardItem(rowText);
4171 for (int j = 0; j < 1; ++j) {
4172 child->appendRow(aitem: new QStandardItem(rowText + QLatin1Char('/') + QString::number(j)));
4173 }
4174 model.appendRow(aitem: child);
4175 }
4176
4177 FilterProxy proxy;
4178 proxy.setSourceModel(&model);
4179
4180 QTreeView view;
4181 view.setModel(&proxy);
4182
4183 view.setCurrentIndex(proxy.index(row: 0, column: 0, parent: proxy.index(row: 2, column: 0)));
4184
4185 view.show();
4186 QVERIFY(QTest::qWaitForWindowExposed(&view));
4187
4188 proxy.setMode(true);
4189}
4190
4191class FilterProxy2 : public QSortFilterProxyModel
4192{
4193 Q_OBJECT
4194public:
4195 using QSortFilterProxyModel::QSortFilterProxyModel;
4196public slots:
4197 void setMode(bool on)
4198 {
4199 mode = on;
4200 invalidateFilter();
4201 }
4202
4203protected:
4204 bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
4205 {
4206 if (source_parent.isValid())
4207 return true;
4208 if (0 == source_row)
4209 return true;
4210 return !mode;
4211 }
4212
4213private:
4214 bool mode = false;
4215};
4216
4217void tst_QSortFilterProxyModel::simpleFilterInvalidation()
4218{
4219 QStandardItemModel model;
4220 for (int i = 0; i < 2; ++i) {
4221 QStandardItem *child = new QStandardItem(QLatin1String("Row ") + QString::number(i));
4222 child->appendRow(aitem: new QStandardItem("child"));
4223 model.appendRow(aitem: child);
4224 }
4225
4226 FilterProxy2 proxy;
4227 proxy.setSourceModel(&model);
4228
4229 QTreeView view;
4230 view.setModel(&proxy);
4231
4232 view.show();
4233 QVERIFY(QTest::qWaitForWindowExposed(&view));
4234
4235 proxy.setMode(true);
4236 model.insertRow(arow: 0, aitem: new QStandardItem("extra"));
4237}
4238
4239class CustomRoleNameModel : public QAbstractListModel
4240{
4241 Q_OBJECT
4242public:
4243 using QAbstractListModel::QAbstractListModel;
4244 QVariant data(const QModelIndex &index, int role) const override
4245 {
4246 Q_UNUSED(index)
4247 Q_UNUSED(role)
4248 return QVariant();
4249 }
4250
4251 int rowCount(const QModelIndex &parent = QModelIndex()) const override
4252 {
4253 Q_UNUSED(parent)
4254 return 0;
4255 }
4256
4257 QHash<int, QByteArray> roleNames() const override
4258 {
4259 QHash<int, QByteArray> rn = QAbstractListModel::roleNames();
4260 rn[Qt::UserRole + 1] = "custom";
4261 return rn;
4262 }
4263};
4264
4265void tst_QSortFilterProxyModel::chainedProxyModelRoleNames()
4266{
4267 QSortFilterProxyModel proxy1;
4268 QSortFilterProxyModel proxy2;
4269 CustomRoleNameModel customModel;
4270
4271 proxy2.setSourceModel(&proxy1);
4272
4273 // changing the sourceModel of proxy1 must also update roleNames of proxy2
4274 proxy1.setSourceModel(&customModel);
4275 QVERIFY(proxy2.roleNames().value(Qt::UserRole + 1) == "custom");
4276}
4277
4278// A source model with ABABAB rows, where only A rows accept drops.
4279// It will then be sorted by a QSFPM.
4280class DropOnOddRows : public QAbstractListModel
4281{
4282 Q_OBJECT
4283public:
4284 using QAbstractListModel::QAbstractListModel;
4285 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
4286 {
4287 if (role == Qt::DisplayRole)
4288 return (index.row() % 2 == 0) ? "A" : "B";
4289 return QVariant();
4290 }
4291
4292 int rowCount(const QModelIndex &parent = QModelIndex()) const override
4293 {
4294 Q_UNUSED(parent)
4295 return 10;
4296 }
4297
4298 bool canDropMimeData(const QMimeData *, Qt::DropAction,
4299 int row, int column, const QModelIndex &parent) const override
4300 {
4301 Q_UNUSED(row)
4302 Q_UNUSED(column)
4303 return parent.row() % 2 == 0;
4304 }
4305};
4306
4307class SourceAssertion : public QSortFilterProxyModel
4308{
4309 Q_OBJECT
4310public:
4311 using QSortFilterProxyModel::QSortFilterProxyModel;
4312 QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
4313 {
4314 Q_ASSERT(sourceModel());
4315 return QSortFilterProxyModel::mapToSource(proxyIndex);
4316 }
4317};
4318
4319void tst_QSortFilterProxyModel::noMapAfterSourceDelete()
4320{
4321 SourceAssertion proxy;
4322 QStringListModel *model = new QStringListModel(QStringList() << "Foo" << "Bar");
4323
4324 proxy.setSourceModel(model);
4325
4326 // Create mappings
4327 QPersistentModelIndex persistent = proxy.index(row: 0, column: 0);
4328
4329 QVERIFY(persistent.isValid());
4330
4331 delete model;
4332
4333 QVERIFY(!persistent.isValid());
4334}
4335
4336// QTBUG-39549, test whether canDropMimeData(), dropMimeData() are proxied as well
4337// by invoking them on a QSortFilterProxyModel proxying a QStandardItemModel that allows drops
4338// on row #1, filtering for that row.
4339class DropTestModel : public QStandardItemModel
4340{
4341 Q_OBJECT
4342public:
4343 explicit DropTestModel(QObject *parent = nullptr) : QStandardItemModel(0, 1, parent)
4344 {
4345 appendRow(aitem: new QStandardItem(QStringLiteral("Row0")));
4346 appendRow(aitem: new QStandardItem(QStringLiteral("Row1")));
4347 }
4348
4349 bool canDropMimeData(const QMimeData *, Qt::DropAction,
4350 int row, int /* column */, const QModelIndex & /* parent */) const override
4351 { return row == 1; }
4352
4353 bool dropMimeData(const QMimeData *, Qt::DropAction,
4354 int row, int /* column */, const QModelIndex & /* parent */) override
4355 { return row == 1; }
4356};
4357
4358void tst_QSortFilterProxyModel::forwardDropApi()
4359{
4360 QSortFilterProxyModel model;
4361 model.setSourceModel(new DropTestModel(&model));
4362 model.setFilterFixedString(QStringLiteral("Row1"));
4363 QCOMPARE(model.rowCount(), 1);
4364 QVERIFY(model.canDropMimeData(nullptr, Qt::CopyAction, 0, 0, QModelIndex()));
4365 QVERIFY(model.dropMimeData(nullptr, Qt::CopyAction, 0, 0, QModelIndex()));
4366}
4367
4368static QString rowTexts(QAbstractItemModel *model)
4369{
4370 QString str;
4371 for (int row = 0 ; row < model->rowCount(); ++row)
4372 str += model->index(row, column: 0).data().toString();
4373 return str;
4374}
4375
4376void tst_QSortFilterProxyModel::canDropMimeData()
4377{
4378 // Given a source model which only supports dropping on even rows
4379 DropOnOddRows sourceModel;
4380 QCOMPARE(rowTexts(&sourceModel), QString("ABABABABAB"));
4381
4382 // and a proxy model that sorts the rows
4383 QSortFilterProxyModel proxy;
4384 proxy.setSourceModel(&sourceModel);
4385 proxy.sort(column: 0, order: Qt::AscendingOrder);
4386 QCOMPARE(rowTexts(&proxy), QString("AAAAABBBBB"));
4387
4388 // the proxy should correctly map canDropMimeData to the source model,
4389 // i.e. accept drops on the first 5 rows and refuse drops on the next 5.
4390 for (int row = 0; row < proxy.rowCount(); ++row)
4391 QCOMPARE(proxy.canDropMimeData(nullptr, Qt::CopyAction, -1, -1, proxy.index(row, 0)), row < 5);
4392}
4393
4394void tst_QSortFilterProxyModel::resortingDoesNotBreakTreeModels()
4395{
4396 QStandardItemModel *treeModel = new QStandardItemModel(this);
4397 QStandardItem *e1 = new QStandardItem("Loading...");
4398 e1->appendRow(aitem: new QStandardItem("entry10"));
4399 treeModel->appendRow(aitem: e1);
4400 QStandardItem *e0 = new QStandardItem("Loading...");
4401 e0->appendRow(aitem: new QStandardItem("entry00"));
4402 e0->appendRow(aitem: new QStandardItem("entry01"));
4403 treeModel->appendRow(aitem: e0);
4404
4405 QSortFilterProxyModel proxy;
4406 proxy.setDynamicSortFilter(true);
4407 proxy.sort(column: 0);
4408 proxy.setSourceModel(treeModel);
4409
4410 QAbstractItemModelTester modelTester(&proxy);
4411
4412 QCOMPARE(proxy.rowCount(), 2);
4413 e1->setText("entry1");
4414 e0->setText("entry0");
4415
4416 QModelIndex pi0 = proxy.index(row: 0, column: 0);
4417 QCOMPARE(pi0.data().toString(), QString("entry0"));
4418 QCOMPARE(proxy.rowCount(pi0), 2);
4419
4420 QModelIndex pi01 = proxy.index(row: 1, column: 0, parent: pi0);
4421 QCOMPARE(pi01.data().toString(), QString("entry01"));
4422
4423 QModelIndex pi1 = proxy.index(row: 1, column: 0);
4424 QCOMPARE(pi1.data().toString(), QString("entry1"));
4425 QCOMPARE(proxy.rowCount(pi1), 1);
4426}
4427
4428void tst_QSortFilterProxyModel::filterHint()
4429{
4430 // test that a filtering model does not emit layoutChanged with a hint
4431 QStringListModel model({"one", "two", "three", "four", "five", "six"});
4432 QSortFilterProxyModel proxy1;
4433 proxy1.setSourceModel(&model);
4434 proxy1.setSortRole(Qt::DisplayRole);
4435 proxy1.setDynamicSortFilter(true);
4436 proxy1.sort(column: 0);
4437
4438 QSortFilterProxyModel proxy2;
4439 proxy2.setSourceModel(&proxy1);
4440 proxy2.setFilterRole(Qt::DisplayRole);
4441 setupFilter(model: &proxy2, pattern: QLatin1String("^[^ ]*$"));
4442 proxy2.setDynamicSortFilter(true);
4443
4444 QSignalSpy proxy1BeforeSpy(&proxy1, &QSortFilterProxyModel::layoutAboutToBeChanged);
4445 QSignalSpy proxy1AfterSpy(&proxy1, &QSortFilterProxyModel::layoutChanged);
4446 QSignalSpy proxy2BeforeSpy(&proxy2, &QSortFilterProxyModel::layoutAboutToBeChanged);
4447 QSignalSpy proxy2AfterSpy(&proxy2, &QSortFilterProxyModel::layoutChanged);
4448
4449 model.setData(index: model.index(row: 2), QStringLiteral("modified three"), role: Qt::DisplayRole);
4450
4451 // The first proxy was re-sorted as one item as changed.
4452 QCOMPARE(proxy1BeforeSpy.size(), 1);
4453 QCOMPARE(proxy1BeforeSpy.first().at(1).value<QAbstractItemModel::LayoutChangeHint>(),
4454 QAbstractItemModel::VerticalSortHint);
4455 QCOMPARE(proxy1AfterSpy.size(), 1);
4456 QCOMPARE(proxy1AfterSpy.first().at(1).value<QAbstractItemModel::LayoutChangeHint>(),
4457 QAbstractItemModel::VerticalSortHint);
4458
4459 // But the second proxy must not have the VerticalSortHint since an item was filtered
4460 QCOMPARE(proxy2BeforeSpy.size(), 1);
4461 QCOMPARE(proxy2BeforeSpy.first().at(1).value<QAbstractItemModel::LayoutChangeHint>(),
4462 QAbstractItemModel::NoLayoutChangeHint);
4463 QCOMPARE(proxy2AfterSpy.size(), 1);
4464 QCOMPARE(proxy2AfterSpy.first().at(1).value<QAbstractItemModel::LayoutChangeHint>(),
4465 QAbstractItemModel::NoLayoutChangeHint);
4466}
4467
4468/**
4469
4470 Creates a model where each item has one child, to a set depth,
4471 and the last item has no children. For a model created with
4472 setDepth(4):
4473
4474 - 1
4475 - - 2
4476 - - - 3
4477 - - - - 4
4478*/
4479class StepTreeModel : public QAbstractItemModel
4480{
4481 Q_OBJECT
4482public:
4483 using QAbstractItemModel::QAbstractItemModel;
4484
4485 int columnCount(const QModelIndex& = QModelIndex()) const override { return 1; }
4486
4487 int rowCount(const QModelIndex& parent = QModelIndex()) const override
4488 {
4489 quintptr parentId = (parent.isValid()) ? parent.internalId() : 0;
4490 return (parentId < m_depth) ? 1 : 0;
4491 }
4492
4493 QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override
4494 {
4495 if (role != Qt::DisplayRole)
4496 return QVariant();
4497
4498 return QString::number(index.internalId());
4499 }
4500
4501 QModelIndex index(int, int, const QModelIndex& parent = QModelIndex()) const override
4502 {
4503 // QTBUG-44962: Would we always expect the parent to belong to the model
4504 qCDebug(lcItemModels) << parent.model() << this;
4505 Q_ASSERT(!parent.isValid() || parent.model() == this);
4506
4507 quintptr parentId = (parent.isValid()) ? parent.internalId() : 0;
4508 if (parentId >= m_depth)
4509 return QModelIndex();
4510
4511 return createIndex(arow: 0, acolumn: 0, aid: parentId + 1);
4512 }
4513
4514 QModelIndex parent(const QModelIndex& index) const override
4515 {
4516 if (index.internalId() == 0)
4517 return QModelIndex();
4518
4519 return createIndex(arow: 0, acolumn: 0, aid: index.internalId() - 1);
4520 }
4521
4522 void setDepth(quintptr depth)
4523 {
4524 quintptr parentIdWithLayoutChange = (m_depth < depth) ? m_depth : depth;
4525
4526 QList<QPersistentModelIndex> parentsOfLayoutChange;
4527 parentsOfLayoutChange.push_back(t: createIndex(arow: 0, acolumn: 0, aid: parentIdWithLayoutChange));
4528
4529 layoutAboutToBeChanged(parents: parentsOfLayoutChange);
4530
4531 auto existing = persistentIndexList();
4532
4533 QList<QModelIndex> updated;
4534
4535 for (auto idx : existing) {
4536 if (indexDepth(index: idx) <= depth)
4537 updated.push_back(t: idx);
4538 else
4539 updated.push_back(t: {});
4540 }
4541
4542 m_depth = depth;
4543
4544 changePersistentIndexList(from: existing, to: updated);
4545
4546 layoutChanged(parents: parentsOfLayoutChange);
4547 }
4548
4549private:
4550 static quintptr indexDepth(QModelIndex const& index)
4551 {
4552 return (index.isValid()) ? 1 + indexDepth(index: index.parent()) : 0;
4553 }
4554
4555private:
4556 quintptr m_depth = 0;
4557};
4558
4559void tst_QSortFilterProxyModel::sourceLayoutChangeLeavesValidPersistentIndexes()
4560{
4561 StepTreeModel model;
4562 Q_SET_OBJECT_NAME(model);
4563 model.setDepth(4);
4564
4565 QSortFilterProxyModel proxy1;
4566 proxy1.setSourceModel(&model);
4567 Q_SET_OBJECT_NAME(proxy1);
4568 setupFilter(model: &proxy1, pattern: QLatin1String("1|2"));
4569
4570 // The current state of things:
4571 // model proxy
4572 // - 1 - 1
4573 // - - 2 - - 2
4574 // - - - 3
4575 // - - - - 4
4576
4577 // The setDepth call below removes '4' with a layoutChanged call.
4578 // Because the proxy filters that out anyway, the proxy doesn't need
4579 // to emit any signals or update persistent indexes.
4580
4581 QPersistentModelIndex persistentIndex = proxy1.index(row: 0, column: 0, parent: proxy1.index(row: 0, column: 0));
4582
4583 model.setDepth(3);
4584
4585 // Calling parent() causes the internalPointer to be used.
4586 // Before fixing QTBUG-47711, that could be a dangling pointer.
4587 // The use of qDebug here makes sufficient use of the heap to
4588 // cause corruption at runtime with normal use on linux (before
4589 // the fix). valgrind confirms the fix.
4590 qCDebug(lcItemModels) << persistentIndex.parent();
4591 QVERIFY(persistentIndex.parent().isValid());
4592}
4593
4594void tst_QSortFilterProxyModel::rowMoveLeavesValidPersistentIndexes()
4595{
4596 DynamicTreeModel model;
4597 Q_SET_OBJECT_NAME(model);
4598
4599 QList<int> ancestors;
4600 for (auto i = 0; i < 5; ++i)
4601 {
4602 Q_UNUSED(i);
4603 ModelInsertCommand insertCommand(&model);
4604 insertCommand.setAncestorRowNumbers(ancestors);
4605 insertCommand.setStartRow(0);
4606 insertCommand.setEndRow(0);
4607 insertCommand.doCommand();
4608 ancestors.push_back(t: 0);
4609 }
4610
4611 QSortFilterProxyModel proxy1;
4612 proxy1.setSourceModel(&model);
4613 Q_SET_OBJECT_NAME(proxy1);
4614
4615 setupFilter(model: &proxy1, pattern: QLatin1String("1|2"));
4616
4617 auto item5 = model.match(start: model.index(row: 0, column: 0), role: Qt::DisplayRole, value: "5", hits: 1, flags: Qt::MatchRecursive).first();
4618 auto item3 = model.match(start: model.index(row: 0, column: 0), role: Qt::DisplayRole, value: "3", hits: 1, flags: Qt::MatchRecursive).first();
4619
4620 Q_ASSERT(item5.isValid());
4621 Q_ASSERT(item3.isValid());
4622
4623 QPersistentModelIndex persistentIndex = proxy1.match(start: proxy1.index(row: 0, column: 0), role: Qt::DisplayRole, value: "2", hits: 1, flags: Qt::MatchRecursive).first();
4624
4625 ModelMoveCommand moveCommand(&model, nullptr);
4626 moveCommand.setAncestorRowNumbers(QList<int>{0, 0, 0, 0});
4627 moveCommand.setStartRow(0);
4628 moveCommand.setEndRow(0);
4629 moveCommand.setDestRow(0);
4630 moveCommand.setDestAncestors(QList<int>{0, 0, 0});
4631 moveCommand.doCommand();
4632
4633 // Calling parent() causes the internalPointer to be used.
4634 // Before fixing QTBUG-47711 (moveRows case), that could be
4635 // a dangling pointer.
4636 QVERIFY(persistentIndex.parent().isValid());
4637}
4638
4639void tst_QSortFilterProxyModel::emitLayoutChangedOnlyIfSortingChanged_data()
4640{
4641 QTest::addColumn<int>(name: "changedRow");
4642 QTest::addColumn<Qt::ItemDataRole>(name: "changedRole");
4643 QTest::addColumn<QString>(name: "newData");
4644 QTest::addColumn<QString>(name: "expectedSourceRowTexts");
4645 QTest::addColumn<QString>(name: "expectedProxyRowTexts");
4646 QTest::addColumn<int>(name: "expectedLayoutChanged");
4647
4648 // Starting point:
4649 // a source model with 8,7,6,5,4,3,2,1
4650 // a proxy model keeping only even rows and sorting them, therefore showing 2,4,6,8
4651
4652 // When setData changes ordering, layoutChanged should be emitted
4653 QTest::newRow(dataTag: "ordering_change") << 0 << Qt::DisplayRole << "0" << "07654321" << "0246" << 1;
4654
4655 // When setData on visible row doesn't change ordering, layoutChanged should not be emitted
4656 QTest::newRow(dataTag: "no_ordering_change") << 6 << Qt::DisplayRole << "0" << "87654301" << "0468" << 0;
4657
4658 // When setData happens on a filtered out row, layoutChanged should not be emitted
4659 QTest::newRow(dataTag: "filtered_out") << 1 << Qt::DisplayRole << "9" << "89654321" << "2468" << 0;
4660
4661 // When setData makes a row visible, layoutChanged should not be emitted (rowsInserted is emitted instead)
4662 QTest::newRow(dataTag: "make_row_visible") << 7 << Qt::DisplayRole << "0" << "87654320" << "02468" << 0;
4663
4664 // When setData makes a row hidden, layoutChanged should not be emitted (rowsRemoved is emitted instead)
4665 QTest::newRow(dataTag: "make_row_hidden") << 4 << Qt::DisplayRole << "1" << "87651321" << "268" << 0;
4666
4667 // When setData happens on an unrelated role, layoutChanged should not be emitted
4668 QTest::newRow(dataTag: "unrelated_role") << 0 << Qt::DecorationRole << "" << "87654321" << "2468" << 0;
4669
4670 // When many changes happen together... and trigger removal, insertion, and layoutChanged
4671 QTest::newRow(dataTag: "many_changes") << -1 << Qt::DisplayRole << "3,4,2,5,6,0,7,9" << "34256079" << "0246" << 1;
4672
4673 // When many changes happen together... and trigger removal, insertion, but no change in ordering of visible rows => no layoutChanged
4674 QTest::newRow(dataTag: "many_changes_no_layoutChanged") << -1 << Qt::DisplayRole << "7,5,4,3,2,1,0,8" << "75432108" << "0248" << 0;
4675}
4676
4677// Custom version of QStringListModel which supports emitting dataChanged for many rows at once
4678class CustomStringListModel : public QAbstractListModel
4679{
4680public:
4681 bool setData(const QModelIndex &index, const QVariant &value, int role) override
4682 {
4683 if (index.row() >= 0 && index.row() < lst.size()
4684 && (role == Qt::EditRole || role == Qt::DisplayRole)) {
4685 lst.replace(i: index.row(), t: value.toString());
4686 emit dataChanged(topLeft: index, bottomRight: index, roles: { Qt::DisplayRole, Qt::EditRole });
4687 return true;
4688 }
4689 return false;
4690 }
4691 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
4692 {
4693 if (role == Qt::DisplayRole || role == Qt::EditRole)
4694 return lst.at(i: index.row());
4695 return QVariant();
4696 }
4697 int rowCount(const QModelIndex & = QModelIndex()) const override { return lst.count(); }
4698
4699 void replaceData(const QStringList &newData)
4700 {
4701 lst = newData;
4702 emit dataChanged(topLeft: index(row: 0, column: 0), bottomRight: index(row: rowCount() - 1, column: 0), roles: { Qt::DisplayRole, Qt::EditRole });
4703 }
4704
4705 void emitDecorationChangedSignal()
4706 {
4707 const QModelIndex idx = index(row: 0, column: 0);
4708 emit dataChanged(topLeft: idx, bottomRight: idx, roles: { Qt::DecorationRole });
4709 }
4710
4711private:
4712 QStringList lst;
4713};
4714
4715void tst_QSortFilterProxyModel::emitLayoutChangedOnlyIfSortingChanged()
4716{
4717 QFETCH(int, changedRow);
4718 QFETCH(QString, newData);
4719 QFETCH(Qt::ItemDataRole, changedRole);
4720 QFETCH(QString, expectedSourceRowTexts);
4721 QFETCH(QString, expectedProxyRowTexts);
4722 QFETCH(int, expectedLayoutChanged);
4723
4724 CustomStringListModel model;
4725 QStringList strings;
4726 for (auto i = 8; i >= 1; --i)
4727 strings.append(t: QString::number(i));
4728 model.replaceData(newData: strings);
4729 QCOMPARE(rowTexts(&model), QStringLiteral("87654321"));
4730
4731 class FilterEvenRowsProxyModel : public QSortFilterProxyModel
4732 {
4733 public:
4734 bool filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const override
4735 {
4736 return sourceModel()->index(row: srcRow, column: 0, parent: srcParent).data().toInt() % 2 == 0;
4737 }
4738 };
4739
4740 FilterEvenRowsProxyModel proxy;
4741 proxy.sort(column: 0);
4742 proxy.setSourceModel(&model);
4743 QCOMPARE(rowTexts(&proxy), QStringLiteral("2468"));
4744
4745 QSignalSpy modelDataChangedSpy(&model, &QAbstractItemModel::dataChanged);
4746 QSignalSpy proxyLayoutChangedSpy(&proxy, &QAbstractItemModel::layoutChanged);
4747
4748 if (changedRole == Qt::DecorationRole)
4749 model.emitDecorationChangedSignal();
4750 else if (changedRow == -1)
4751 model.replaceData(newData: newData.split(sep: QLatin1Char(',')));
4752 else
4753 model.setData(index: model.index(row: changedRow, column: 0), value: newData, role: changedRole);
4754
4755 QCOMPARE(rowTexts(&model), expectedSourceRowTexts);
4756 QCOMPARE(rowTexts(&proxy), expectedProxyRowTexts);
4757 QCOMPARE(modelDataChangedSpy.size(), 1);
4758 QCOMPARE(proxyLayoutChangedSpy.size(), expectedLayoutChanged);
4759}
4760
4761void tst_QSortFilterProxyModel::removeIntervals_data()
4762{
4763 QTest::addColumn<QStringList>(name: "sourceItems");
4764 QTest::addColumn<Qt::SortOrder>(name: "sortOrder");
4765 QTest::addColumn<QString>(name: "filter");
4766 QTest::addColumn<QStringList>(name: "replacementSourceItems");
4767 QTest::addColumn<IntPairList>(name: "expectedRemovedProxyIntervals");
4768 QTest::addColumn<QStringList>(name: "expectedProxyItems");
4769
4770 QTest::newRow(dataTag: "filter all, sort ascending")
4771 << (QStringList() << "a"
4772 << "b"
4773 << "c") // sourceItems
4774 << Qt::AscendingOrder // sortOrder
4775 << "[^x]" // filter
4776 << (QStringList() << "x"
4777 << "x"
4778 << "x") // replacementSourceItems
4779 << (IntPairList() << IntPair(0, 2)) // expectedRemovedIntervals
4780 << QStringList() // expectedProxyItems
4781 ;
4782
4783 QTest::newRow(dataTag: "filter all, sort descending")
4784 << (QStringList() << "a"
4785 << "b"
4786 << "c") // sourceItems
4787 << Qt::DescendingOrder // sortOrder
4788 << "[^x]" // filter
4789 << (QStringList() << "x"
4790 << "x"
4791 << "x") // replacementSourceItems
4792 << (IntPairList() << IntPair(0, 2)) // expectedRemovedIntervals
4793 << QStringList() // expectedProxyItems
4794 ;
4795
4796 QTest::newRow(dataTag: "filter first and last, sort ascending")
4797 << (QStringList() << "a"
4798 << "b"
4799 << "c") // sourceItems
4800 << Qt::AscendingOrder // sortOrder
4801 << "[^x]" // filter
4802 << (QStringList() << "x"
4803 << "b"
4804 << "x") // replacementSourceItems
4805 << (IntPairList() << IntPair(2, 2) << IntPair(0, 0)) // expectedRemovedIntervals
4806 << (QStringList() << "b") // expectedProxyItems
4807 ;
4808
4809 QTest::newRow(dataTag: "filter first and last, sort descending")
4810 << (QStringList() << "a"
4811 << "b"
4812 << "c") // sourceItems
4813 << Qt::DescendingOrder // sortOrder
4814 << "[^x]" // filter
4815 << (QStringList() << "x"
4816 << "b"
4817 << "x") // replacementSourceItems
4818 << (IntPairList() << IntPair(2, 2) << IntPair(0, 0)) // expectedRemovedIntervals
4819 << (QStringList() << "b") // expectedProxyItems
4820 ;
4821}
4822
4823void tst_QSortFilterProxyModel::removeIntervals()
4824{
4825 QFETCH(QStringList, sourceItems);
4826 QFETCH(Qt::SortOrder, sortOrder);
4827 QFETCH(QString, filter);
4828 QFETCH(QStringList, replacementSourceItems);
4829 QFETCH(IntPairList, expectedRemovedProxyIntervals);
4830 QFETCH(QStringList, expectedProxyItems);
4831
4832 CustomStringListModel model;
4833 QSortFilterProxyModel proxy;
4834
4835 model.replaceData(newData: sourceItems);
4836 proxy.setSourceModel(&model);
4837
4838 for (int i = 0; i < sourceItems.count(); ++i) {
4839 QModelIndex sindex = model.index(row: i, column: 0, parent: QModelIndex());
4840 QModelIndex pindex = proxy.index(row: i, column: 0, parent: QModelIndex());
4841 QCOMPARE(proxy.data(pindex, Qt::DisplayRole), model.data(sindex, Qt::DisplayRole));
4842 }
4843
4844 proxy.setDynamicSortFilter(true);
4845 proxy.sort(column: 0, order: sortOrder);
4846 if (!filter.isEmpty())
4847 setupFilter(model: &proxy, pattern: filter);
4848
4849 (void)proxy.rowCount(parent: QModelIndex()); // force mapping
4850
4851 QSignalSpy removeSpy(&proxy, &QSortFilterProxyModel::rowsRemoved);
4852 QSignalSpy insertSpy(&proxy, &QSortFilterProxyModel::rowsInserted);
4853 QSignalSpy aboutToRemoveSpy(&proxy, &QSortFilterProxyModel::rowsAboutToBeRemoved);
4854 QSignalSpy aboutToInsertSpy(&proxy, &QSortFilterProxyModel::rowsAboutToBeInserted);
4855
4856 QVERIFY(removeSpy.isValid());
4857 QVERIFY(insertSpy.isValid());
4858 QVERIFY(aboutToRemoveSpy.isValid());
4859 QVERIFY(aboutToInsertSpy.isValid());
4860
4861 model.replaceData(newData: replacementSourceItems);
4862
4863 QCOMPARE(aboutToRemoveSpy.count(), expectedRemovedProxyIntervals.count());
4864 for (int i = 0; i < aboutToRemoveSpy.count(); ++i) {
4865 const auto &args = aboutToRemoveSpy.at(i);
4866 QCOMPARE(args.at(1).type(), QVariant::Int);
4867 QCOMPARE(args.at(2).type(), QVariant::Int);
4868 QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first);
4869 QCOMPARE(args.at(2).toInt(), expectedRemovedProxyIntervals.at(i).second);
4870 }
4871 QCOMPARE(removeSpy.count(), expectedRemovedProxyIntervals.count());
4872 for (int i = 0; i < removeSpy.count(); ++i) {
4873 const auto &args = removeSpy.at(i);
4874 QCOMPARE(args.at(1).type(), QVariant::Int);
4875 QCOMPARE(args.at(2).type(), QVariant::Int);
4876 QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first);
4877 QCOMPARE(args.at(2).toInt(), expectedRemovedProxyIntervals.at(i).second);
4878 }
4879
4880 QCOMPARE(insertSpy.count(), 0);
4881 QCOMPARE(aboutToInsertSpy.count(), 0);
4882
4883 QCOMPARE(proxy.rowCount(QModelIndex()), expectedProxyItems.count());
4884 for (int i = 0; i < expectedProxyItems.count(); ++i) {
4885 QModelIndex pindex = proxy.index(row: i, column: 0, parent: QModelIndex());
4886 QCOMPARE(proxy.data(pindex, Qt::DisplayRole).toString(), expectedProxyItems.at(i));
4887 }
4888}
4889
4890void tst_QSortFilterProxyModel::dynamicFilterWithoutSort()
4891{
4892 QStringListModel model;
4893 const QStringList initial = QString("bravo charlie delta echo").split(sep: QLatin1Char(' '));
4894 model.setStringList(initial);
4895 QSortFilterProxyModel proxy;
4896 proxy.setDynamicSortFilter(true);
4897 proxy.setSourceModel(&model);
4898
4899 QSignalSpy layoutChangeSpy(&proxy, &QAbstractItemModel::layoutChanged);
4900 QSignalSpy resetSpy(&proxy, &QAbstractItemModel::modelReset);
4901
4902 QVERIFY(layoutChangeSpy.isValid());
4903 QVERIFY(resetSpy.isValid());
4904
4905 model.setStringList(QStringList() << "Monday" << "Tuesday" << "Wednesday" << "Thursday" << "Friday");
4906
4907 QVERIFY(layoutChangeSpy.isEmpty());
4908
4909 QCOMPARE(model.stringList(), QStringList() << "Monday" << "Tuesday" << "Wednesday" << "Thursday" << "Friday");
4910
4911 QCOMPARE(resetSpy.count(), 1);
4912}
4913
4914void tst_QSortFilterProxyModel::checkSetNewModel()
4915{
4916 QTreeView tv;
4917 StepTreeModel model1;
4918 model1.setDepth(4);
4919
4920 QSortFilterProxyModel proxy;
4921 proxy.setSourceModel(&model1);
4922 tv.setModel(&proxy);
4923 tv.show();
4924 QVERIFY(QTest::qWaitForWindowExposed(&tv));
4925 tv.expandAll();
4926 {
4927 StepTreeModel model2;
4928 model2.setDepth(4);
4929 proxy.setSourceModel(&model2);
4930 tv.expandAll();
4931 proxy.setSourceModel(&model1);
4932 tv.expandAll();
4933 // the destruction of model2 here caused a proxy model reset due to
4934 // missing disconnect in setSourceModel()
4935 }
4936 // handle repaint events, will assert when qsortfilterproxymodel is in wrong state
4937 QCoreApplication::processEvents();
4938}
4939
4940enum ColumnFilterMode {
4941 FilterNothing,
4942 FilterOutMiddle,
4943 FilterOutBeginEnd,
4944 FilterAll
4945};
4946Q_DECLARE_METATYPE(ColumnFilterMode)
4947
4948void tst_QSortFilterProxyModel::filterAndInsertColumn_data()
4949{
4950
4951 QTest::addColumn<int>(name: "insertCol");
4952 QTest::addColumn<ColumnFilterMode>(name: "filterMode");
4953 QTest::addColumn<QStringList>(name: "expectedModelList");
4954 QTest::addColumn<QStringList>(name: "expectedProxyModelList");
4955
4956 QTest::newRow(dataTag: "at_beginning_filter_out_middle")
4957 << 0
4958 << FilterOutMiddle
4959 << QStringList{{"", "A1", "B1", "C1", "D1"}}
4960 << QStringList{{"", "D1"}}
4961 ;
4962 QTest::newRow(dataTag: "at_end_filter_out_middle")
4963 << 2
4964 << FilterOutMiddle
4965 << QStringList{{"A1", "B1", "C1", "D1", ""}}
4966 << QStringList{{"A1", ""}}
4967 ;
4968 QTest::newRow(dataTag: "in_the_middle_filter_out_middle")
4969 << 1
4970 << FilterOutMiddle
4971 << QStringList{{"A1", "B1", "C1", "", "D1"}}
4972 << QStringList{{"A1", "D1"}}
4973 ;
4974 QTest::newRow(dataTag: "at_beginning_filter_out_begin_and_end")
4975 << 0
4976 << FilterOutBeginEnd
4977 << QStringList{{"A1", "", "B1", "C1", "D1"}}
4978 << QStringList{{"", "B1", "C1"}}
4979 ;
4980 QTest::newRow(dataTag: "at_end_filter_out_begin_and_end")
4981 << 2
4982 << FilterOutBeginEnd
4983 << QStringList{{"A1", "B1", "C1", "D1", ""}}
4984 << QStringList{{"B1", "C1", "D1"}}
4985 ;
4986 QTest::newRow(dataTag: "in_the_middle_filter_out_begin_and_end")
4987 << 1
4988 << FilterOutBeginEnd
4989 << QStringList{{"A1", "B1", "", "C1", "D1"}}
4990 << QStringList{{"B1", "", "C1"}}
4991 ;
4992
4993 QTest::newRow(dataTag: "at_beginning_filter_nothing")
4994 << 0
4995 << FilterAll
4996 << QStringList{{"", "A1", "B1", "C1", "D1"}}
4997 << QStringList{{"", "A1", "B1", "C1", "D1"}}
4998 ;
4999 QTest::newRow(dataTag: "at_end_filter_nothing")
5000 << 4
5001 << FilterAll
5002 << QStringList{{"A1", "B1", "C1", "D1", ""}}
5003 << QStringList{{"A1", "B1", "C1", "D1", ""}}
5004 ;
5005 QTest::newRow(dataTag: "in_the_middle_nothing")
5006 << 2
5007 << FilterAll
5008 << QStringList{{"A1", "B1", "", "C1", "D1"}}
5009 << QStringList{{"A1", "B1", "", "C1", "D1"}}
5010 ;
5011
5012 QTest::newRow(dataTag: "filter_all")
5013 << 0
5014 << FilterNothing
5015 << QStringList{{"A1", "B1", "C1", "D1", ""}}
5016 << QStringList{}
5017 ;
5018}
5019void tst_QSortFilterProxyModel::filterAndInsertColumn()
5020{
5021
5022 class ColumnFilterProxy : public QSortFilterProxyModel
5023 {
5024 ColumnFilterMode filerMode;
5025 public:
5026 ColumnFilterProxy(ColumnFilterMode mode)
5027 : filerMode(mode)
5028 {}
5029 bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override
5030 {
5031 Q_UNUSED(source_parent)
5032 switch (filerMode){
5033 case FilterAll:
5034 return true;
5035 case FilterNothing:
5036 return false;
5037 case FilterOutMiddle:
5038 return source_column == 0 || source_column == sourceModel()->columnCount() - 1;
5039 case FilterOutBeginEnd:
5040 return source_column > 0 && source_column< sourceModel()->columnCount() - 1;
5041 }
5042 Q_UNREACHABLE();
5043 }
5044 };
5045 QFETCH(int, insertCol);
5046 QFETCH(ColumnFilterMode, filterMode);
5047 QFETCH(QStringList, expectedModelList);
5048 QFETCH(QStringList, expectedProxyModelList);
5049 QStandardItemModel model;
5050 model.insertColumns(column: 0, count: 4);
5051 model.insertRows(row: 0, count: 1);
5052 for (int i = 0; i < model.rowCount(); ++i) {
5053 for (int j = 0; j < model.columnCount(); ++j)
5054 model.setData(index: model.index(row: i, column: j), value: QString('A' + j) + QString::number(i + 1));
5055 }
5056 ColumnFilterProxy proxy(filterMode);
5057 proxy.setSourceModel(&model);
5058 QVERIFY(proxy.insertColumn(insertCol));
5059 proxy.invalidate();
5060 QStringList modelStringList;
5061 for (int i = 0; i < model.rowCount(); ++i) {
5062 for (int j = 0; j < model.columnCount(); ++j)
5063 modelStringList.append(t: model.index(row: i, column: j).data().toString());
5064 }
5065 QCOMPARE(expectedModelList, modelStringList);
5066 modelStringList.clear();
5067 for (int i = 0; i < proxy.rowCount(); ++i) {
5068 for (int j = 0; j < proxy.columnCount(); ++j)
5069 modelStringList.append(t: proxy.index(row: i, column: j).data().toString());
5070 }
5071 QCOMPARE(expectedProxyModelList, modelStringList);
5072}
5073
5074void tst_QSortFilterProxyModel::filterAndInsertRow_data()
5075{
5076 QTest::addColumn<QStringList>(name: "initialModelList");
5077 QTest::addColumn<int>(name: "row");
5078 QTest::addColumn<QString>(name: "filterRegExp");
5079 QTest::addColumn<QStringList>(name: "expectedModelList");
5080 QTest::addColumn<QStringList>(name: "expectedProxyModelList");
5081
5082 QTest::newRow(dataTag: "at_beginning_filter_out_middle")
5083 << QStringList{{"A5", "B5", "B6", "A7"}}
5084 << 0
5085 << "^A"
5086 << QStringList{{"", "A5", "B5", "B6", "A7"}}
5087 << QStringList{{"A5", "A7"}};
5088 QTest::newRow(dataTag: "at_end_filter_out_middle")
5089 << QStringList{{"A5", "B5", "B6", "A7"}}
5090 << 2
5091 << "^A"
5092 << QStringList{{"A5", "B5", "B6", "A7", ""}}
5093 << QStringList{{"A5", "A7"}};
5094 QTest::newRow(dataTag: "in_the_middle_filter_out_middle")
5095 << QStringList{{"A5", "B5", "B6", "A7"}}
5096 << 1
5097 << "^A"
5098 << QStringList{{"A5", "B5", "B6", "", "A7"}}
5099 << QStringList{{"A5", "A7"}};
5100
5101 QTest::newRow(dataTag: "at_beginning_filter_out_first_and_last")
5102 << QStringList{{"A5", "B5", "B6", "A7"}}
5103 << 0
5104 << "^B"
5105 << QStringList{{"A5", "", "B5", "B6", "A7"}}
5106 << QStringList{{"B5", "B6"}};
5107 QTest::newRow(dataTag: "at_end_filter_out_first_and_last")
5108 << QStringList{{"A5", "B5", "B6", "A7"}}
5109 << 2
5110 << "^B"
5111 << QStringList{{"A5", "B5", "B6", "A7", ""}}
5112 << QStringList{{"B5", "B6"}};
5113 QTest::newRow(dataTag: "in_the_middle_filter_out_first_and_last")
5114 << QStringList{{"A5", "B5", "B6", "A7"}}
5115 << 1
5116 << "^B"
5117 << QStringList{{"A5", "B5", "", "B6", "A7"}}
5118 << QStringList{{"B5", "B6"}};
5119
5120 QTest::newRow(dataTag: "at_beginning_no_filter")
5121 << QStringList{{"A5", "B5", "B6", "A7"}}
5122 << 0
5123 << ".*"
5124 << QStringList{{"", "A5", "B5", "B6", "A7"}}
5125 << QStringList{{"", "A5", "B5", "B6", "A7"}};
5126 QTest::newRow(dataTag: "at_end_no_filter")
5127 << QStringList{{"A5", "B5", "B6", "A7"}}
5128 << 4
5129 << ".*"
5130 << QStringList{{"A5", "B5", "B6", "A7", ""}}
5131 << QStringList{{"A5", "B5", "B6", "A7", ""}};
5132 QTest::newRow(dataTag: "in_the_middle_no_filter")
5133 << QStringList{{"A5", "B5", "B6", "A7"}}
5134 << 2
5135 << ".*"
5136 << QStringList{{"A5", "B5", "", "B6", "A7"}}
5137 << QStringList{{"A5", "B5", "", "B6", "A7"}};
5138
5139 QTest::newRow(dataTag: "filter_all")
5140 << QStringList{{"A5", "B5", "B6", "A7"}}
5141 << 0
5142 << "$a"
5143 << QStringList{{"A5", "B5", "B6", "A7", ""}}
5144 << QStringList{};
5145}
5146
5147void tst_QSortFilterProxyModel::filterAndInsertRow()
5148{
5149 QFETCH(QStringList, initialModelList);
5150 QFETCH(int, row);
5151 QFETCH(QString, filterRegExp);
5152 QFETCH(QStringList, expectedModelList);
5153 QFETCH(QStringList, expectedProxyModelList);
5154 QStringListModel model;
5155 QSortFilterProxyModel proxyModel;
5156
5157 model.setStringList(initialModelList);
5158 proxyModel.setSourceModel(&model);
5159 proxyModel.setDynamicSortFilter(true);
5160 proxyModel.setFilterRegExp(filterRegExp);
5161
5162 QVERIFY(proxyModel.insertRow(row));
5163 QCOMPARE(model.stringList(), expectedModelList);
5164 QCOMPARE(proxyModel.rowCount(), expectedProxyModelList.count());
5165 for (int r = 0; r < proxyModel.rowCount(); ++r) {
5166 QModelIndex index = proxyModel.index(row: r, column: 0);
5167 QVERIFY(index.isValid());
5168 QCOMPARE(proxyModel.data(index).toString(), expectedProxyModelList.at(r));
5169 }
5170}
5171
5172#include "tst_qsortfilterproxymodel.moc"
5173

source code of qtbase/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp