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 "../../../../shared/fakedirmodel.h"
30
31#include <QDesktopWidget>
32#include <QHeaderView>
33#include <QLabel>
34#include <QLineEdit>
35#include <QMainWindow>
36#include <QProxyStyle>
37#include <QPushButton>
38#include <QScrollBar>
39#include <QSignalSpy>
40#include <QSortFilterProxyModel>
41#include <QStatusBar>
42#include <QStringListModel>
43#include <QStyledItemDelegate>
44#include <QTextEdit>
45#include <QTimer>
46#include <QToolButton>
47#include <QTreeWidget>
48#include <QTest>
49#include <QVBoxLayout>
50#include <private/qtreeview_p.h>
51#include <private/qtesthelpers_p.h>
52
53using namespace QTestPrivate;
54
55#if QT_CONFIG(draganddrop)
56Q_DECLARE_METATYPE(QAbstractItemView::DragDropMode)
57#endif
58Q_DECLARE_METATYPE(QAbstractItemView::EditTriggers)
59Q_DECLARE_METATYPE(QAbstractItemView::EditTrigger)
60
61using IntBounds = std::numeric_limits<int>;
62static void initStandardTreeModel(QStandardItemModel *model)
63{
64 QStandardItem *item;
65 item = new QStandardItem(QLatin1String("Row 1 Item"));
66 model->insertRow(arow: 0, aitem: item);
67
68 item = new QStandardItem(QLatin1String("Row 2 Item"));
69 item->setCheckable(true);
70 model->insertRow(arow: 1, aitem: item);
71
72 QStandardItem *childItem = new QStandardItem(QLatin1String("Row 2 Child Item"));
73 item->setChild(arow: 0, aitem: childItem);
74
75 item = new QStandardItem(QLatin1String("Row 3 Item"));
76 item->setIcon(QIcon());
77 model->insertRow(arow: 2, aitem: item);
78}
79
80class TreeView : public QTreeView
81{
82 Q_OBJECT
83public:
84 using QTreeView::QTreeView;
85 using QTreeView::selectedIndexes;
86
87 void paintEvent(QPaintEvent *event) override
88 {
89 QTreeView::paintEvent(event);
90 wasPainted = true;
91 }
92 bool wasPainted = false;
93public slots:
94 void handleSelectionChanged()
95 {
96 //let's select the last item
97 QModelIndex idx = model()->index(row: 0, column: 0);
98 selectionModel()->select(selection: QItemSelection(idx, idx), command: QItemSelectionModel::Select);
99 disconnect(sender: selectionModel(), signal: &QItemSelectionModel::selectionChanged,
100 receiver: this, slot: &TreeView::handleSelectionChanged);
101 }
102};
103
104class tst_QTreeView : public QObject
105{
106 Q_OBJECT
107
108public slots:
109 void selectionOrderTest();
110
111private slots:
112 void initTestCase() { QApplication::setKeyboardInputInterval(100); }
113 void getSetCheck();
114
115 // one test per QTreeView property
116 void construction();
117 void alternatingRowColors();
118 void currentIndex_data();
119 void currentIndex();
120#if QT_CONFIG(draganddrop)
121 void dragDropMode_data();
122 void dragDropMode();
123 void dragDropModeFromDragEnabledAndAcceptDrops_data();
124 void dragDropModeFromDragEnabledAndAcceptDrops();
125 void dragDropOverwriteMode();
126#endif
127 void editTriggers_data();
128 void editTriggers();
129 void hasAutoScroll();
130 void horizontalScrollMode();
131 void iconSize();
132 void indexAt();
133 void indexWidget();
134 void itemDelegate();
135 void itemDelegateForColumnOrRow();
136 void keyboardSearch();
137 void keyboardSearchMultiColumn();
138 void setModel();
139 void openPersistentEditor();
140 void rootIndex();
141
142 // specialized tests below
143 void setHeader();
144 void columnHidden();
145 void rowHidden();
146 void noDelegate();
147 void noModel();
148 void emptyModel();
149 void removeRows();
150 void removeCols();
151 void limitedExpand();
152 void expandAndCollapse_data();
153 void expandAndCollapse();
154 void expandAndCollapseAll();
155 void expandWithNoChildren();
156#if QT_CONFIG(animation)
157 void quickExpandCollapse();
158#endif
159 void keyboardNavigation();
160 void headerSections();
161 void moveCursor_data();
162 void moveCursor();
163 void setSelection_data();
164 void setSelection();
165 void extendedSelection_data();
166 void extendedSelection();
167 void indexAbove();
168 void indexBelow();
169 void clicked();
170 void mouseDoubleClick();
171 void rowsAboutToBeRemoved();
172 void headerSections_unhideSection();
173 void columnAt();
174 void scrollTo();
175 void rowsAboutToBeRemoved_move();
176 void resizeColumnToContents();
177 void insertAfterSelect();
178 void removeAfterSelect();
179 void hiddenItems();
180 void spanningItems();
181 void rowSizeHint();
182 void setSortingEnabledTopLevel();
183 void setSortingEnabledChild();
184 void headerHidden();
185 void indentation();
186
187 void selection();
188 void removeAndInsertExpandedCol0();
189 void selectionWithHiddenItems();
190 void selectAll();
191
192 void disabledButCheckable();
193 void sortByColumn_data();
194 void sortByColumn();
195
196 void evilModel_data();
197 void evilModel();
198
199 void indexRowSizeHint();
200 void addRowsWhileSectionsAreHidden();
201 void filterProxyModelCrash();
202 void renderToPixmap_data();
203 void renderToPixmap();
204 void styleOptionViewItem();
205 void keyboardNavigationWithDisabled();
206 void saveRestoreState();
207
208 void statusTip_data();
209 void statusTip();
210 void fetchMoreOnScroll();
211
212 // task-specific tests:
213 void task174627_moveLeftToRoot();
214 void task171902_expandWith1stColHidden();
215 void task203696_hidingColumnsAndRowsn();
216 void task211293_removeRootIndex();
217 void task216717_updateChildren();
218 void task220298_selectColumns();
219 void task224091_appendColumns();
220 void task225539_deleteModel();
221 void task230123_setItemsExpandable();
222 void task202039_closePersistentEditor();
223 void task238873_avoidAutoReopening();
224 void task244304_clickOnDecoration();
225 void task246536_scrollbarsNotWorking();
226 void task250683_wrongSectionSize();
227 void task239271_addRowsWithFirstColumnHidden();
228 void task254234_proxySort();
229 void task248022_changeSelection();
230 void task245654_changeModelAndExpandAll();
231 void doubleClickedWithSpans();
232 void taskQTBUG_6450_selectAllWith1stColumnHidden();
233 void taskQTBUG_9216_setSizeAndUniformRowHeightsWrongRepaint();
234 void taskQTBUG_11466_keyboardNavigationRegression();
235 void taskQTBUG_13567_removeLastItemRegression();
236 void taskQTBUG_25333_adjustViewOptionsForIndex();
237 void taskQTBUG_18539_emitLayoutChanged();
238 void taskQTBUG_8176_emitOnExpandAll();
239 void taskQTBUG_37813_crash();
240 void taskQTBUG_45697_crash();
241 void taskQTBUG_7232_AllowUserToControlSingleStep();
242 void taskQTBUG_8376();
243 void taskQTBUG_61476();
244 void taskQTBUG_42469_crash();
245 void testInitialFocus();
246 void fetchUntilScreenFull();
247};
248
249class QtTestModel: public QAbstractItemModel
250{
251 Q_OBJECT
252public:
253 QtTestModel(int _rows, int _cols, QObject *parent = nullptr)
254 : QAbstractItemModel(parent), rows(_rows), cols(_cols)
255 {}
256
257 inline qint32 level(const QModelIndex &index) const
258 {
259 return index.isValid() ? qint32(index.internalId()) : qint32(-1);
260 }
261
262 bool canFetchMore(const QModelIndex &) const override { return !fetched; }
263
264 void fetchMore(const QModelIndex &) override { fetched = true; }
265
266 bool hasChildren(const QModelIndex &parent = QModelIndex()) const override
267 {
268 bool hasFetched = fetched;
269 fetched = true;
270 bool r = QAbstractItemModel::hasChildren(parent);
271 fetched = hasFetched;
272 return r;
273 }
274
275 int rowCount(const QModelIndex& parent = QModelIndex()) const override
276 {
277 if (!fetched)
278 qFatal(msg: "%s: rowCount should not be called before fetching", Q_FUNC_INFO);
279 if ((parent.column() > 0) || (level(index: parent) > levels))
280 return 0;
281 return rows;
282 }
283 int columnCount(const QModelIndex& parent = QModelIndex()) const override
284 {
285 if ((parent.column() > 0) || (level(index: parent) > levels))
286 return 0;
287 return cols;
288 }
289
290 bool isEditable(const QModelIndex &index) const
291 {
292 if (index.isValid())
293 return true;
294 return false;
295 }
296
297 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
298 {
299 if (onlyValidCalls) {
300 Q_ASSERT(row >= 0);
301 Q_ASSERT(column >= 0);
302 Q_ASSERT(row < rows);
303 Q_ASSERT(column < cols);
304 }
305 if (row < 0 || column < 0 || (level(index: parent) > levels) || column >= cols || row >= rows) {
306 return QModelIndex();
307 }
308 QModelIndex i = createIndex(arow: row, acolumn: column, aid: quintptr(level(index: parent) + 1));
309 parentHash[i] = parent;
310 return i;
311 }
312
313 QModelIndex parent(const QModelIndex &index) const override
314 {
315 if (!parentHash.contains(key: index))
316 return QModelIndex();
317 return parentHash[index];
318 }
319
320 QVariant data(const QModelIndex &idx, int role) const override
321 {
322 if (!idx.isValid())
323 return QVariant();
324
325 if (role == Qt::DisplayRole) {
326 if (idx.row() < 0 || idx.column() < 0 || idx.column() >= cols || idx.row() >= rows) {
327 wrongIndex = true;
328 qWarning(msg: "Invalid modelIndex [%d,%d,%p]", idx.row(), idx.column(),
329 idx.internalPointer());
330 }
331 QString result = QLatin1Char('[') + QString::number(idx.row()) + QLatin1Char(',')
332 + QString::number(idx.column()) + QLatin1Char(',') + QString::number(level(index: idx))
333 + QLatin1Char(']');
334 if (idx.row() & 1)
335 result += QLatin1String(" - this item is extra wide");
336 return result;
337 }
338 if (decorationsEnabled && role == Qt::DecorationRole) {
339 QPixmap pm(16,16);
340 pm.fill(fillColor: QColor::fromHsv(h: (idx.column() % 16)*8 + 64, s: 254, v: (idx.row() % 16)*8 + 32));
341 return pm;
342 }
343 if (statusTipsEnabled && role == Qt::StatusTipRole)
344 return QString("[%1,%2,%3] -- Status").arg(a: idx.row()).arg(a: idx.column()).arg(a: level(index: idx));
345 return QVariant();
346 }
347
348 QVariant headerData(int section, Qt::Orientation orientation,
349 int role = Qt::DisplayRole) const override
350 {
351 Q_UNUSED(orientation);
352 if (section < 0 || section >= columnCount())
353 return QVariant();
354 if (statusTipsEnabled && role == Qt::StatusTipRole)
355 return QString("Header %1 -- Status").arg(a: section);
356 return QVariant();
357 }
358
359 void simulateMoveRows()
360 {
361 beginMoveRows(sourceParent: QModelIndex(), sourceFirst: 0, sourceLast: 0, destinationParent: QModelIndex(), destinationRow: 2);
362 endMoveRows();
363 }
364
365 void removeLastRow()
366 {
367 beginRemoveRows(parent: QModelIndex(), first: rows - 1, last: rows - 1);
368 --rows;
369 endRemoveRows();
370 }
371
372 void removeAllRows()
373 {
374 beginRemoveRows(parent: QModelIndex(), first: 0, last: rows - 1);
375 rows = 0;
376 endRemoveRows();
377 }
378
379 void removeLastColumn()
380 {
381 beginRemoveColumns(parent: QModelIndex(), first: cols - 1, last: cols - 1);
382 --cols;
383 endRemoveColumns();
384 }
385
386 void removeAllColumns()
387 {
388 beginRemoveColumns(parent: QModelIndex(), first: 0, last: cols - 1);
389 cols = 0;
390 endRemoveColumns();
391 }
392
393 void insertNewRow()
394 {
395 beginInsertRows(parent: QModelIndex(), first: rows - 1, last: rows - 1);
396 ++rows;
397 endInsertRows();
398 }
399
400 void setDecorationsEnabled(bool enable)
401 {
402 decorationsEnabled = enable;
403 }
404
405 mutable QMap<QModelIndex,QModelIndex> parentHash;
406 int rows = 0;
407 int cols = 0;
408 int levels = IntBounds::max();
409 mutable bool wrongIndex = false;
410 mutable bool fetched = false;
411 bool decorationsEnabled = false;
412 bool statusTipsEnabled = false;
413 bool onlyValidCalls = false;
414};
415
416// Testing get/set functions
417void tst_QTreeView::getSetCheck()
418{
419 QTreeView obj1;
420
421 // int QTreeView::indentation()
422 // void QTreeView::setIndentation(int)
423 const int styledIndentation = obj1.style()->pixelMetric(
424 metric: QStyle::PM_TreeViewIndentation, option: nullptr, widget: &obj1);
425 QCOMPARE(obj1.indentation(), styledIndentation);
426 obj1.setIndentation(0);
427 QCOMPARE(obj1.indentation(), 0);
428 obj1.setIndentation(IntBounds::min());
429 QCOMPARE(obj1.indentation(), IntBounds::min());
430 obj1.setIndentation(IntBounds::max());
431 QCOMPARE(obj1.indentation(), IntBounds::max());
432
433 // bool QTreeView::rootIsDecorated()
434 // void QTreeView::setRootIsDecorated(bool)
435 QCOMPARE(obj1.rootIsDecorated(), true);
436 obj1.setRootIsDecorated(false);
437 QCOMPARE(obj1.rootIsDecorated(), false);
438 obj1.setRootIsDecorated(true);
439 QCOMPARE(obj1.rootIsDecorated(), true);
440
441 // bool QTreeView::uniformRowHeights()
442 // void QTreeView::setUniformRowHeights(bool)
443 QCOMPARE(obj1.uniformRowHeights(), false);
444 obj1.setUniformRowHeights(false);
445 QCOMPARE(obj1.uniformRowHeights(), false);
446 obj1.setUniformRowHeights(true);
447 QCOMPARE(obj1.uniformRowHeights(), true);
448
449 // bool QTreeView::itemsExpandable()
450 // void QTreeView::setItemsExpandable(bool)
451 QCOMPARE(obj1.itemsExpandable(), true);
452 obj1.setItemsExpandable(false);
453 QCOMPARE(obj1.itemsExpandable(), false);
454 obj1.setItemsExpandable(true);
455 QCOMPARE(obj1.itemsExpandable(), true);
456
457 // bool QTreeView::allColumnsShowFocus
458 // void QTreeView::setAllColumnsShowFocus
459 QCOMPARE(obj1.allColumnsShowFocus(), false);
460 obj1.setAllColumnsShowFocus(false);
461 QCOMPARE(obj1.allColumnsShowFocus(), false);
462 obj1.setAllColumnsShowFocus(true);
463 QCOMPARE(obj1.allColumnsShowFocus(), true);
464
465 // bool QTreeView::isAnimated
466 // void QTreeView::setAnimated
467 QCOMPARE(obj1.isAnimated(), false);
468 obj1.setAnimated(false);
469 QCOMPARE(obj1.isAnimated(), false);
470 obj1.setAnimated(true);
471 QCOMPARE(obj1.isAnimated(), true);
472
473 // bool QTreeView::setSortingEnabled
474 // void QTreeView::isSortingEnabled
475 QCOMPARE(obj1.isSortingEnabled(), false);
476 obj1.setSortingEnabled(false);
477 QCOMPARE(obj1.isSortingEnabled(), false);
478 obj1.setSortingEnabled(true);
479 QCOMPARE(obj1.isSortingEnabled(), true);
480}
481
482void tst_QTreeView::construction()
483{
484 QTreeView view;
485
486 // QAbstractItemView properties
487 QVERIFY(!view.alternatingRowColors());
488 QCOMPARE(view.currentIndex(), QModelIndex());
489#if QT_CONFIG(draganddrop)
490 QCOMPARE(view.dragDropMode(), QAbstractItemView::NoDragDrop);
491 QVERIFY(!view.dragDropOverwriteMode());
492 QVERIFY(!view.dragEnabled());
493#endif
494 QCOMPARE(view.editTriggers(), QAbstractItemView::EditKeyPressed | QAbstractItemView::DoubleClicked);
495 QVERIFY(view.hasAutoScroll());
496 QCOMPARE(view.horizontalScrollMode(), QAbstractItemView::ScrollPerPixel);
497 QCOMPARE(view.iconSize(), QSize());
498 QCOMPARE(view.indexAt(QPoint()), QModelIndex());
499 QVERIFY(!view.indexWidget(QModelIndex()));
500 QVERIFY(qobject_cast<QStyledItemDelegate *>(view.itemDelegate()));
501 QVERIFY(!view.itemDelegateForColumn(-1));
502 QVERIFY(!view.itemDelegateForColumn(0));
503 QVERIFY(!view.itemDelegateForColumn(1));
504 QVERIFY(!view.itemDelegateForRow(-1));
505 QVERIFY(!view.itemDelegateForRow(0));
506 QVERIFY(!view.itemDelegateForRow(1));
507 QVERIFY(!view.model());
508 QCOMPARE(view.rootIndex(), QModelIndex());
509 QCOMPARE(view.selectionBehavior(), QAbstractItemView::SelectRows);
510 QCOMPARE(view.selectionMode(), QAbstractItemView::SingleSelection);
511 QVERIFY(!view.selectionModel());
512#if QT_CONFIG(draganddrop)
513 QVERIFY(view.showDropIndicator());
514#endif
515 QCOMPARE(view.QAbstractItemView::sizeHintForColumn(-1), -1); // <- protected in QTreeView
516 QCOMPARE(view.QAbstractItemView::sizeHintForColumn(0), -1); // <- protected in QTreeView
517 QCOMPARE(view.QAbstractItemView::sizeHintForColumn(1), -1); // <- protected in QTreeView
518 QCOMPARE(view.sizeHintForIndex(QModelIndex()), QSize());
519 QCOMPARE(view.sizeHintForRow(-1), -1);
520 QCOMPARE(view.sizeHintForRow(0), -1);
521 QCOMPARE(view.sizeHintForRow(1), -1);
522 QVERIFY(!view.tabKeyNavigation());
523 QCOMPARE(view.textElideMode(), Qt::ElideRight);
524 QCOMPARE(static_cast<int>(view.verticalScrollMode()),
525 view.style()->styleHint(QStyle::SH_ItemView_ScrollMode, nullptr, &view));
526 QCOMPARE(view.visualRect(QModelIndex()), QRect());
527
528 // QTreeView properties
529 QVERIFY(!view.allColumnsShowFocus());
530 QCOMPARE(view.autoExpandDelay(), -1);
531 QCOMPARE(view.columnAt(-1), -1);
532 QCOMPARE(view.columnAt(0), -1);
533 QCOMPARE(view.columnAt(1), -1);
534 QCOMPARE(view.columnViewportPosition(-1), -1);
535 QCOMPARE(view.columnViewportPosition(0), -1);
536 QCOMPARE(view.columnViewportPosition(1), -1);
537 QCOMPARE(view.columnWidth(-1), 0);
538 QCOMPARE(view.columnWidth(0), 0);
539 QCOMPARE(view.columnWidth(1), 0);
540 QVERIFY(view.header());
541 QCOMPARE(view.indentation(),
542 view.style()->pixelMetric(QStyle::PM_TreeViewIndentation, nullptr, &view));
543 QCOMPARE(view.indexAbove(QModelIndex()), QModelIndex());
544 QCOMPARE(view.indexBelow(QModelIndex()), QModelIndex());
545 QVERIFY(!view.isAnimated());
546 QVERIFY(!view.isColumnHidden(-1));
547 QVERIFY(!view.isColumnHidden(0));
548 QVERIFY(!view.isColumnHidden(1));
549 QVERIFY(!view.isExpanded(QModelIndex()));
550 QVERIFY(!view.isRowHidden(-1, QModelIndex()));
551 QVERIFY(!view.isRowHidden(0, QModelIndex()));
552 QVERIFY(!view.isRowHidden(1, QModelIndex()));
553 QVERIFY(!view.isFirstColumnSpanned(-1, QModelIndex()));
554 QVERIFY(!view.isFirstColumnSpanned(0, QModelIndex()));
555 QVERIFY(!view.isFirstColumnSpanned(1, QModelIndex()));
556 QVERIFY(!view.isSortingEnabled());
557 QVERIFY(view.itemsExpandable());
558 QVERIFY(view.rootIsDecorated());
559 QVERIFY(!view.uniformRowHeights());
560 QCOMPARE(view.visualRect(QModelIndex()), QRect());
561 QVERIFY(!view.wordWrap());
562}
563
564void tst_QTreeView::alternatingRowColors()
565{
566 QTreeView view;
567 QVERIFY(!view.alternatingRowColors());
568 view.setAlternatingRowColors(true);
569 QVERIFY(view.alternatingRowColors());
570 view.setAlternatingRowColors(false);
571 QVERIFY(!view.alternatingRowColors());
572
573 // ### Test visual effect.
574}
575
576void tst_QTreeView::currentIndex_data()
577{
578 QTest::addColumn<int>(name: "row");
579 QTest::addColumn<int>(name: "column");
580 QTest::addColumn<int>(name: "indexRow");
581 QTest::addColumn<int>(name: "indexColumn");
582 QTest::addColumn<int>(name: "parentIndexRow");
583 QTest::addColumn<int>(name: "parentIndexColumn");
584
585 QTest::newRow(dataTag: "-1, -1") << -1 << -1 << -1 << -1 << -1 << -1;
586 QTest::newRow(dataTag: "-1, 0") << -1 << 0 << -1 << -1 << -1 << -1;
587 QTest::newRow(dataTag: "0, -1") << 0 << -1 << -1 << -1 << -1 << -1;
588 QTest::newRow(dataTag: "0, 0") << 0 << 0 << 0 << 0 << -1 << -1;
589 QTest::newRow(dataTag: "0, 1") << 0 << 0 << 0 << 0 << -1 << -1;
590 QTest::newRow(dataTag: "1, 0") << 1 << 0 << 1 << 0 << -1 << -1;
591 QTest::newRow(dataTag: "1, 1") << 1 << 1 << -1 << -1 << -1 << -1;
592 QTest::newRow(dataTag: "2, 0") << 2 << 0 << 2 << 0 << -1 << -1;
593 QTest::newRow(dataTag: "2, 1") << 2 << 1 << -1 << -1 << -1 << -1;
594 QTest::newRow(dataTag: "3, -1") << 3 << -1 << -1 << -1 << -1 << -1;
595 QTest::newRow(dataTag: "3, 0") << 3 << 0 << -1 << -1 << -1 << -1;
596 QTest::newRow(dataTag: "3, 1") << 3 << 1 << -1 << -1 << -1 << -1;
597}
598
599void tst_QTreeView::currentIndex()
600{
601 QFETCH(int, row);
602 QFETCH(int, column);
603 QFETCH(int, indexRow);
604 QFETCH(int, indexColumn);
605 QFETCH(int, parentIndexRow);
606 QFETCH(int, parentIndexColumn);
607
608 QTreeView view;
609 QStandardItemModel treeModel;
610 initStandardTreeModel(model: &treeModel);
611 view.setModel(&treeModel);
612
613 QCOMPARE(view.currentIndex(), QModelIndex());
614 view.setCurrentIndex(view.model()->index(row, column));
615 QCOMPARE(view.currentIndex().row(), indexRow);
616 QCOMPARE(view.currentIndex().column(), indexColumn);
617 QCOMPARE(view.currentIndex().parent().row(), parentIndexRow);
618 QCOMPARE(view.currentIndex().parent().column(), parentIndexColumn);
619
620 // ### Test child and grandChild indexes.
621}
622
623#if QT_CONFIG(draganddrop)
624
625void tst_QTreeView::dragDropMode_data()
626{
627 QTest::addColumn<QAbstractItemView::DragDropMode>(name: "dragDropMode");
628 QTest::addColumn<bool>(name: "acceptDrops");
629 QTest::addColumn<bool>(name: "dragEnabled");
630 QTest::newRow(dataTag: "NoDragDrop") << QAbstractItemView::NoDragDrop << false << false;
631 QTest::newRow(dataTag: "DragOnly") << QAbstractItemView::DragOnly << false << true;
632 QTest::newRow(dataTag: "DropOnly") << QAbstractItemView::DropOnly << true << false;
633 QTest::newRow(dataTag: "DragDrop") << QAbstractItemView::DragDrop << true << true;
634 QTest::newRow(dataTag: "InternalMove") << QAbstractItemView::InternalMove << true << true;
635}
636
637void tst_QTreeView::dragDropMode()
638{
639 QFETCH(QAbstractItemView::DragDropMode, dragDropMode);
640 QFETCH(bool, acceptDrops);
641 QFETCH(bool, dragEnabled);
642
643 QTreeView view;
644 QCOMPARE(view.dragDropMode(), QAbstractItemView::NoDragDrop);
645 QVERIFY(!view.acceptDrops());
646 QVERIFY(!view.dragEnabled());
647
648 view.setDragDropMode(dragDropMode);
649 QCOMPARE(view.dragDropMode(), dragDropMode);
650 QCOMPARE(view.acceptDrops(), acceptDrops);
651 QCOMPARE(view.dragEnabled(), dragEnabled);
652
653 // ### Test effects of this mode
654}
655
656void tst_QTreeView::dragDropModeFromDragEnabledAndAcceptDrops_data()
657{
658 QTest::addColumn<bool>(name: "dragEnabled");
659 QTest::addColumn<bool>(name: "acceptDrops");
660 QTest::addColumn<QAbstractItemView::DragDropMode>(name: "dragDropMode");
661 QTest::addColumn<bool>(name: "setBehavior");
662 QTest::addColumn<QAbstractItemView::DragDropMode>(name: "behavior");
663
664 QTest::newRow(dataTag: "NoDragDrop -1") << false << false << QAbstractItemView::NoDragDrop << false << QAbstractItemView::DragDropMode();
665 QTest::newRow(dataTag: "NoDragDrop 0") << false << false << QAbstractItemView::NoDragDrop << true << QAbstractItemView::NoDragDrop;
666 QTest::newRow(dataTag: "NoDragDrop 1") << false << false << QAbstractItemView::NoDragDrop << true << QAbstractItemView::DragOnly;
667 QTest::newRow(dataTag: "NoDragDrop 2") << false << false << QAbstractItemView::NoDragDrop << true << QAbstractItemView::DropOnly;
668 QTest::newRow(dataTag: "NoDragDrop 3") << false << false << QAbstractItemView::NoDragDrop << true << QAbstractItemView::DragDrop;
669 QTest::newRow(dataTag: "NoDragDrop 4") << false << false << QAbstractItemView::NoDragDrop << true << QAbstractItemView::InternalMove;
670 QTest::newRow(dataTag: "DragOnly -1") << true << false << QAbstractItemView::DragOnly << false << QAbstractItemView::DragDropMode();
671 QTest::newRow(dataTag: "DragOnly 0") << true << false << QAbstractItemView::DragOnly << true << QAbstractItemView::NoDragDrop;
672 QTest::newRow(dataTag: "DragOnly 1") << true << false << QAbstractItemView::DragOnly << true << QAbstractItemView::DragOnly;
673 QTest::newRow(dataTag: "DragOnly 2") << true << false << QAbstractItemView::DragOnly << true << QAbstractItemView::DropOnly;
674 QTest::newRow(dataTag: "DragOnly 3") << true << false << QAbstractItemView::DragOnly << true << QAbstractItemView::DragDrop;
675 QTest::newRow(dataTag: "DragOnly 4") << true << false << QAbstractItemView::DragOnly << true << QAbstractItemView::InternalMove;
676 QTest::newRow(dataTag: "DropOnly -1") << false << true << QAbstractItemView::DropOnly << false << QAbstractItemView::DragDropMode();
677 QTest::newRow(dataTag: "DropOnly 0") << false << true << QAbstractItemView::DropOnly << true << QAbstractItemView::NoDragDrop;
678 QTest::newRow(dataTag: "DropOnly 1") << false << true << QAbstractItemView::DropOnly << true << QAbstractItemView::DragOnly;
679 QTest::newRow(dataTag: "DropOnly 2") << false << true << QAbstractItemView::DropOnly << true << QAbstractItemView::DropOnly;
680 QTest::newRow(dataTag: "DropOnly 3") << false << true << QAbstractItemView::DropOnly << true << QAbstractItemView::DragDrop;
681 QTest::newRow(dataTag: "DropOnly 4") << false << true << QAbstractItemView::DropOnly << true << QAbstractItemView::InternalMove;
682 QTest::newRow(dataTag: "DragDrop -1") << true << true << QAbstractItemView::DragDrop << false << QAbstractItemView::DragDropMode();
683 QTest::newRow(dataTag: "DragDrop 0") << true << true << QAbstractItemView::DragDrop << false << QAbstractItemView::DragDropMode();
684 QTest::newRow(dataTag: "DragDrop 1") << true << true << QAbstractItemView::DragDrop << true << QAbstractItemView::NoDragDrop;
685 QTest::newRow(dataTag: "DragDrop 2") << true << true << QAbstractItemView::DragDrop << true << QAbstractItemView::DragOnly;
686 QTest::newRow(dataTag: "DragDrop 3") << true << true << QAbstractItemView::DragDrop << true << QAbstractItemView::DropOnly;
687 QTest::newRow(dataTag: "DragDrop 4") << true << true << QAbstractItemView::DragDrop << true << QAbstractItemView::DragDrop;
688 QTest::newRow(dataTag: "DragDrop 5") << true << true << QAbstractItemView::InternalMove << true << QAbstractItemView::InternalMove;
689}
690
691void tst_QTreeView::dragDropModeFromDragEnabledAndAcceptDrops()
692{
693 QFETCH(bool, acceptDrops);
694 QFETCH(bool, dragEnabled);
695 QFETCH(QAbstractItemView::DragDropMode, dragDropMode);
696 QFETCH(bool, setBehavior);
697 QFETCH(QAbstractItemView::DragDropMode, behavior);
698
699 QTreeView view;
700 QCOMPARE(view.dragDropMode(), QAbstractItemView::NoDragDrop);
701
702 if (setBehavior)
703 view.setDragDropMode(behavior);
704
705 view.setAcceptDrops(acceptDrops);
706 view.setDragEnabled(dragEnabled);
707 QCOMPARE(view.dragDropMode(), dragDropMode);
708
709 // ### Test effects of this mode
710}
711
712void tst_QTreeView::dragDropOverwriteMode()
713{
714 QTreeView view;
715 QVERIFY(!view.dragDropOverwriteMode());
716 view.setDragDropOverwriteMode(true);
717 QVERIFY(view.dragDropOverwriteMode());
718 view.setDragDropOverwriteMode(false);
719 QVERIFY(!view.dragDropOverwriteMode());
720
721 // ### This property changes the behavior of dropIndicatorPosition(),
722 // which is protected and called only from within QListWidget and
723 // QTableWidget, from their reimplementations of dropMimeData(). Hard to
724 // test.
725}
726#endif
727
728void tst_QTreeView::editTriggers_data()
729{
730 QTest::addColumn<QAbstractItemView::EditTriggers>(name: "editTriggers");
731 QTest::addColumn<QAbstractItemView::EditTrigger>(name: "triggeredTrigger");
732 QTest::addColumn<bool>(name: "editorOpened");
733
734 // NoEditTriggers
735 QTest::newRow(dataTag: "NoEditTriggers 0") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers)
736 << QAbstractItemView::NoEditTriggers << false;
737 QTest::newRow(dataTag: "NoEditTriggers 1") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers)
738 << QAbstractItemView::CurrentChanged << false;
739 QTest::newRow(dataTag: "NoEditTriggers 2") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers)
740 << QAbstractItemView::DoubleClicked << false;
741 QTest::newRow(dataTag: "NoEditTriggers 3") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers)
742 << QAbstractItemView::SelectedClicked << false;
743 QTest::newRow(dataTag: "NoEditTriggers 4") << QAbstractItemView::EditTriggers(QAbstractItemView::NoEditTriggers)
744 << QAbstractItemView::EditKeyPressed << false;
745
746 // CurrentChanged
747 QTest::newRow(dataTag: "CurrentChanged 0") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged)
748 << QAbstractItemView::NoEditTriggers << false;
749 QTest::newRow(dataTag: "CurrentChanged 1") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged)
750 << QAbstractItemView::CurrentChanged << true;
751 QTest::newRow(dataTag: "CurrentChanged 2") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged)
752 << QAbstractItemView::DoubleClicked << false;
753 QTest::newRow(dataTag: "CurrentChanged 3") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged)
754 << QAbstractItemView::SelectedClicked << false;
755 QTest::newRow(dataTag: "CurrentChanged 4") << QAbstractItemView::EditTriggers(QAbstractItemView::CurrentChanged)
756 << QAbstractItemView::EditKeyPressed << false;
757
758 // DoubleClicked
759 QTest::newRow(dataTag: "DoubleClicked 0") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked)
760 << QAbstractItemView::NoEditTriggers << false;
761 QTest::newRow(dataTag: "DoubleClicked 1") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked)
762 << QAbstractItemView::CurrentChanged << false;
763 QTest::newRow(dataTag: "DoubleClicked 2") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked)
764 << QAbstractItemView::DoubleClicked << true;
765 QTest::newRow(dataTag: "DoubleClicked 3") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked)
766 << QAbstractItemView::SelectedClicked << false;
767 QTest::newRow(dataTag: "DoubleClicked 4") << QAbstractItemView::EditTriggers(QAbstractItemView::DoubleClicked)
768 << QAbstractItemView::EditKeyPressed << false;
769
770 // SelectedClicked
771 QTest::newRow(dataTag: "SelectedClicked 0") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked)
772 << QAbstractItemView::NoEditTriggers << false;
773 QTest::newRow(dataTag: "SelectedClicked 1") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked)
774 << QAbstractItemView::CurrentChanged << false;
775 QTest::newRow(dataTag: "SelectedClicked 2") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked)
776 << QAbstractItemView::DoubleClicked << false;
777 QTest::newRow(dataTag: "SelectedClicked 3") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked)
778 << QAbstractItemView::SelectedClicked << true;
779 QTest::newRow(dataTag: "SelectedClicked 4") << QAbstractItemView::EditTriggers(QAbstractItemView::SelectedClicked)
780 << QAbstractItemView::EditKeyPressed << false;
781
782 // EditKeyPressed
783 QTest::newRow(dataTag: "EditKeyPressed 0") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed)
784 << QAbstractItemView::NoEditTriggers << false;
785 QTest::newRow(dataTag: "EditKeyPressed 1") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed)
786 << QAbstractItemView::CurrentChanged << false;
787 QTest::newRow(dataTag: "EditKeyPressed 2") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed)
788 << QAbstractItemView::DoubleClicked << false;
789 QTest::newRow(dataTag: "EditKeyPressed 3") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed)
790 << QAbstractItemView::SelectedClicked << false;
791 QTest::newRow(dataTag: "EditKeyPressed 4") << QAbstractItemView::EditTriggers(QAbstractItemView::EditKeyPressed)
792 << QAbstractItemView::EditKeyPressed << true;
793}
794
795void tst_QTreeView::editTriggers()
796{
797 QFETCH(QAbstractItemView::EditTriggers, editTriggers);
798 QFETCH(QAbstractItemView::EditTrigger, triggeredTrigger);
799 QFETCH(bool, editorOpened);
800
801 QTreeView view;
802 QStandardItemModel treeModel;
803 initStandardTreeModel(model: &treeModel);
804 view.setModel(&treeModel);
805 view.show();
806
807 QCOMPARE(view.editTriggers(), QAbstractItemView::EditKeyPressed | QAbstractItemView::DoubleClicked);
808
809 // Initialize the first index
810 view.setCurrentIndex(view.model()->index(row: 0, column: 0));
811
812 // Verify that we don't have any editor initially
813 QVERIFY(!view.findChild<QLineEdit *>(QString()));
814
815 // Set the triggers
816 view.setEditTriggers(editTriggers);
817
818 // Interact with the view
819 switch (triggeredTrigger) {
820 case QAbstractItemView::NoEditTriggers:
821 // Do nothing, the editor shouldn't be there
822 break;
823 case QAbstractItemView::CurrentChanged:
824 // Change the index to open an editor
825 view.setCurrentIndex(view.model()->index(row: 1, column: 0));
826 break;
827 case QAbstractItemView::DoubleClicked:
828 // Doubleclick the center of the current cell
829 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {},
830 pos: view.visualRect(index: view.model()->index(row: 0, column: 0)).center());
831 QTest::mouseDClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {},
832 pos: view.visualRect(index: view.model()->index(row: 0, column: 0)).center());
833 break;
834 case QAbstractItemView::SelectedClicked:
835 // Click the center of the current cell
836 view.selectionModel()->select(index: view.model()->index(row: 0, column: 0), command: QItemSelectionModel::Select);
837 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {},
838 pos: view.visualRect(index: view.model()->index(row: 0, column: 0)).center());
839 QTest::qWait(ms: qRound(d: QApplication::doubleClickInterval() * 1.5));
840 break;
841 case QAbstractItemView::EditKeyPressed:
842 view.setFocus();
843#ifdef Q_OS_MAC
844 // OS X uses Enter for editing
845 QTest::keyPress(&view, Qt::Key_Enter);
846#else
847 // All other platforms use F2
848 QTest::keyPress(widget: &view, key: Qt::Key_F2);
849#endif
850 break;
851 default:
852 break;
853 }
854
855 // Check if we got an editor
856 QTRY_COMPARE(view.findChild<QLineEdit *>(QString()) != nullptr, editorOpened);
857}
858
859void tst_QTreeView::hasAutoScroll()
860{
861 QTreeView view;
862 QVERIFY(view.hasAutoScroll());
863 view.setAutoScroll(false);
864 QVERIFY(!view.hasAutoScroll());
865 view.setAutoScroll(true);
866 QVERIFY(view.hasAutoScroll());
867}
868
869void tst_QTreeView::horizontalScrollMode()
870{
871 QStandardItemModel model;
872 for (int i = 0; i < 100; ++i) {
873 model.appendRow(items: QList<QStandardItem *>()
874 << new QStandardItem("An item that has very long text and should"
875 " cause the horizontal scroll bar to appear.")
876 << new QStandardItem("An item that has very long text and should"
877 " cause the horizontal scroll bar to appear."));
878 }
879
880 QTreeView view;
881 setFrameless(&view);
882 view.setModel(&model);
883 view.setFixedSize(w: 100, h: 100);
884 view.header()->resizeSection(logicalIndex: 0, size: 200);
885 view.show();
886
887 QCOMPARE(view.horizontalScrollMode(), QAbstractItemView::ScrollPerPixel);
888 QCOMPARE(view.horizontalScrollBar()->minimum(), 0);
889#ifdef Q_OS_WINRT
890 QEXPECT_FAIL("", "setFixedSize does not work on WinRT - QTBUG-68297", Abort);
891#endif
892 QVERIFY(view.horizontalScrollBar()->maximum() > 2);
893
894 view.setHorizontalScrollMode(QAbstractItemView::ScrollPerItem);
895 QCOMPARE(view.horizontalScrollMode(), QAbstractItemView::ScrollPerItem);
896 QCOMPARE(view.horizontalScrollBar()->minimum(), 0);
897 QCOMPARE(view.horizontalScrollBar()->maximum(), 1);
898
899 view.setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
900 QCOMPARE(view.horizontalScrollMode(), QAbstractItemView::ScrollPerPixel);
901 QCOMPARE(view.horizontalScrollBar()->minimum(), 0);
902 QVERIFY(view.horizontalScrollBar()->maximum() > 2);
903}
904
905class RepaintTreeView : public QTreeView
906{
907public:
908 using QTreeView::QTreeView;
909 bool repainted = false;
910
911protected:
912 void paintEvent(QPaintEvent *event) override
913 { repainted = true; QTreeView::paintEvent(event); }
914};
915
916void tst_QTreeView::iconSize()
917{
918 RepaintTreeView view;
919 QCOMPARE(view.iconSize(), QSize());
920
921 QStandardItemModel treeModel;
922 initStandardTreeModel(model: &treeModel);
923 view.setModel(&treeModel);
924 QCOMPARE(view.iconSize(), QSize());
925 QVERIFY(!view.repainted);
926
927 view.show();
928 view.update();
929 QVERIFY(QTest::qWaitForWindowExposed(&view));
930 QTRY_VERIFY(view.repainted);
931 QCOMPARE(view.iconSize(), QSize());
932
933 view.repainted = false;
934 view.setIconSize(QSize());
935 QTRY_VERIFY(!view.repainted);
936 QCOMPARE(view.iconSize(), QSize());
937
938 view.setIconSize(QSize(10, 10));
939 QTRY_VERIFY(view.repainted);
940 QCOMPARE(view.iconSize(), QSize(10, 10));
941
942 view.repainted = false;
943 view.setIconSize(QSize(10000, 10000));
944 QTRY_VERIFY(view.repainted);
945 QCOMPARE(view.iconSize(), QSize(10000, 10000));
946}
947
948void tst_QTreeView::indexAt()
949{
950 QtTestModel model(5, 5);
951
952 QTreeView view;
953 QCOMPARE(view.indexAt(QPoint()), QModelIndex());
954 view.setModel(&model);
955 QVERIFY(view.indexAt(QPoint()) != QModelIndex());
956
957 QSize itemSize = view.visualRect(index: model.index(row: 0, column: 0)).size();
958 for (int i = 0; i < model.rowCount(); ++i) {
959 QPoint pos(itemSize.width() / 2, (i * itemSize.height()) + (itemSize.height() / 2));
960 QModelIndex index = view.indexAt(p: pos);
961 QCOMPARE(index, model.index(i, 0));
962 }
963
964 /*
965 // ### this is wrong; the widget _will_ affect the item size
966 for (int j = 0; j < model.rowCount(); ++j)
967 view.setIndexWidget(model.index(j, 0), new QPushButton);
968 */
969
970 for (int k = 0; k < model.rowCount(); ++k) {
971 QPoint pos(itemSize.width() / 2, (k * itemSize.height()) + (itemSize.height() / 2));
972 QModelIndex index = view.indexAt(p: pos);
973 QCOMPARE(index, model.index(k, 0));
974 }
975
976 view.show();
977 view.resize(w: 600, h: 600);
978 view.header()->setStretchLastSection(false);
979 QCOMPARE(view.indexAt(QPoint(550, 20)), QModelIndex());
980}
981
982void tst_QTreeView::indexWidget()
983{
984 QTreeView view;
985 QStandardItemModel treeModel;
986 initStandardTreeModel(model: &treeModel);
987 view.setModel(&treeModel);
988 view.resize(w: 300, h: 400); // make sure the width of the view is larger than the widgets below
989
990 QModelIndex index = view.model()->index(row: 0, column: 0);
991
992 QVERIFY(!view.indexWidget(QModelIndex()));
993 QVERIFY(!view.indexWidget(index));
994
995 QString text = QLatin1String("TestLabel");
996
997 QWidget *label = new QLabel(text);
998 view.setIndexWidget(index: QModelIndex(), widget: label);
999 QVERIFY(!view.indexWidget(QModelIndex()));
1000 QVERIFY(!label->parent());
1001 view.setIndexWidget(index, widget: label);
1002 QCOMPARE(view.indexWidget(index), label);
1003 QCOMPARE(label->parentWidget(), view.viewport());
1004
1005
1006 QTextEdit *widget = new QTextEdit(text);
1007 widget->setFixedSize(w: 200,h: 100);
1008 view.setIndexWidget(index, widget);
1009 QCOMPARE(view.indexWidget(index), static_cast<QWidget *>(widget));
1010
1011 QCOMPARE(widget->parentWidget(), view.viewport());
1012 QCOMPARE(widget->geometry(), view.visualRect(index).intersected(widget->geometry()));
1013 QCOMPARE(widget->toPlainText(), text);
1014
1015 //now let's try to do that later when the widget is already shown
1016 view.show();
1017 QVERIFY(QTest::qWaitForWindowExposed(&view));
1018 index = view.model()->index(row: 1, column: 0);
1019 QVERIFY(!view.indexWidget(index));
1020
1021 widget = new QTextEdit(text);
1022 widget->setFixedSize(w: 200,h: 100);
1023 view.setIndexWidget(index, widget);
1024 QCOMPARE(view.indexWidget(index), static_cast<QWidget *>(widget));
1025
1026 QCOMPARE(widget->parentWidget(), view.viewport());
1027 QCOMPARE(widget->geometry(), view.visualRect(index).intersected(widget->geometry()));
1028 QCOMPARE(widget->toPlainText(), text);
1029}
1030
1031void tst_QTreeView::itemDelegate()
1032{
1033 QPointer<QAbstractItemDelegate> oldDelegate;
1034 QPointer<QStyledItemDelegate> otherItemDelegate;
1035
1036 {
1037 QTreeView view;
1038 QVERIFY(qobject_cast<QStyledItemDelegate *>(view.itemDelegate()));
1039 QPointer<QAbstractItemDelegate> oldDelegate = view.itemDelegate();
1040
1041 otherItemDelegate = new QStyledItemDelegate;
1042 view.setItemDelegate(otherItemDelegate);
1043 QVERIFY(!otherItemDelegate->parent());
1044 QVERIFY(oldDelegate);
1045
1046 QCOMPARE(view.itemDelegate(), otherItemDelegate);
1047
1048 view.setItemDelegate(nullptr);
1049 QVERIFY(!view.itemDelegate()); // <- view does its own drawing?
1050 QVERIFY(otherItemDelegate);
1051 }
1052
1053 // This is strange. Why doesn't setItemDelegate() reparent the delegate?
1054 QVERIFY(!oldDelegate);
1055 QVERIFY(otherItemDelegate);
1056
1057 delete otherItemDelegate;
1058}
1059
1060void tst_QTreeView::itemDelegateForColumnOrRow()
1061{
1062 QTreeView view;
1063 QAbstractItemDelegate *defaultDelegate = view.itemDelegate();
1064 QVERIFY(defaultDelegate);
1065
1066 QVERIFY(!view.itemDelegateForRow(0));
1067 QVERIFY(!view.itemDelegateForColumn(0));
1068 QCOMPARE(view.itemDelegate(QModelIndex()), defaultDelegate);
1069
1070 QStandardItemModel model;
1071 for (int i = 0; i < 100; ++i) {
1072 model.appendRow(items: QList<QStandardItem *>()
1073 << new QStandardItem("An item that has very long text and should"
1074 " cause the horizontal scroll bar to appear.")
1075 << new QStandardItem("An item that has very long text and should"
1076 " cause the horizontal scroll bar to appear.")
1077 << new QStandardItem("An item that has very long text and should"
1078 " cause the horizontal scroll bar to appear."));
1079 }
1080 view.setModel(&model);
1081
1082 QVERIFY(!view.itemDelegateForRow(0));
1083 QVERIFY(!view.itemDelegateForColumn(0));
1084 QCOMPARE(view.itemDelegate(QModelIndex()), defaultDelegate);
1085 QCOMPARE(view.itemDelegate(view.model()->index(0, 0)), defaultDelegate);
1086
1087 QPointer<QAbstractItemDelegate> rowDelegate = new QStyledItemDelegate;
1088 view.setItemDelegateForRow(row: 0, delegate: rowDelegate);
1089 QVERIFY(!rowDelegate->parent());
1090 QCOMPARE(view.itemDelegateForRow(0), rowDelegate);
1091 QCOMPARE(view.itemDelegate(view.model()->index(0, 0)), rowDelegate);
1092 QCOMPARE(view.itemDelegate(view.model()->index(0, 1)), rowDelegate);
1093 QCOMPARE(view.itemDelegate(view.model()->index(1, 0)), defaultDelegate);
1094 QCOMPARE(view.itemDelegate(view.model()->index(1, 1)), defaultDelegate);
1095
1096 QPointer<QAbstractItemDelegate> columnDelegate = new QStyledItemDelegate;
1097 view.setItemDelegateForColumn(column: 1, delegate: columnDelegate);
1098 QVERIFY(!columnDelegate->parent());
1099 QCOMPARE(view.itemDelegateForColumn(1), columnDelegate);
1100 QCOMPARE(view.itemDelegate(view.model()->index(0, 0)), rowDelegate);
1101 QCOMPARE(view.itemDelegate(view.model()->index(0, 1)), rowDelegate); // row wins
1102 QCOMPARE(view.itemDelegate(view.model()->index(1, 0)), defaultDelegate);
1103 QCOMPARE(view.itemDelegate(view.model()->index(1, 1)), columnDelegate);
1104
1105 view.setItemDelegateForRow(row: 0, delegate: nullptr);
1106 QVERIFY(!view.itemDelegateForRow(0));
1107 QVERIFY(rowDelegate); // <- wasn't deleted
1108
1109 view.setItemDelegateForColumn(column: 1, delegate: nullptr);
1110 QVERIFY(!view.itemDelegateForColumn(1));
1111 QVERIFY(columnDelegate); // <- wasn't deleted
1112
1113 delete rowDelegate;
1114 delete columnDelegate;
1115}
1116
1117void tst_QTreeView::keyboardSearch()
1118{
1119 QTreeView view;
1120 QStandardItemModel model;
1121 model.appendRow(aitem: new QStandardItem("Andreas"));
1122 model.appendRow(aitem: new QStandardItem("Baldrian"));
1123 model.appendRow(aitem: new QStandardItem("Cecilie"));
1124 view.setModel(&model);
1125 view.show();
1126
1127 // Nothing is selected
1128 QVERIFY(!view.selectionModel()->hasSelection());
1129 QVERIFY(!view.selectionModel()->isSelected(model.index(0, 0)));
1130
1131 // First item is selected
1132 view.keyboardSearch(search: QLatin1String("A"));
1133 QTRY_VERIFY(view.selectionModel()->isSelected(model.index(0, 0)));
1134
1135 // First item is still selected
1136 view.keyboardSearch(search: QLatin1String("n"));
1137 QVERIFY(view.selectionModel()->isSelected(model.index(0, 0)));
1138
1139 // No "AnB" item - keep the same selection.
1140 view.keyboardSearch(search: QLatin1String("B"));
1141 QVERIFY(view.selectionModel()->isSelected(model.index(0, 0)));
1142
1143 // Wait a bit.
1144 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1145
1146 // The item that starts with B is selected.
1147 view.keyboardSearch(search: QLatin1String("B"));
1148 QVERIFY(view.selectionModel()->isSelected(model.index(1, 0)));
1149
1150 // Test that it wraps round
1151 model.appendRow(aitem: new QStandardItem("Andy"));
1152 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1153 view.keyboardSearch(search: QLatin1String("A"));
1154 QVERIFY(view.selectionModel()->isSelected(model.index(3, 0)));
1155 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1156 view.keyboardSearch(search: QLatin1String("A"));
1157 QVERIFY(view.selectionModel()->isSelected(model.index(0, 0)));
1158 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1159 view.keyboardSearch(search: QLatin1String("A"));
1160 QVERIFY(view.selectionModel()->isSelected(model.index(3, 0)));
1161
1162 // Test that it handles the case where the first item is hidden correctly
1163 model.insertRow(arow: 0, aitem: new QStandardItem("Hidden item"));
1164 view.setRowHidden(row: 0, parent: QModelIndex(), hide: true);
1165
1166 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1167 view.keyboardSearch(search: QLatin1String("A"));
1168 QVERIFY(view.selectionModel()->isSelected(model.index(1, 0)));
1169 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1170 view.keyboardSearch(search: QLatin1String("A"));
1171 QVERIFY(view.selectionModel()->isSelected(model.index(4, 0)));
1172 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1173 view.keyboardSearch(search: QLatin1String("A"));
1174 QVERIFY(view.selectionModel()->isSelected(model.index(1, 0)));
1175
1176 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1177 model.clear();
1178 view.setCurrentIndex(QModelIndex());
1179 model.appendRow(items: { new QStandardItem("Andreas"), new QStandardItem("Alicia") });
1180 model.appendRow(items: { new QStandardItem("Baldrian"), new QStandardItem("Belinda") });
1181 model.appendRow(items: { new QStandardItem("Cecilie"), new QStandardItem("Claire") });
1182 QVERIFY(!view.selectionModel()->hasSelection());
1183 QVERIFY(!view.selectionModel()->isSelected(model.index(0, 0)));
1184
1185 // We want to search on the 2nd column so we have to force it to have
1186 // an index in that column as a starting point
1187 view.setCurrentIndex(QModelIndex(model.index(row: 0, column: 1)));
1188 // Second item in first row is selected
1189 view.keyboardSearch(search: QLatin1String("A"));
1190 QTRY_VERIFY(view.selectionModel()->isSelected(model.index(0, 1)));
1191 QVERIFY(view.currentIndex() == model.index(0, 1));
1192
1193 // Second item in first row is still selected
1194 view.keyboardSearch(search: QLatin1String("l"));
1195 QVERIFY(view.selectionModel()->isSelected(model.index(0, 1)));
1196 QCOMPARE(view.currentIndex(), model.index(0, 1));
1197
1198 // No "AnB" item - keep the same selection.
1199 view.keyboardSearch(search: QLatin1String("B"));
1200 QVERIFY(view.selectionModel()->isSelected(model.index(0, 1)));
1201 QCOMPARE(view.currentIndex(), model.index(0, 1));
1202
1203 // Wait a bit.
1204 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1205
1206 // The item that starts with B is selected.
1207 view.keyboardSearch(search: QLatin1String("B"));
1208 QVERIFY(view.selectionModel()->isSelected(model.index(1, 1)));
1209 QCOMPARE(view.currentIndex(), model.index(1, 1));
1210
1211 // Test that it wraps round
1212 model.appendRow(items: { new QStandardItem("Andy"), new QStandardItem("Adele") });
1213 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1214 view.keyboardSearch(search: QLatin1String("A"));
1215 QVERIFY(view.selectionModel()->isSelected(model.index(3, 1)));
1216 QCOMPARE(view.currentIndex(), model.index(3, 1));
1217 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1218 view.keyboardSearch(search: QLatin1String("A"));
1219 QVERIFY(view.selectionModel()->isSelected(model.index(0, 1)));
1220 QCOMPARE(view.currentIndex(), model.index(0, 1));
1221 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1222 view.keyboardSearch(search: QLatin1String("A"));
1223 QVERIFY(view.selectionModel()->isSelected(model.index(3, 1)));
1224 QCOMPARE(view.currentIndex(), model.index(3, 1));
1225
1226 // Test that it handles the case where the first item is hidden correctly
1227 model.insertRow(arow: 0, aitem: new QStandardItem("Hidden item"));
1228 view.setRowHidden(row: 0, parent: QModelIndex(), hide: true);
1229
1230 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1231 view.keyboardSearch(search: QLatin1String("A"));
1232 QVERIFY(view.selectionModel()->isSelected(model.index(1, 1)));
1233 QCOMPARE(view.currentIndex(), model.index(1, 1));
1234 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1235 view.keyboardSearch(search: QLatin1String("A"));
1236 QVERIFY(view.selectionModel()->isSelected(model.index(4, 1)));
1237 QCOMPARE(view.currentIndex(), model.index(4, 1));
1238 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1239 view.keyboardSearch(search: QLatin1String("A"));
1240 QVERIFY(view.selectionModel()->isSelected(model.index(1, 1)));
1241 QCOMPARE(view.currentIndex(), model.index(1, 1));
1242}
1243
1244void tst_QTreeView::keyboardSearchMultiColumn()
1245{
1246 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1247 QSKIP("Wayland: This fails. Figure out why.");
1248
1249 QTreeView view;
1250 QStandardItemModel model(4, 2);
1251
1252 model.setItem(row: 0, column: 0, item: new QStandardItem("1")); model.setItem(row: 0, column: 1, item: new QStandardItem("green"));
1253 model.setItem(row: 1, column: 0, item: new QStandardItem("bad")); model.setItem(row: 1, column: 1, item: new QStandardItem("eggs"));
1254 model.setItem(row: 2, column: 0, item: new QStandardItem("moof")); model.setItem(row: 2, column: 1, item: new QStandardItem("and"));
1255 model.setItem(row: 3, column: 0, item: new QStandardItem("elf")); model.setItem(row: 3, column: 1, item: new QStandardItem("ham"));
1256
1257 view.setModel(&model);
1258 view.show();
1259 QApplication::setActiveWindow(&view);
1260 QVERIFY(QTest::qWaitForWindowActive(&view));
1261
1262 view.setCurrentIndex(model.index(row: 0, column: 1));
1263
1264 // First item is selected
1265 view.keyboardSearch(search: QLatin1String("eggs"));
1266 QVERIFY(view.selectionModel()->isSelected(model.index(1, 1)));
1267
1268 QTest::qWait(ms: QApplication::keyboardInputInterval() * 2);
1269
1270 // 'ham' is selected
1271 view.keyboardSearch(search: QLatin1String("h"));
1272 QVERIFY(view.selectionModel()->isSelected(model.index(3, 1)));
1273}
1274
1275void tst_QTreeView::setModel()
1276{
1277 QTreeView view;
1278 view.show();
1279 QCOMPARE(view.model(), nullptr);
1280 QCOMPARE(view.selectionModel(), nullptr);
1281 QCOMPARE(view.header()->model(), nullptr);
1282 QCOMPARE(view.header()->selectionModel(), nullptr);
1283
1284 for (int x = 0; x < 2; ++x) {
1285 QtTestModel *model = new QtTestModel(10, 8);
1286 QAbstractItemModel *oldModel = view.model();
1287 QSignalSpy modelDestroyedSpy(oldModel ? oldModel : model, &QObject::destroyed);
1288 // set the same model twice
1289 for (int i = 0; i < 2; ++i) {
1290 QItemSelectionModel *oldSelectionModel = view.selectionModel();
1291 QItemSelectionModel *dummy = new QItemSelectionModel(model);
1292 QSignalSpy selectionModelDestroyedSpy(
1293 oldSelectionModel ? oldSelectionModel : dummy, &QObject::destroyed);
1294 view.setModel(model);
1295// QCOMPARE(selectionModelDestroyedSpy.count(), (x == 0 || i == 1) ? 0 : 1);
1296 QCOMPARE(view.model(), model);
1297 QCOMPARE(view.header()->model(), model);
1298 QCOMPARE(view.selectionModel() != oldSelectionModel, (i == 0));
1299 }
1300 QTRY_COMPARE(modelDestroyedSpy.count(), 0);
1301
1302 view.setModel(nullptr);
1303 QCOMPARE(view.model(), nullptr);
1304 // ### shouldn't selectionModel also be 0 now?
1305// QCOMPARE(view.selectionModel(), nullptr);
1306 delete model;
1307 }
1308}
1309
1310void tst_QTreeView::openPersistentEditor()
1311{
1312 QTreeView view;
1313 QStandardItemModel treeModel;
1314 initStandardTreeModel(model: &treeModel);
1315 view.setModel(&treeModel);
1316 view.show();
1317
1318 QVERIFY(!view.viewport()->findChild<QLineEdit *>());
1319 view.openPersistentEditor(index: view.model()->index(row: 0, column: 0));
1320 QVERIFY(view.viewport()->findChild<QLineEdit *>());
1321
1322 view.closePersistentEditor(index: view.model()->index(row: 0, column: 0));
1323 QVERIFY(!view.viewport()->findChild<QLineEdit *>()->isVisible());
1324
1325 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
1326 QVERIFY(!view.viewport()->findChild<QLineEdit *>());
1327}
1328
1329void tst_QTreeView::rootIndex()
1330{
1331 QTreeView view;
1332 QCOMPARE(view.rootIndex(), QModelIndex());
1333 QStandardItemModel treeModel;
1334 initStandardTreeModel(model: &treeModel);
1335 view.setModel(&treeModel);
1336 QCOMPARE(view.rootIndex(), QModelIndex());
1337
1338 view.setRootIndex(view.model()->index(row: 1, column: 0));
1339
1340 QCOMPARE(view.model()->data(view.model()->index(0, view.header()->visualIndex(0), view.rootIndex()), Qt::DisplayRole)
1341 .toString(), QString("Row 2 Child Item"));
1342}
1343
1344void tst_QTreeView::setHeader()
1345{
1346 QTreeView view;
1347 QVERIFY(view.header() != nullptr);
1348 QCOMPARE(view.header()->orientation(), Qt::Horizontal);
1349 QCOMPARE(view.header()->parent(), &view);
1350 for (int x = 0; x < 2; ++x) {
1351 QSignalSpy destroyedSpy(view.header(), &QObject::destroyed);
1352 Qt::Orientation orient = x ? Qt::Vertical : Qt::Horizontal;
1353 QHeaderView *head = new QHeaderView(orient);
1354 view.setHeader(head);
1355 QCOMPARE(destroyedSpy.count(), 1);
1356 QCOMPARE(head->parent(), &view);
1357 QCOMPARE(view.header(), head);
1358 view.setHeader(head);
1359 QCOMPARE(view.header(), head);
1360 view.setHeader(nullptr);
1361 QCOMPARE(view.header(), head);
1362 }
1363}
1364
1365void tst_QTreeView::columnHidden()
1366{
1367 QTreeView view;
1368 QtTestModel model(10, 8);
1369 view.setModel(&model);
1370 view.show();
1371 for (int c = 0; c < model.columnCount(); ++c)
1372 QCOMPARE(view.isColumnHidden(c), false);
1373 // hide even columns
1374 for (int c = 0; c < model.columnCount(); c += 2)
1375 view.setColumnHidden(column: c, hide: true);
1376 for (int c = 0; c < model.columnCount(); ++c)
1377 QCOMPARE(view.isColumnHidden(c), (c & 1) == 0);
1378 view.update();
1379 // hide odd columns too
1380 for (int c = 1; c < model.columnCount(); c += 2)
1381 view.setColumnHidden(column: c, hide: true);
1382 for (int c = 0; c < model.columnCount(); ++c)
1383 QCOMPARE(view.isColumnHidden(c), true);
1384 view.update();
1385
1386 // QTBUG 54610
1387 // QAbstractItemViewPrivate::_q_layoutChanged() is called on
1388 // rows/columnMoved and because this function is virtual,
1389 // QHeaderViewPrivate::_q_layoutChanged() was called and unhided
1390 // all sections because QHeaderViewPrivate::_q_layoutAboutToBeChanged()
1391 // could not fill persistentHiddenSections (and is not needed)
1392 view.hideColumn(column: model.cols - 1);
1393 QCOMPARE(view.isColumnHidden(model.cols - 1), true);
1394 model.simulateMoveRows();
1395 QCOMPARE(view.isColumnHidden(model.cols - 1), true);
1396}
1397
1398void tst_QTreeView::rowHidden()
1399{
1400 QtTestModel model(4, 6);
1401 model.levels = 3;
1402 QTreeView view;
1403 view.resize(w: 500,h: 500);
1404 view.setModel(&model);
1405 view.show();
1406
1407 QCOMPARE(view.isRowHidden(-1, QModelIndex()), false);
1408 QCOMPARE(view.isRowHidden(999999, QModelIndex()), false);
1409 view.setRowHidden(row: -1, parent: QModelIndex(), hide: true);
1410 view.setRowHidden(row: 999999, parent: QModelIndex(), hide: true);
1411 QCOMPARE(view.isRowHidden(-1, QModelIndex()), false);
1412 QCOMPARE(view.isRowHidden(999999, QModelIndex()), false);
1413
1414 view.setRowHidden(row: 0, parent: QModelIndex(), hide: true);
1415 QCOMPARE(view.isRowHidden(0, QModelIndex()), true);
1416 view.setRowHidden(row: 0, parent: QModelIndex(), hide: false);
1417 QCOMPARE(view.isRowHidden(0, QModelIndex()), false);
1418
1419 QStack<QModelIndex> parents;
1420 parents.push(t: QModelIndex());
1421 while (!parents.isEmpty()) {
1422 QModelIndex p = parents.pop();
1423 if (model.canFetchMore(p))
1424 model.fetchMore(p);
1425 int rows = model.rowCount(parent: p);
1426 // hide all
1427 for (int r = 0; r < rows; ++r) {
1428 view.setRowHidden(row: r, parent: p, hide: true);
1429 QCOMPARE(view.isRowHidden(r, p), true);
1430 }
1431 // hide none
1432 for (int r = 0; r < rows; ++r) {
1433 view.setRowHidden(row: r, parent: p, hide: false);
1434 QCOMPARE(view.isRowHidden(r, p), false);
1435 }
1436 // hide only even rows
1437 for (int r = 0; r < rows; ++r) {
1438 bool hide = (r & 1) == 0;
1439 view.setRowHidden(row: r, parent: p, hide);
1440 QCOMPARE(view.isRowHidden(r, p), hide);
1441 }
1442 for (int r = 0; r < rows; ++r)
1443 parents.push(t: model.index(row: r, column: 0, parent: p));
1444 }
1445
1446 parents.push(t: QModelIndex());
1447 while (!parents.isEmpty()) {
1448 QModelIndex p = parents.pop();
1449 // all even rows should still be hidden
1450 for (int r = 0; r < model.rowCount(parent: p); ++r)
1451 QCOMPARE(view.isRowHidden(r, p), (r & 1) == 0);
1452 if (model.rowCount(parent: p) > 0) {
1453 for (int r = 0; r < model.rowCount(parent: p); ++r)
1454 parents.push(t: model.index(row: r, column: 0, parent: p));
1455 }
1456 }
1457}
1458
1459void tst_QTreeView::noDelegate()
1460{
1461 QtTestModel model(10, 7);
1462 QTreeView view;
1463 view.setModel(&model);
1464 view.setItemDelegate(nullptr);
1465 QCOMPARE(view.itemDelegate(), nullptr);
1466}
1467
1468void tst_QTreeView::noModel()
1469{
1470 QTreeView view;
1471 view.show();
1472 view.setRowHidden(row: 0, parent: QModelIndex(), hide: true);
1473 // no model -> not able to hide a row
1474 QVERIFY(!view.isRowHidden(0, QModelIndex()));
1475}
1476
1477void tst_QTreeView::emptyModel()
1478{
1479 QtTestModel model(0, 0);
1480 QTreeView view;
1481 view.setModel(&model);
1482 view.show();
1483 QVERIFY(!model.wrongIndex);
1484}
1485
1486void tst_QTreeView::removeRows()
1487{
1488 QtTestModel model(7, 10);
1489
1490 QTreeView view;
1491
1492 view.setModel(&model);
1493 view.show();
1494
1495 model.removeLastRow();
1496 QVERIFY(!model.wrongIndex);
1497
1498 model.removeAllRows();
1499 QVERIFY(!model.wrongIndex);
1500}
1501
1502void tst_QTreeView::removeCols()
1503{
1504 QtTestModel model(5, 8);
1505
1506 QTreeView view;
1507 view.setModel(&model);
1508 view.show();
1509 model.fetched = true;
1510 model.removeLastColumn();
1511 QVERIFY(!model.wrongIndex);
1512 QCOMPARE(view.header()->count(), model.cols);
1513
1514 model.removeAllColumns();
1515 QVERIFY(!model.wrongIndex);
1516 QCOMPARE(view.header()->count(), model.cols);
1517}
1518
1519void tst_QTreeView::limitedExpand()
1520{
1521 {
1522 QStandardItemModel model;
1523 QStandardItem *parentItem = model.invisibleRootItem();
1524 parentItem->appendRow(aitem: new QStandardItem);
1525 parentItem->appendRow(aitem: new QStandardItem);
1526 parentItem->appendRow(aitem: new QStandardItem);
1527
1528 QStandardItem *firstItem = model.item(row: 0, column: 0);
1529 firstItem->setFlags(firstItem->flags() | Qt::ItemNeverHasChildren);
1530
1531 QTreeView view;
1532 view.setModel(&model);
1533
1534 QSignalSpy spy(&view, &QTreeView::expanded);
1535 QVERIFY(spy.isValid());
1536
1537 view.expand(index: model.index(row: 0, column: 0));
1538 QCOMPARE(spy.count(), 0);
1539
1540 view.expand(index: model.index(row: 1, column: 0));
1541 QCOMPARE(spy.count(), 1);
1542 }
1543 {
1544 QStringListModel model(QStringList() << "one" << "two");
1545 QTreeView view;
1546 view.setModel(&model);
1547
1548 QSignalSpy spy(&view, &QTreeView::expanded);
1549 QVERIFY(spy.isValid());
1550
1551 view.expand(index: model.index(row: 0, column: 0));
1552 QCOMPARE(spy.count(), 0);
1553 view.expandAll();
1554 QCOMPARE(spy.count(), 0);
1555 }
1556}
1557
1558void tst_QTreeView::expandAndCollapse_data()
1559{
1560 QTest::addColumn<bool>(name: "animationEnabled");
1561 QTest::newRow(dataTag: "normal") << false;
1562 QTest::newRow(dataTag: "animated") << true;
1563}
1564
1565void tst_QTreeView::expandAndCollapse()
1566{
1567 QFETCH(bool, animationEnabled);
1568
1569 QtTestModel model(10, 9);
1570
1571 QTreeView view;
1572 view.setUniformRowHeights(true);
1573 view.setModel(&model);
1574 view.setAnimated(animationEnabled);
1575 view.show();
1576
1577 QModelIndex a = model.index(row: 0, column: 0, parent: QModelIndex());
1578 QModelIndex b = model.index(row: 0, column: 0, parent: a);
1579
1580 QSignalSpy expandedSpy(&view, &QTreeView::expanded);
1581 QSignalSpy collapsedSpy(&view, &QTreeView::collapsed);
1582 QVariantList args;
1583
1584 for (int y = 0; y < 2; ++y) {
1585 view.setVisible(y == 0);
1586 for (int x = 0; x < 2; ++x) {
1587 view.setItemsExpandable(x == 0);
1588
1589 // Test bad args
1590 view.expand(index: QModelIndex());
1591 QCOMPARE(view.isExpanded(QModelIndex()), false);
1592 view.collapse(index: QModelIndex());
1593 QCOMPARE(expandedSpy.count(), 0);
1594 QCOMPARE(collapsedSpy.count(), 0);
1595
1596 // expand a first level item
1597 QVERIFY(!view.isExpanded(a));
1598 view.expand(index: a);
1599 QVERIFY(view.isExpanded(a));
1600 QCOMPARE(expandedSpy.count(), 1);
1601 QCOMPARE(collapsedSpy.count(), 0);
1602 args = expandedSpy.takeFirst();
1603 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), a);
1604
1605 view.expand(index: a);
1606 QVERIFY(view.isExpanded(a));
1607 QCOMPARE(expandedSpy.count(), 0);
1608 QCOMPARE(collapsedSpy.count(), 0);
1609
1610 // expand a second level item
1611 QVERIFY(!view.isExpanded(b));
1612 view.expand(index: b);
1613 QVERIFY(view.isExpanded(a));
1614 QVERIFY(view.isExpanded(b));
1615 QCOMPARE(expandedSpy.count(), 1);
1616 QCOMPARE(collapsedSpy.count(), 0);
1617 args = expandedSpy.takeFirst();
1618 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), b);
1619
1620 view.expand(index: b);
1621 QVERIFY(view.isExpanded(b));
1622 QCOMPARE(expandedSpy.count(), 0);
1623 QCOMPARE(collapsedSpy.count(), 0);
1624
1625 // collapse the first level item
1626 view.collapse(index: a);
1627 QVERIFY(!view.isExpanded(a));
1628 QVERIFY(view.isExpanded(b));
1629 QCOMPARE(expandedSpy.count(), 0);
1630 QCOMPARE(collapsedSpy.count(), 1);
1631 args = collapsedSpy.takeFirst();
1632 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), a);
1633
1634 view.collapse(index: a);
1635 QVERIFY(!view.isExpanded(a));
1636 QCOMPARE(expandedSpy.count(), 0);
1637 QCOMPARE(collapsedSpy.count(), 0);
1638
1639 // expand the first level item again
1640 view.expand(index: a);
1641 QVERIFY(view.isExpanded(a));
1642 QVERIFY(view.isExpanded(b));
1643 QCOMPARE(expandedSpy.count(), 1);
1644 QCOMPARE(collapsedSpy.count(), 0);
1645 args = expandedSpy.takeFirst();
1646 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), a);
1647
1648 // collapse the second level item
1649 view.collapse(index: b);
1650 QVERIFY(view.isExpanded(a));
1651 QVERIFY(!view.isExpanded(b));
1652 QCOMPARE(expandedSpy.count(), 0);
1653 QCOMPARE(collapsedSpy.count(), 1);
1654 args = collapsedSpy.takeFirst();
1655 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), b);
1656
1657 // collapse the first level item
1658 view.collapse(index: a);
1659 QVERIFY(!view.isExpanded(a));
1660 QVERIFY(!view.isExpanded(b));
1661 QCOMPARE(expandedSpy.count(), 0);
1662 QCOMPARE(collapsedSpy.count(), 1);
1663 args = collapsedSpy.takeFirst();
1664 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), a);
1665
1666 // expand and remove row
1667 QPersistentModelIndex c = model.index(row: 9, column: 0, parent: b);
1668 view.expand(index: a);
1669 view.expand(index: b);
1670 model.removeLastRow(); // remove c
1671 QVERIFY(view.isExpanded(a));
1672 QVERIFY(view.isExpanded(b));
1673 QVERIFY(!view.isExpanded(c));
1674 QCOMPARE(expandedSpy.count(), 2);
1675 QCOMPARE(collapsedSpy.count(), 0);
1676 args = expandedSpy.takeFirst();
1677 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), a);
1678 args = expandedSpy.takeFirst();
1679 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), b);
1680
1681 view.collapse(index: a);
1682 view.collapse(index: b);
1683 QVERIFY(!view.isExpanded(a));
1684 QVERIFY(!view.isExpanded(b));
1685 QVERIFY(!view.isExpanded(c));
1686 QCOMPARE(expandedSpy.count(), 0);
1687 QCOMPARE(collapsedSpy.count(), 2);
1688 args = collapsedSpy.takeFirst();
1689 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), a);
1690 args = collapsedSpy.takeFirst();
1691 QCOMPARE(qvariant_cast<QModelIndex>(args.at(0)), b);
1692 }
1693 }
1694}
1695
1696static void checkExpandState(const QAbstractItemModel &model, const QTreeView &view,
1697 const QModelIndex &startIdx, bool bIsExpanded, int *count)
1698{
1699 *count = 0;
1700 QStack<QModelIndex> parents;
1701 parents.push(t: startIdx);
1702 if (startIdx.isValid()) {
1703 QCOMPARE(view.isExpanded(startIdx), bIsExpanded);
1704 *count += 1;
1705 }
1706 while (!parents.isEmpty()) {
1707 const QModelIndex p = parents.pop();
1708 const int rows = model.rowCount(parent: p);
1709 for (int r = 0; r < rows; ++r) {
1710 const QModelIndex c = model.index(row: r, column: 0, parent: p);
1711 QCOMPARE(view.isExpanded(c), bIsExpanded);
1712 parents.push(t: c);
1713 }
1714 *count += rows;
1715 }
1716}
1717
1718void tst_QTreeView::expandAndCollapseAll()
1719{
1720 QStandardItemModel model;
1721 // QtTestModel has a broken parent/child handling which will break the test
1722 for (int i1 = 0; i1 < 3; ++i1) {
1723 QStandardItem *s1 = new QStandardItem;
1724 s1->setText(QString::number(i1));
1725 model.appendRow(aitem: s1);
1726 for (int i2 = 0; i2 < 3; ++i2) {
1727 QStandardItem *s2 = new QStandardItem;
1728 s2->setText(QStringLiteral("%1 - %2").arg(a: i1).arg(a: i2));
1729 s1->appendRow(aitem: s2);
1730 for (int i3 = 0; i3 < 3; ++i3) {
1731 QStandardItem *s3 = new QStandardItem;
1732 s3->setText(QStringLiteral("%1 - %2 - %3").arg(a: i1).arg(a: i2).arg(a: i3));
1733 s2->appendRow(aitem: s3);
1734 }
1735 }
1736 }
1737 QTreeView view;
1738 view.setUniformRowHeights(true);
1739 view.setModel(&model);
1740 view.show();
1741 QVERIFY(QTest::qWaitForWindowExposed(&view));
1742
1743 QSignalSpy expandedSpy(&view, &QTreeView::expanded);
1744 QSignalSpy collapsedSpy(&view, &QTreeView::collapsed);
1745 int count;
1746
1747 view.expandAll();
1748 checkExpandState(model, view, startIdx: QModelIndex(), bIsExpanded: true, count: &count);
1749 QCOMPARE(collapsedSpy.count(), 0);
1750 QCOMPARE(expandedSpy.count(), 39); // == 3 (first) + 9 (second) + 27 (third level)
1751 QCOMPARE(count, 39);
1752
1753 collapsedSpy.clear();
1754 expandedSpy.clear();
1755 view.collapseAll();
1756 checkExpandState(model, view, startIdx: QModelIndex(), bIsExpanded: false, count: &count);
1757 QCOMPARE(collapsedSpy.count(), 39);
1758 QCOMPARE(expandedSpy.count(), 0);
1759 QCOMPARE(count, 39);
1760
1761 collapsedSpy.clear();
1762 expandedSpy.clear();
1763 view.expandRecursively(index: model.index(row: 0, column: 0));
1764 QCOMPARE(expandedSpy.count(), 13); // 1 + 3 + 9
1765
1766 checkExpandState(model, view, startIdx: model.index(row: 0, column: 0), bIsExpanded: true, count: &count);
1767 QCOMPARE(count, 13);
1768 checkExpandState(model, view, startIdx: model.index(row: 1, column: 0), bIsExpanded: false, count: &count);
1769 QCOMPARE(count, 13);
1770 checkExpandState(model, view, startIdx: model.index(row: 2, column: 0), bIsExpanded: false, count: &count);
1771 QCOMPARE(count, 13);
1772
1773 expandedSpy.clear();
1774 view.collapseAll();
1775 view.expandRecursively(index: model.index(row: 0, column: 0), depth: 1);
1776 QCOMPARE(expandedSpy.count(), 4); // 1 + 3
1777 view.expandRecursively(index: model.index(row: 0, column: 0), depth: 2);
1778 QCOMPARE(expandedSpy.count(), 13); // (1 + 3) + 9
1779
1780 checkExpandState(model, view, startIdx: model.index(row: 0, column: 0), bIsExpanded: true, count: &count);
1781 QCOMPARE(count, 13);
1782 checkExpandState(model, view, startIdx: model.index(row: 1, column: 0), bIsExpanded: false, count: &count);
1783 QCOMPARE(count, 13);
1784 checkExpandState(model, view, startIdx: model.index(row: 2, column: 0), bIsExpanded: false, count: &count);
1785 QCOMPARE(count, 13);
1786}
1787
1788void tst_QTreeView::expandWithNoChildren()
1789{
1790 QTreeView tree;
1791 QStandardItemModel model(1, 1);
1792 tree.setModel(&model);
1793 tree.setAnimated(true);
1794 tree.doItemsLayout();
1795 //this test should not output warnings
1796 tree.expand(index: model.index(row: 0,column: 0));
1797}
1798
1799
1800
1801void tst_QTreeView::keyboardNavigation()
1802{
1803 const int rows = 10;
1804 const int columns = 7;
1805
1806 QtTestModel model(rows, columns);
1807
1808 QTreeView view;
1809 view.setModel(&model);
1810 view.show();
1811
1812 const auto keymoves = {
1813 Qt::Key_Down, Qt::Key_Right, Qt::Key_Right, Qt::Key_Right,
1814 Qt::Key_Down, Qt::Key_Down, Qt::Key_Down, Qt::Key_Down,
1815 Qt::Key_Right, Qt::Key_Right, Qt::Key_Right,
1816 Qt::Key_Left, Qt::Key_Up, Qt::Key_Left, Qt::Key_Left,
1817 Qt::Key_Up, Qt::Key_Down, Qt::Key_Up, Qt::Key_Up,
1818 Qt::Key_Up, Qt::Key_Up, Qt::Key_Up, Qt::Key_Up,
1819 Qt::Key_Left, Qt::Key_Left, Qt::Key_Up, Qt::Key_Down
1820 };
1821
1822 int row = 0;
1823 int column = 0;
1824 QModelIndex index = model.index(row, column, parent: QModelIndex());
1825 view.setCurrentIndex(index);
1826 QCOMPARE(view.currentIndex(), index);
1827
1828 for (Qt::Key key : keymoves) {
1829 QTest::keyClick(widget: &view, key);
1830
1831 switch (key) {
1832 case Qt::Key_Up:
1833 if (row > 0) {
1834 index = index.sibling(arow: row - 1, acolumn: column);
1835 row -= 1;
1836 } else if (index.parent() != QModelIndex()) {
1837 index = index.parent();
1838 row = index.row();
1839 }
1840 break;
1841 case Qt::Key_Down:
1842 if (view.isExpanded(index)) {
1843 row = 0;
1844 index = model.index(row, column, parent: index);
1845 } else {
1846 row = qMin(a: rows - 1, b: row + 1);
1847 index = index.sibling(arow: row, acolumn: column);
1848 }
1849 break;
1850 case Qt::Key_Left: {
1851 QScrollBar *b = view.horizontalScrollBar();
1852 if (b->value() == b->minimum())
1853 QVERIFY(!view.isExpanded(index));
1854 // windows style right will walk to the parent
1855 if (view.currentIndex() != index) {
1856 QCOMPARE(view.currentIndex(), index.parent());
1857 index = view.currentIndex();
1858 row = index.row();
1859 column = index.column();
1860 }
1861 break;
1862 }
1863 case Qt::Key_Right:
1864 QVERIFY(view.isExpanded(index));
1865 // windows style right will walk to the first child
1866 if (view.currentIndex() != index) {
1867 QCOMPARE(view.currentIndex().parent(), index);
1868 row = view.currentIndex().row();
1869 column = view.currentIndex().column();
1870 index = view.currentIndex();
1871 }
1872 break;
1873 default:
1874 QVERIFY(false);
1875 }
1876
1877 QCOMPARE(view.currentIndex().row(), row);
1878 QCOMPARE(view.currentIndex().column(), column);
1879 QCOMPARE(view.currentIndex(), index);
1880 }
1881}
1882
1883class Dmodel : public QtTestModel
1884{
1885 Q_OBJECT
1886public:
1887 using QtTestModel::QtTestModel;
1888 int columnCount(const QModelIndex &parent) const override
1889 {
1890 if (parent.row() == 5)
1891 return 1;
1892 return QtTestModel::columnCount(parent);
1893 }
1894};
1895
1896void tst_QTreeView::headerSections()
1897{
1898 Dmodel model(10, 10);
1899
1900 QTreeView view;
1901 QHeaderView *header = view.header();
1902
1903 view.setModel(&model);
1904 QModelIndex index = model.index(row: 5, column: 0);
1905 view.setRootIndex(index);
1906 QCOMPARE(model.columnCount(QModelIndex()), 10);
1907 QCOMPARE(model.columnCount(index), 1);
1908 QCOMPARE(header->count(), model.columnCount(index));
1909}
1910
1911void tst_QTreeView::moveCursor_data()
1912{
1913 QTest::addColumn<bool>(name: "uniformRowHeights");
1914 QTest::addColumn<bool>(name: "scrollPerPixel");
1915 QTest::newRow(dataTag: "uniformRowHeights = false, scrollPerPixel = false")
1916 << false << false;
1917 QTest::newRow(dataTag: "uniformRowHeights = true, scrollPerPixel = false")
1918 << true << false;
1919 QTest::newRow(dataTag: "uniformRowHeights = false, scrollPerPixel = true")
1920 << false << true;
1921 QTest::newRow(dataTag: "uniformRowHeights = true, scrollPerPixel = true")
1922 << true << true;
1923}
1924
1925void tst_QTreeView::moveCursor()
1926{
1927 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1928 QSKIP("Wayland: This fails. Figure out why.");
1929
1930 QFETCH(bool, uniformRowHeights);
1931 QFETCH(bool, scrollPerPixel);
1932 QtTestModel model(8, 6);
1933
1934 QTreeView view;
1935 view.setUniformRowHeights(uniformRowHeights);
1936 view.setModel(&model);
1937 view.setRowHidden(row: 0, parent: QModelIndex(), hide: true);
1938 view.setVerticalScrollMode(scrollPerPixel ?
1939 QAbstractItemView::ScrollPerPixel :
1940 QAbstractItemView::ScrollPerItem);
1941 QVERIFY(view.isRowHidden(0, QModelIndex()));
1942 view.setColumnHidden(column: 0, hide: true);
1943 QVERIFY(view.isColumnHidden(0));
1944 view.show();
1945 QApplication::setActiveWindow(&view);
1946 QVERIFY(QTest::qWaitForWindowActive(&view));
1947
1948 //here the first visible index should be selected
1949 //because the view got the focus
1950 QModelIndex expected = model.index(row: 1, column: 1, parent: QModelIndex());
1951 QCOMPARE(view.currentIndex(), expected);
1952
1953 //then pressing down should go to the next line
1954 QModelIndex actual = view.moveCursor(cursorAction: QTreeView::MoveDown, modifiers: Qt::NoModifier);
1955 expected = model.index(row: 2, column: 1, parent: QModelIndex());
1956 QCOMPARE(actual, expected);
1957
1958 view.setRowHidden(row: 0, parent: QModelIndex(), hide: false);
1959 view.setColumnHidden(column: 0, hide: false);
1960
1961 // PageUp was broken with uniform row heights turned on
1962 view.setCurrentIndex(model.index(row: 1, column: 0));
1963 actual = view.moveCursor(cursorAction: QTreeView::MovePageUp, modifiers: Qt::NoModifier);
1964 expected = model.index(row: 0, column: 0, parent: QModelIndex());
1965 QCOMPARE(actual, expected);
1966
1967 //let's try another column
1968 view.setCurrentIndex(model.index(row: 1, column: 1));
1969 view.setSelectionBehavior(QAbstractItemView::SelectItems);
1970 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Up);
1971 expected = model.index(row: 0, column: 1, parent: QModelIndex());
1972 QCOMPARE(view.currentIndex(), expected);
1973 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Down);
1974 expected = model.index(row: 1, column: 1, parent: QModelIndex());
1975 QCOMPARE(view.currentIndex(), expected);
1976 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Up);
1977 expected = model.index(row: 0, column: 1, parent: QModelIndex());
1978 QCOMPARE(view.currentIndex(), expected);
1979 view.setColumnHidden(column: 0, hide: true);
1980 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Left);
1981 expected = model.index(row: 0, column: 1, parent: QModelIndex()); //it shouldn't have changed
1982 QCOMPARE(view.currentIndex(), expected);
1983 view.setColumnHidden(column: 0, hide: false);
1984 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Left);
1985 expected = model.index(row: 0, column: 0, parent: QModelIndex());
1986 QCOMPARE(view.currentIndex(), expected);
1987}
1988
1989class TestDelegate : public QStyledItemDelegate
1990{
1991 Q_OBJECT
1992public:
1993 using QStyledItemDelegate::QStyledItemDelegate;
1994 QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override
1995 { return QSize(200, 50); }
1996};
1997
1998typedef QVector<QPoint> PointList;
1999
2000void tst_QTreeView::setSelection_data()
2001{
2002 QTest::addColumn<QRect>(name: "selectionRect");
2003 QTest::addColumn<QAbstractItemView::SelectionMode>(name: "selectionMode");
2004 QTest::addColumn<QItemSelectionModel::SelectionFlags>(name: "selectionCommand");
2005 QTest::addColumn<PointList>(name: "expectedItems");
2006 QTest::addColumn<int>(name: "verticalOffset");
2007
2008 const PointList pl1{QPoint(0, 0), QPoint(1, 0), QPoint(2, 0), QPoint(3, 0), QPoint(4, 0)};
2009 const PointList pl2{QPoint(0, 0), QPoint(1, 0), QPoint(2, 0), QPoint(3, 0), QPoint(4, 0),
2010 QPoint(0, 1), QPoint(1, 1), QPoint(2, 1), QPoint(3, 1), QPoint(4, 1)};
2011 const QItemSelectionModel::SelectionFlags selFlags(QItemSelectionModel::ClearAndSelect |
2012 QItemSelectionModel::Rows);
2013 QTest::newRow(dataTag: "(0,0,50,20),rows")
2014 << QRect(0, 0, 50, 20)
2015 << QAbstractItemView::SingleSelection
2016 << selFlags << pl1 << 0;
2017
2018 QTest::newRow(dataTag: "(0,0,50,90),rows")
2019 << QRect(0, 0, 50, 90)
2020 << QAbstractItemView::ExtendedSelection
2021 << selFlags << pl2 << 0;
2022
2023 QTest::newRow(dataTag: "(50,0,0,90),rows,invalid rect")
2024 << QRect(QPoint(50, 0), QPoint(0, 90))
2025 << QAbstractItemView::ExtendedSelection
2026 << selFlags << pl2 << 0;
2027
2028 QTest::newRow(dataTag: "(0,-20,20,50),rows")
2029 << QRect(0, -20, 20, 50)
2030 << QAbstractItemView::ExtendedSelection
2031 << selFlags << pl2 << 1;
2032 QTest::newRow(dataTag: "(0,-50,20,90),rows")
2033 << QRect(0, -50, 20, 90)
2034 << QAbstractItemView::ExtendedSelection
2035 << selFlags << pl2 << 1;
2036}
2037
2038void tst_QTreeView::setSelection()
2039{
2040 QFETCH(QRect, selectionRect);
2041 QFETCH(QAbstractItemView::SelectionMode, selectionMode);
2042 QFETCH(QItemSelectionModel::SelectionFlags, selectionCommand);
2043 QFETCH(PointList, expectedItems);
2044 QFETCH(int, verticalOffset);
2045
2046 QtTestModel model(10, 5);
2047 model.levels = 1;
2048 model.setDecorationsEnabled(true);
2049 QTreeView view;
2050 view.resize(w: 400, h: 300);
2051 view.show();
2052 view.setRootIsDecorated(false);
2053 view.setItemDelegate(new TestDelegate(&view));
2054 view.setSelectionMode(selectionMode);
2055 view.setModel(&model);
2056 view.setUniformRowHeights(true);
2057 view.setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
2058 view.scrollTo(index: model.index(row: verticalOffset, column: 0), hint: QAbstractItemView::PositionAtTop);
2059 view.setSelection(rect: selectionRect, command: selectionCommand);
2060 QItemSelectionModel *selectionModel = view.selectionModel();
2061 QVERIFY(selectionModel);
2062
2063 const QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
2064#ifdef Q_OS_WINRT
2065 QEXPECT_FAIL("(0,-20,20,50),rows", "Fails on WinRT - QTBUG-68297", Abort);
2066 QEXPECT_FAIL("(0,-50,20,90),rows", "Fails on WinRT - QTBUG-68297", Abort);
2067#endif
2068 QCOMPARE(selectedIndexes.count(), expectedItems.count());
2069 for (const QModelIndex &idx : selectedIndexes)
2070 QVERIFY(expectedItems.contains(QPoint(idx.column(), idx.row())));
2071}
2072
2073void tst_QTreeView::indexAbove()
2074{
2075 QtTestModel model(6, 7);
2076 model.levels = 2;
2077 QTreeView view;
2078
2079 QCOMPARE(view.indexAbove(QModelIndex()), QModelIndex());
2080 view.setModel(&model);
2081 QCOMPARE(view.indexAbove(QModelIndex()), QModelIndex());
2082
2083 QStack<QModelIndex> parents;
2084 parents.push(t: QModelIndex());
2085 while (!parents.isEmpty()) {
2086 QModelIndex p = parents.pop();
2087 if (model.canFetchMore(p))
2088 model.fetchMore(p);
2089 int rows = model.rowCount(parent: p);
2090 for (int r = rows - 1; r > 0; --r) {
2091 for (int column = 0; column < 3; ++column) {
2092 QModelIndex idx = model.index(row: r, column, parent: p);
2093 QModelIndex expected = model.index(row: r - 1, column, parent: p);
2094 QCOMPARE(view.indexAbove(idx), expected);
2095 }
2096 }
2097 // hide even rows
2098 for (int r = 0; r < rows; r+=2)
2099 view.setRowHidden(row: r, parent: p, hide: true);
2100 for (int r = rows - 1; r > 0; r-=2) {
2101 for (int column = 0; column < 3; ++column) {
2102 QModelIndex idx = model.index(row: r, column, parent: p);
2103 QModelIndex expected = model.index(row: r - 2, column, parent: p);
2104 QCOMPARE(view.indexAbove(idx), expected);
2105 }
2106 }
2107// for (int r = 0; r < rows; ++r)
2108// parents.push(model.index(r, 0, p));
2109 }
2110}
2111
2112void tst_QTreeView::indexBelow()
2113{
2114 QtTestModel model(2, 2);
2115
2116 QTreeView view;
2117 view.setModel(&model);
2118 view.show();
2119
2120 {
2121 QModelIndex i = model.index(row: 0, column: 0, parent: view.rootIndex());
2122 QVERIFY(i.isValid());
2123 QCOMPARE(i.row(), 0);
2124 QCOMPARE(i.column(), 0);
2125
2126 i = view.indexBelow(index: i);
2127 QVERIFY(i.isValid());
2128 QCOMPARE(i.row(), 1);
2129 QCOMPARE(i.column(), 0);
2130 i = view.indexBelow(index: i);
2131 QVERIFY(!i.isValid());
2132 }
2133
2134 {
2135 QModelIndex i = model.index(row: 0, column: 1, parent: view.rootIndex());
2136 QVERIFY(i.isValid());
2137 QCOMPARE(i.row(), 0);
2138 QCOMPARE(i.column(), 1);
2139
2140 i = view.indexBelow(index: i);
2141 QVERIFY(i.isValid());
2142 QCOMPARE(i.row(), 1);
2143 QCOMPARE(i.column(), 1);
2144 i = view.indexBelow(index: i);
2145 QVERIFY(!i.isValid());
2146 }
2147}
2148
2149void tst_QTreeView::clicked()
2150{
2151 QtTestModel model(10, 2);
2152
2153 QTreeView view;
2154 view.setModel(&model);
2155 view.show();
2156
2157 QVERIFY(QTest::qWaitForWindowExposed(&view));
2158
2159 QModelIndex firstIndex = model.index(row: 0, column: 0, parent: QModelIndex());
2160 QVERIFY(firstIndex.isValid());
2161 int itemHeight = view.visualRect(index: firstIndex).height();
2162 int itemOffset = view.visualRect(index: firstIndex).width() / 2;
2163 view.resize(w: 200, h: itemHeight * (model.rows + 2));
2164
2165 for (int i = 0; i < model.rowCount(); ++i) {
2166 QPoint p(itemOffset, 1 + itemHeight * i);
2167 QModelIndex index = view.indexAt(p);
2168 if (!index.isValid())
2169 continue;
2170 QSignalSpy spy(&view, &QTreeView::clicked);
2171 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2172 QTRY_COMPARE(spy.count(), 1);
2173 }
2174}
2175
2176void tst_QTreeView::mouseDoubleClick()
2177{
2178 // Test double clicks outside the viewport.
2179 // (Should be a no-op and should not expand any item.)
2180
2181 QStandardItemModel model(20, 2);
2182 for (int i = 0; i < model.rowCount(); i++) {
2183 QModelIndex index = model.index(row: i, column: 0, parent: QModelIndex());
2184 model.insertRows(row: 0, count: 20, parent: index);
2185 model.insertColumns(column: 0, count: 2,parent: index);
2186 for (int i1 = 0; i1 < model.rowCount(parent: index); i1++) {
2187 QVERIFY(model.index(i1, 0, index).isValid());
2188 }
2189 }
2190
2191 QTreeView view;
2192 view.setModel(&model);
2193
2194 // make sure the viewport height is smaller than the contents height.
2195 view.resize(w: 200, h: 200);
2196 view.move(ax: 0, ay: 0);
2197 view.show();
2198 QModelIndex index = model.index(row: 0, column: 0, parent: QModelIndex());
2199 view.setCurrentIndex(index);
2200
2201 view.setExpanded(index: model.index(row: 0,column: 0, parent: QModelIndex()), expand: true);
2202 view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
2203 view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
2204
2205 // Make sure all items are collapsed
2206 for (int i = 0; i < model.rowCount(parent: QModelIndex()); i++)
2207 view.setExpanded(index: model.index(row: i, column: 0, parent: QModelIndex()), expand: false);
2208
2209 int maximum = view.verticalScrollBar()->maximum();
2210
2211 // Doubleclick in the bottom right corner, in the unused area between the vertical and horizontal scrollbar.
2212 int vsw = view.verticalScrollBar()->width();
2213 int hsh = view.horizontalScrollBar()->height();
2214 QTest::mouseDClick(widget: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(view.width() - vsw + 1, view.height() - hsh + 1));
2215 // Should not have expanded, thus maximum() should have the same value.
2216 QCOMPARE(maximum, view.verticalScrollBar()->maximum());
2217
2218 view.setExpandsOnDoubleClick(false);
2219 QTest::mouseDClick(widget: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: view.visualRect(index).center());
2220 QVERIFY(!view.isExpanded(index));
2221}
2222
2223void tst_QTreeView::rowsAboutToBeRemoved()
2224{
2225 QStandardItemModel model(3, 1);
2226 for (int i = 0; i < model.rowCount(); i++) {
2227 const QString iS = QString::number(i);
2228 QModelIndex index = model.index(row: i, column: 0, parent: QModelIndex());
2229 model.setData(index, value: iS);
2230 model.insertRows(row: 0, count: 4, parent: index);
2231 model.insertColumns(column: 0,count: 1,parent: index);
2232 for (int i1 = 0; i1 < model.rowCount(parent: index); i1++) {
2233 QModelIndex index2 = model.index(row: i1, column: 0, parent: index);
2234 model.setData(index: index2, value: iS + QString::number(i1));
2235 }
2236 }
2237
2238 QTreeView view;
2239 view.setModel(&model);
2240 view.show();
2241 QModelIndex index = model.index(row: 0,column: 0, parent: QModelIndex());
2242 view.setCurrentIndex(index);
2243 view.setExpanded(index: model.index(row: 0,column: 0, parent: QModelIndex()), expand: true);
2244
2245 for (int i = 0; i < model.rowCount(parent: QModelIndex()); i++)
2246 view.setExpanded(index: model.index(row: i, column: 0, parent: QModelIndex()), expand: true);
2247
2248 QSignalSpy spy1(&model, &QAbstractItemModel::rowsAboutToBeRemoved);
2249
2250 model.removeRows(row: 1,count: 1);
2251 QCOMPARE((view.state()), 0);
2252 // Should not be 5 (or any other number for that sake :)
2253 QCOMPARE(spy1.count(), 1);
2254
2255}
2256
2257void tst_QTreeView::headerSections_unhideSection()
2258{
2259 QtTestModel model(10, 7);
2260
2261 QTreeView view;
2262
2263 view.setModel(&model);
2264 view.show();
2265 int size = view.header()->sectionSize(logicalIndex: 0);
2266 view.setColumnHidden(column: 0, hide: true);
2267
2268 // should go back to old size
2269 view.setColumnHidden(column: 0, hide: false);
2270 QCOMPARE(size, view.header()->sectionSize(0));
2271}
2272
2273void tst_QTreeView::columnAt()
2274{
2275 QtTestModel model(10, 10);
2276 QTreeView view;
2277 view.resize(w: 500,h: 500);
2278 view.setModel(&model);
2279
2280 QCOMPARE(view.columnAt(0), 0);
2281 // really this is testing the header... so not much more should be needed if the header is working...
2282}
2283
2284void tst_QTreeView::scrollTo()
2285{
2286#define CHECK_VISIBLE(ROW,COL) QVERIFY(QRect(QPoint(),view.viewport()->size()).contains(\
2287 view.visualRect(model.index((ROW),(COL),QModelIndex()))))
2288
2289 QtTestModel model(100, 100);
2290 QTreeView view;
2291 view.setUniformRowHeights(true);
2292 view.scrollTo(index: QModelIndex(), hint: QTreeView::PositionAtTop);
2293 view.setModel(&model);
2294
2295 // ### check the scrollbar values an make sure it actually scrolls to the item
2296 // ### check for bot item based and pixel based scrolling
2297 // ### create a data function for this test
2298
2299 view.scrollTo(index: QModelIndex());
2300 view.scrollTo(index: model.index(row: 0, column: 0, parent: QModelIndex()));
2301 view.scrollTo(index: model.index(row: 0, column: 0, parent: QModelIndex()), hint: QTreeView::PositionAtTop);
2302 view.scrollTo(index: model.index(row: 0, column: 0, parent: QModelIndex()), hint: QTreeView::PositionAtBottom);
2303
2304 view.show();
2305 view.setVerticalScrollMode(QAbstractItemView::ScrollPerItem); //some styles change that in Polish
2306 view.resize(w: 300, h: 200);
2307
2308 QVERIFY(QTest::qWaitForWindowExposed(&view));
2309 //view.verticalScrollBar()->setValue(0);
2310
2311 view.scrollTo(index: model.index(row: 0, column: 0, parent: QModelIndex()));
2312 CHECK_VISIBLE(0,0);
2313 QCOMPARE(view.verticalScrollBar()->value(), 0);
2314
2315 view.header()->resizeSection(logicalIndex: 0, size: 5); // now we only see the branches
2316 view.scrollTo(index: model.index(row: 5, column: 0, parent: QModelIndex()), hint: QTreeView::PositionAtTop);
2317 QCOMPARE(view.verticalScrollBar()->value(), 5);
2318
2319 view.scrollTo(index: model.index(row: 60, column: 60, parent: QModelIndex()));
2320
2321 CHECK_VISIBLE(60,60);
2322 view.scrollTo(index: model.index(row: 60, column: 30, parent: QModelIndex()));
2323 CHECK_VISIBLE(60,30);
2324 view.scrollTo(index: model.index(row: 30, column: 30, parent: QModelIndex()));
2325 CHECK_VISIBLE(30,30);
2326
2327 // TODO force it to move to the left and then the right
2328}
2329
2330void tst_QTreeView::rowsAboutToBeRemoved_move()
2331{
2332 QStandardItemModel model(3,1);
2333 QTreeView view;
2334 view.setModel(&model);
2335 QModelIndex indexThatWantsToLiveButWillDieDieITellYou;
2336 QModelIndex parent = model.index(row: 2, column: 0);
2337 view.expand(index: parent);
2338 for (int i = 0; i < 6; ++i) {
2339 model.insertRows(row: 0, count: 1, parent);
2340 model.insertColumns(column: 0, count: 1, parent);
2341 QModelIndex index = model.index(row: 0, column: 0, parent);
2342 view.expand(index);
2343 if (i == 3)
2344 indexThatWantsToLiveButWillDieDieITellYou = index;
2345 model.setData(index, value: i);
2346 parent = index;
2347 }
2348 view.resize(w: 600,h: 800);
2349 view.show();
2350 QVERIFY(QTest::qWaitForWindowExposed(&view));
2351 view.doItemsLayout();
2352 view.executeDelayedItemsLayout();
2353 parent = indexThatWantsToLiveButWillDieDieITellYou.parent();
2354 QCOMPARE(view.isExpanded(indexThatWantsToLiveButWillDieDieITellYou), true);
2355 QCOMPARE(parent.isValid(), true);
2356 QCOMPARE(parent.parent().isValid(), true);
2357 view.expand(index: parent);
2358 QCOMPARE(view.isExpanded(parent), true);
2359 QCOMPARE(view.isExpanded(indexThatWantsToLiveButWillDieDieITellYou), true);
2360 model.removeRow(arow: 0, aparent: indexThatWantsToLiveButWillDieDieITellYou);
2361 QCOMPARE(view.isExpanded(parent), true);
2362 QCOMPARE(view.isExpanded(indexThatWantsToLiveButWillDieDieITellYou), true);
2363}
2364
2365void tst_QTreeView::resizeColumnToContents()
2366{
2367 QStandardItemModel model(50,2);
2368 for (int r = 0; r < model.rowCount(); ++r) {
2369 const QString rS = QString::number(r);
2370 for (int c = 0; c < model.columnCount(); ++c) {
2371 QModelIndex idx = model.index(row: r, column: c);
2372 model.setData(index: idx, value: rS + QLatin1Char(',') + QString::number(c));
2373 model.insertColumns(column: 0, count: 2, parent: idx);
2374 model.insertRows(row: 0, count: 6, parent: idx);
2375 for (int i = 0; i < 6; ++i) {
2376 const QString iS = QString::number(i);
2377 for (int j = 0; j < 2 ; ++j) {
2378 model.setData(index: model.index(row: i, column: j, parent: idx), value: QLatin1String("child") + iS + QString::number(j));
2379 }
2380 }
2381 }
2382 }
2383 QTreeView view;
2384 view.setModel(&model);
2385 view.show();
2386 QVERIFY(QTest::qWaitForWindowExposed(&view));
2387
2388 view.scrollToBottom();
2389 view.resizeColumnToContents(column: 0);
2390 int oldColumnSize = view.header()->sectionSize(logicalIndex: 0);
2391 view.setRootIndex(model.index(row: 0, column: 0));
2392 view.resizeColumnToContents(column: 0); //Earlier, this gave an assert
2393 QVERIFY(view.header()->sectionSize(0) > oldColumnSize);
2394}
2395
2396void tst_QTreeView::insertAfterSelect()
2397{
2398 QtTestModel model(10, 10);
2399 QTreeView view;
2400 view.setModel(&model);
2401 view.show();
2402 QVERIFY(QTest::qWaitForWindowExposed(&view));
2403
2404 QModelIndex firstIndex = model.index(row: 0, column: 0, parent: QModelIndex());
2405 QVERIFY(firstIndex.isValid());
2406 int itemOffset = view.visualRect(index: firstIndex).width() / 2;
2407 QPoint p(itemOffset, 1);
2408 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2409 QVERIFY(view.selectionModel()->isSelected(firstIndex));
2410 model.insertNewRow();
2411 QVERIFY(view.selectionModel()->isSelected(firstIndex)); // Should still be selected
2412}
2413
2414void tst_QTreeView::removeAfterSelect()
2415{
2416 QtTestModel model(10, 10);
2417 QTreeView view;
2418 view.setModel(&model);
2419 view.show();
2420 QVERIFY(QTest::qWaitForWindowExposed(&view));
2421
2422 QModelIndex firstIndex = model.index(row: 0, column: 0, parent: QModelIndex());
2423 QVERIFY(firstIndex.isValid());
2424 int itemOffset = view.visualRect(index: firstIndex).width() / 2;
2425 QPoint p(itemOffset, 1);
2426 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2427 QVERIFY(view.selectionModel()->isSelected(firstIndex));
2428 model.removeLastRow();
2429 QVERIFY(view.selectionModel()->isSelected(firstIndex)); // Should still be selected
2430}
2431
2432void tst_QTreeView::hiddenItems()
2433{
2434 QtTestModel model(10, 10);
2435 QTreeView view;
2436 view.setModel(&model);
2437 view.show();
2438 QVERIFY(QTest::qWaitForWindowExposed(&view));
2439
2440 QModelIndex firstIndex = model.index(row: 1, column: 0, parent: QModelIndex());
2441 QVERIFY(firstIndex.isValid());
2442 if (model.canFetchMore(firstIndex))
2443 model.fetchMore(firstIndex);
2444 for (int i = 0; i < model.rowCount(parent: firstIndex); i++)
2445 view.setRowHidden(row: i , parent: firstIndex, hide: true );
2446
2447 int itemOffset = view.visualRect(index: firstIndex).width() / 2;
2448 int itemHeight = view.visualRect(index: firstIndex).height();
2449 QPoint p(itemOffset, itemHeight + 1);
2450 view.setExpanded(index: firstIndex, expand: false);
2451 QTest::mouseDClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2452 QCOMPARE(view.isExpanded(firstIndex), false);
2453
2454 p.setX(5);
2455 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2456 QCOMPARE(view.isExpanded(firstIndex), false);
2457}
2458
2459void tst_QTreeView::spanningItems()
2460{
2461 QtTestModel model(10, 10);
2462 model.onlyValidCalls = true;
2463 QTreeView view;
2464 view.setModel(&model);
2465 view.show();
2466 QVERIFY(QTest::qWaitForWindowExposed(&view));
2467
2468 int itemWidth = view.header()->sectionSize(logicalIndex: 0);
2469 int itemHeight = view.visualRect(index: model.index(row: 0, column: 0, parent: QModelIndex())).height();
2470
2471 // every second row is spanning
2472 for (int i = 1; i < model.rowCount(parent: QModelIndex()); i += 2)
2473 view.setFirstColumnSpanned(row: i , parent: QModelIndex(), span: true);
2474
2475 // non-spanning item
2476 QPoint p(itemWidth / 2, itemHeight / 2); // column 0, row 0
2477 view.setCurrentIndex(QModelIndex());
2478 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2479 QCOMPARE(view.currentIndex(), model.index(0, 0, QModelIndex()));
2480 QCOMPARE(view.header()->sectionSize(0) - view.indentation(),
2481 view.visualRect(model.index(0, 0, QModelIndex())).width());
2482
2483 // spanning item
2484 p.setX(itemWidth + (itemWidth / 2)); // column 1
2485 p.setY(itemHeight + (itemHeight / 2)); // row 1
2486 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
2487 QCOMPARE(view.currentIndex(), model.index(1, 0, QModelIndex()));
2488 QCOMPARE(view.header()->length() - view.indentation(),
2489 view.visualRect(model.index(1, 0, QModelIndex())).width());
2490
2491 // size hint
2492 // every second row is un-spanned
2493 QStyleOptionViewItem option = view.viewOptions();
2494 int w = view.header()->sectionSizeHint(logicalIndex: 0);
2495 for (int i = 0; i < model.rowCount(parent: QModelIndex()); ++i) {
2496 if (!view.isFirstColumnSpanned(row: i, parent: QModelIndex())) {
2497 QModelIndex index = model.index(row: i, column: 0, parent: QModelIndex());
2498 w = qMax(a: w, b: view.itemDelegate(index)->sizeHint(option, index).width() + view.indentation());
2499 }
2500 }
2501 QCOMPARE(view.sizeHintForColumn(0), w);
2502
2503 view.repaint(); // to check that this doesn't hit any assert
2504}
2505
2506void tst_QTreeView::selectionOrderTest()
2507{
2508 QVERIFY(static_cast<QItemSelectionModel*>(sender())->currentIndex().row() != -1);
2509}
2510
2511void tst_QTreeView::selection()
2512{
2513 if (!QGuiApplication::platformName().compare(other: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
2514 QSKIP("Wayland: This causes a crash triggered by setVisible(false)");
2515
2516 QTreeView treeView;
2517 QStandardItemModel m(10, 2);
2518 for (int i = 0;i < 10; ++i)
2519 m.setData(index: m.index(row: i, column: 0), value: i);
2520 treeView.setModel(&m);
2521 treeView.show();
2522 QVERIFY(QTest::qWaitForWindowExposed(&treeView));
2523
2524 treeView.setSelectionBehavior(QAbstractItemView::SelectRows);
2525 treeView.setSelectionMode(QAbstractItemView::ExtendedSelection);
2526
2527 connect(sender: treeView.selectionModel(), signal: &QItemSelectionModel::selectionChanged,
2528 receiver: this, slot: &tst_QTreeView::selectionOrderTest);
2529
2530 QTest::mousePress(widget: treeView.viewport(), button: Qt::LeftButton, stateKey: {},
2531 pos: treeView.visualRect(index: m.index(row: 1, column: 0)).center());
2532 QTest::keyPress(widget: treeView.viewport(), key: Qt::Key_Down);
2533 auto selectedRows = treeView.selectionModel()->selectedRows();
2534 QCOMPARE(selectedRows.size(), 1);
2535 QCOMPARE(selectedRows.first(), m.index(2, 0, QModelIndex()));
2536 QTest::keyPress(widget: treeView.viewport(), key: Qt::Key_5);
2537 selectedRows = treeView.selectionModel()->selectedRows();
2538 QCOMPARE(selectedRows.size(), 1);
2539 QCOMPARE(selectedRows.first(), m.index(5, 0, QModelIndex()));
2540}
2541
2542//From task 151686 QTreeView ExtendedSelection selects hidden rows
2543void tst_QTreeView::selectionWithHiddenItems()
2544{
2545 QStandardItemModel model;
2546
2547 QStandardItem item0("row 0");
2548 QStandardItem item1("row 1");
2549 QStandardItem item2("row 2");
2550 QStandardItem item3("row 3");
2551 model.appendColumn(items: {&item0, &item1, &item2, &item3});
2552
2553 QStandardItem child("child");
2554 item1.appendRow(aitem: &child);
2555
2556 QTreeView view;
2557 view.setModel(&model);
2558 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
2559 view.show();
2560 QVERIFY(QTest::qWaitForWindowExposed(&view));
2561
2562 //child should not be selected as it is hidden (its parent is not expanded)
2563 view.selectAll();
2564 QCOMPARE(view.selectionModel()->selection().count(), 1); //one range
2565 QCOMPARE(view.selectionModel()->selectedRows().count(), 4);
2566 view.expandAll();
2567 QVERIFY(view.isExpanded(item1.index()));
2568 QCOMPARE(view.selectionModel()->selection().count(), 1);
2569 QCOMPARE(view.selectionModel()->selectedRows().count(), 4);
2570 QVERIFY( !view.selectionModel()->isSelected(model.indexFromItem(&child)));
2571 view.clearSelection();
2572 QVERIFY(view.isExpanded(item1.index()));
2573
2574 //child should be selected as it is visible (its parent is expanded)
2575 view.selectAll();
2576 QCOMPARE(view.selectionModel()->selection().count(), 2);
2577 QCOMPARE(view.selectionModel()->selectedRows().count(), 5); //everything is selected
2578 view.clearSelection();
2579
2580 //we hide the node with a child (there should then be 3 items selected in 2 ranges)
2581 view.setRowHidden(row: 1, parent: QModelIndex(), hide: true);
2582 QVERIFY(view.isExpanded(item1.index()));
2583 view.selectAll();
2584 QCOMPARE(view.selectionModel()->selection().count(), 2);
2585 QCOMPARE(view.selectionModel()->selectedRows().count(), 3);
2586 QVERIFY(!view.selectionModel()->isSelected(model.indexFromItem(&item1)));
2587 QVERIFY(!view.selectionModel()->isSelected(model.indexFromItem(&child)));
2588
2589 view.setRowHidden(row: 1, parent: QModelIndex(), hide: false);
2590 QVERIFY(view.isExpanded(item1.index()));
2591 view.clearSelection();
2592
2593 //we hide a node without children (there should then be 4 items selected in 3 ranges)
2594 view.setRowHidden(row: 2, parent: QModelIndex(), hide: true);
2595 QVERIFY(view.isExpanded(item1.index()));
2596 view.selectAll();
2597 QVERIFY(view.isExpanded(item1.index()));
2598 QCOMPARE(view.selectionModel()->selection().count(), 3);
2599 QCOMPARE(view.selectionModel()->selectedRows().count(), 4);
2600 QVERIFY( !view.selectionModel()->isSelected(model.indexFromItem(&item2)));
2601 view.setRowHidden(row: 2, parent: QModelIndex(), hide: false);
2602 QVERIFY(view.isExpanded(item1.index()));
2603 view.clearSelection();
2604}
2605
2606void tst_QTreeView::selectAll()
2607{
2608 QStandardItemModel model(4, 4);
2609 QTreeView view2;
2610 view2.setModel(&model);
2611 view2.setSelectionMode(QAbstractItemView::ExtendedSelection);
2612 view2.selectAll(); // Should work with an empty model
2613 //everything should be selected since we are in ExtendedSelection mode
2614 QCOMPARE(view2.selectedIndexes().count(), model.rowCount() * model.columnCount());
2615
2616 for (int i = 0; i < model.rowCount(); ++i)
2617 model.setData(index: model.index(row: i,column: 0), value: QLatin1String("row ") + QString::number(i));
2618 QTreeView view;
2619 view.setModel(&model);
2620 int selectedCount = view.selectedIndexes().count();
2621 view.selectAll();
2622 QCOMPARE(view.selectedIndexes().count(), selectedCount);
2623
2624 QTreeView view3;
2625 view3.setModel(&model);
2626 view3.setSelectionMode(QAbstractItemView::NoSelection);
2627 view3.selectAll();
2628 QCOMPARE(view3.selectedIndexes().count(), 0);
2629}
2630
2631void tst_QTreeView::extendedSelection_data()
2632{
2633 QTest::addColumn<QPoint>(name: "mousePressPos");
2634 QTest::addColumn<int>(name: "selectedCount");
2635
2636 QTest::newRow(dataTag: "select") << QPoint(10, 10) << 2;
2637 QTest::newRow(dataTag: "unselect") << QPoint(10, 300) << 0;
2638}
2639
2640void tst_QTreeView::extendedSelection()
2641{
2642 QFETCH(QPoint, mousePressPos);
2643 QFETCH(int, selectedCount);
2644
2645 QStandardItemModel model(5, 2);
2646 QWidget topLevel;
2647 QTreeView view(&topLevel);
2648 view.resize(w: qMax(a: mousePressPos.x() * 2, b: 300), h: qMax(a: mousePressPos.y() * 2, b: 350));
2649 view.setModel(&model);
2650 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
2651 topLevel.show();
2652 QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
2653 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: mousePressPos);
2654 QCOMPARE(view.selectionModel()->selectedIndexes().count(), selectedCount);
2655}
2656
2657void tst_QTreeView::rowSizeHint()
2658{
2659 //tests whether the correct visible columns are taken into account when
2660 //calculating the height of a line
2661 QStandardItemModel model(1, 3);
2662 model.setData(index: model.index(row: 0, column: 0), value: QSize(20, 40), role: Qt::SizeHintRole);
2663 model.setData(index: model.index(row: 0, column: 1), value: QSize(20, 10), role: Qt::SizeHintRole);
2664 model.setData(index: model.index(row: 0, column: 2), value: QSize(20, 10), role: Qt::SizeHintRole);
2665 QTreeView view;
2666 view.setModel(&model);
2667
2668 view.header()->moveSection(from: 1, to: 0); //the 2nd column goes to the 1st place
2669
2670 view.show();
2671 QVERIFY(QTest::qWaitForWindowExposed(&view));
2672
2673 //it must be 40 since the tallest item that defines the height of a line
2674 QCOMPARE(view.visualRect(model.index(0,0)).height(), 40);
2675 QCOMPARE(view.visualRect(model.index(0,1)).height(), 40);
2676 QCOMPARE(view.visualRect(model.index(0,2)).height(), 40);
2677}
2678
2679
2680//From task 155449 (QTreeWidget has a large width for the first section when sorting
2681//is turned on before items are added)
2682
2683void tst_QTreeView::setSortingEnabledTopLevel()
2684{
2685 QTreeView view;
2686 QStandardItemModel model(1, 1);
2687 view.setModel(&model);
2688 const int size = view.header()->sectionSize(logicalIndex: 0);
2689 view.setSortingEnabled(true);
2690 model.setColumnCount(3);
2691 //we test that changing the column count doesn't change the 1st column size
2692 QCOMPARE(view.header()->sectionSize(0), size);
2693}
2694
2695void tst_QTreeView::setSortingEnabledChild()
2696{
2697 QMainWindow win;
2698 QTreeView view;
2699 // two columns to not get in trouble with stretchLastSection
2700 QStandardItemModel model(1, 2);
2701 view.setModel(&model);
2702 view.header()->setDefaultSectionSize(92);
2703 win.setCentralWidget(&view);
2704 const int size = view.header()->sectionSize(logicalIndex: 0);
2705 view.setSortingEnabled(true);
2706 model.setColumnCount(3);
2707 //we test that changing the column count doesn't change the 1st column size
2708 QCOMPARE(view.header()->sectionSize(0), size);
2709}
2710
2711void tst_QTreeView::headerHidden()
2712{
2713 QTreeView view;
2714 QStandardItemModel model;
2715 view.setModel(&model);
2716 QCOMPARE(view.isHeaderHidden(), false);
2717 QCOMPARE(view.header()->isHidden(), false);
2718 view.setHeaderHidden(true);
2719 QCOMPARE(view.isHeaderHidden(), true);
2720 QCOMPARE(view.header()->isHidden(), true);
2721}
2722
2723class TestTreeViewStyle : public QProxyStyle
2724{
2725 Q_OBJECT
2726public:
2727 using QProxyStyle::QProxyStyle;
2728 int pixelMetric(PixelMetric metric, const QStyleOption *option = nullptr,
2729 const QWidget *widget = nullptr) const override
2730 {
2731 if (metric == QStyle::PM_TreeViewIndentation)
2732 return indentation;
2733 else
2734 return QProxyStyle::pixelMetric(metric, option, widget);
2735 }
2736 int indentation = 20;
2737};
2738
2739void tst_QTreeView::indentation()
2740{
2741 TestTreeViewStyle style1;
2742 TestTreeViewStyle style2;
2743 style1.indentation = 20;
2744 style2.indentation = 30;
2745
2746 QTreeView view;
2747 view.setStyle(&style1);
2748 QCOMPARE(view.indentation(), style1.indentation);
2749 view.setStyle(&style2);
2750 QCOMPARE(view.indentation(), style2.indentation);
2751 view.setIndentation(70);
2752 QCOMPARE(view.indentation(), 70);
2753 view.setStyle(&style1);
2754 QCOMPARE(view.indentation(), 70);
2755 view.resetIndentation();
2756 QCOMPARE(view.indentation(), style1.indentation);
2757 view.setStyle(&style2);
2758 QCOMPARE(view.indentation(), style2.indentation);
2759}
2760
2761// From Task 145199 (crash when column 0 having at least one expanded item is removed and then
2762// inserted). The test passes simply if it doesn't crash, hence there are no calls
2763// to QCOMPARE() or QVERIFY().
2764void tst_QTreeView::removeAndInsertExpandedCol0()
2765{
2766 QTreeView view;
2767 QStandardItemModel model;
2768 view.setModel(&model);
2769
2770 model.setColumnCount(1);
2771
2772 QStandardItem *item0 = new QStandardItem(QString("item 0"));
2773 model.invisibleRootItem()->appendRow(aitem: item0);
2774 view.expand(index: item0->index());
2775 QStandardItem *item1 = new QStandardItem(QString("item 1"));
2776 item0->appendRow(aitem: item1);
2777
2778 model.removeColumns(column: 0, count: 1);
2779 model.insertColumns(column: 0, count: 1);
2780
2781 view.show();
2782 QVERIFY(QTest::qWaitForWindowExposed(&view));
2783}
2784
2785void tst_QTreeView::disabledButCheckable()
2786{
2787 QTreeView view;
2788 QStandardItemModel model;
2789 QStandardItem *item;
2790 item = new QStandardItem(QLatin1String("Row 1 Item"));
2791 model.insertRow(arow: 0, aitem: item);
2792
2793 item = new QStandardItem(QLatin1String("Row 2 Item"));
2794 item->setCheckable(true);
2795 item->setEnabled(false);
2796 model.insertRow(arow: 1, aitem: item);
2797
2798 view.setModel(&model);
2799 view.setCurrentIndex(model.index(row: 1,column: 0));
2800 QCOMPARE(item->checkState(), Qt::Unchecked);
2801 view.show();
2802
2803 QTest::keyClick(widget: &view, key: Qt::Key_Space);
2804 QCOMPARE(item->checkState(), Qt::Unchecked);
2805}
2806
2807void tst_QTreeView::sortByColumn_data()
2808{
2809 QTest::addColumn<bool>(name: "sortingEnabled");
2810 QTest::newRow(dataTag: "sorting enabled") << true;
2811 QTest::newRow(dataTag: "sorting disabled") << false;
2812}
2813
2814// Checks sorting and that sortByColumn also sets the sortIndicator
2815void tst_QTreeView::sortByColumn()
2816{
2817 QFETCH(bool, sortingEnabled);
2818 QTreeView view;
2819 QStandardItemModel model(4, 2);
2820 QSortFilterProxyModel sfpm; // default QStandardItemModel does not support 'unsorted' state
2821 sfpm.setSourceModel(&model);
2822 model.setItem(row: 0, column: 0, item: new QStandardItem("b"));
2823 model.setItem(row: 1, column: 0, item: new QStandardItem("d"));
2824 model.setItem(row: 2, column: 0, item: new QStandardItem("c"));
2825 model.setItem(row: 3, column: 0, item: new QStandardItem("a"));
2826 model.setItem(row: 0, column: 1, item: new QStandardItem("e"));
2827 model.setItem(row: 1, column: 1, item: new QStandardItem("g"));
2828 model.setItem(row: 2, column: 1, item: new QStandardItem("h"));
2829 model.setItem(row: 3, column: 1, item: new QStandardItem("f"));
2830
2831 view.setSortingEnabled(sortingEnabled);
2832 view.setModel(&sfpm);
2833
2834 view.sortByColumn(column: 1, order: Qt::DescendingOrder);
2835 QCOMPARE(view.header()->sortIndicatorSection(), 1);
2836 QCOMPARE(view.model()->data(view.model()->index(0, 0)).toString(), QString::fromLatin1("c"));
2837 QCOMPARE(view.model()->data(view.model()->index(1, 0)).toString(), QString::fromLatin1("d"));
2838 QCOMPARE(view.model()->data(view.model()->index(0, 1)).toString(), QString::fromLatin1("h"));
2839 QCOMPARE(view.model()->data(view.model()->index(1, 1)).toString(), QString::fromLatin1("g"));
2840
2841 view.sortByColumn(column: 0, order: Qt::AscendingOrder);
2842 QCOMPARE(view.header()->sortIndicatorSection(), 0);
2843 QCOMPARE(view.model()->data(view.model()->index(0, 0)).toString(), QString::fromLatin1("a"));
2844 QCOMPARE(view.model()->data(view.model()->index(1, 0)).toString(), QString::fromLatin1("b"));
2845 QCOMPARE(view.model()->data(view.model()->index(0, 1)).toString(), QString::fromLatin1("f"));
2846 QCOMPARE(view.model()->data(view.model()->index(1, 1)).toString(), QString::fromLatin1("e"));
2847
2848 view.sortByColumn(column: -1, order: Qt::AscendingOrder);
2849 QCOMPARE(view.header()->sortIndicatorSection(), -1);
2850 QCOMPARE(view.model()->data(view.model()->index(0, 0)).toString(), QString::fromLatin1("b"));
2851 QCOMPARE(view.model()->data(view.model()->index(1, 0)).toString(), QString::fromLatin1("d"));
2852 QCOMPARE(view.model()->data(view.model()->index(0, 1)).toString(), QString::fromLatin1("e"));
2853 QCOMPARE(view.model()->data(view.model()->index(1, 1)).toString(), QString::fromLatin1("g"));
2854
2855 // a new 'sortByColumn()' should do a re-sort (e.g. due to the data changed), QTBUG-86268
2856 view.setModel(&model);
2857 view.sortByColumn(column: 0, order: Qt::AscendingOrder);
2858 QCOMPARE(view.model()->data(view.model()->index(0, 0)).toString(), QString::fromLatin1("a"));
2859 model.setItem(row: 0, column: 0, item: new QStandardItem("x"));
2860 view.sortByColumn(column: 0, order: Qt::AscendingOrder);
2861 QCOMPARE(view.model()->data(view.model()->index(0, 0)).toString(), QString::fromLatin1("b"));
2862}
2863
2864/*
2865 This is a model that every time kill() is called it will completely change
2866 all of its nodes for new nodes. It then qFatal's if you later use a dead node.
2867 */
2868class EvilModel: public QAbstractItemModel
2869{
2870 Q_OBJECT
2871public:
2872 class Node
2873 {
2874 public:
2875 Node(Node *p = nullptr, int level = 0) : parent(p)
2876 {
2877 populate(level);
2878 }
2879 ~Node()
2880 {
2881 qDeleteAll(begin: children.begin(), end: children.end());
2882 qDeleteAll(begin: deadChildren.begin(), end: deadChildren.end());
2883 }
2884
2885 void populate(int level = 0)
2886 {
2887 if (level < 4) {
2888 for (int i = 0; i < 5; ++i)
2889 children.append(t: new Node(this, level + 1));
2890 }
2891 }
2892 void kill()
2893 {
2894 for (int i = children.count() -1; i >= 0; --i) {
2895 children.at(i)->kill();
2896 if (parent == nullptr) {
2897 deadChildren.append(t: children.at(i));
2898 children.removeAt(i);
2899 }
2900 }
2901 if (parent == nullptr) {
2902 if (!children.isEmpty())
2903 qFatal(msg: "%s: children should be empty when parent is null", Q_FUNC_INFO);
2904 populate();
2905 } else {
2906 isDead = true;
2907 }
2908 }
2909
2910 QVector<Node *> children;
2911 QVector<Node *> deadChildren;
2912 Node *parent;
2913 bool isDead = false;
2914 };
2915
2916 Node *root;
2917 bool crash = false;
2918
2919 EvilModel(QObject *parent = nullptr): QAbstractItemModel(parent), root(new Node)
2920 {}
2921 ~EvilModel()
2922 {
2923 delete root;
2924 }
2925
2926 void setCrash()
2927 {
2928 crash = true;
2929 }
2930
2931 void change()
2932 {
2933 emit layoutAboutToBeChanged();
2934 QModelIndexList oldList = persistentIndexList();
2935 QVector<QStack<int>> oldListPath;
2936 for (int i = 0; i < oldList.count(); ++i) {
2937 QModelIndex idx = oldList.at(i);
2938 QStack<int> path;
2939 while (idx.isValid()) {
2940 path.push(t: idx.row());
2941 idx = idx.parent();
2942 }
2943 oldListPath.append(t: path);
2944 }
2945 root->kill();
2946
2947 QModelIndexList newList;
2948 for (auto path : qAsConst(t&: oldListPath)) {
2949 QModelIndex idx;
2950 while (!path.isEmpty())
2951 idx = index(row: path.pop(), column: 0, parent: idx);
2952 newList.append(t: idx);
2953 }
2954
2955 changePersistentIndexList(from: oldList, to: newList);
2956 emit layoutChanged();
2957 }
2958
2959 int rowCount(const QModelIndex &parent = QModelIndex()) const override
2960 {
2961 Node *parentNode = root;
2962 if (parent.isValid()) {
2963 parentNode = static_cast<Node*>(parent.internalPointer());
2964 if (parentNode->isDead)
2965 qFatal(msg: "%s: parentNode is dead!", Q_FUNC_INFO);
2966 }
2967 return parentNode->children.count();
2968 }
2969 int columnCount(const QModelIndex &parent = QModelIndex()) const override
2970 {
2971 return parent.column() > 0 ? 0 : 1;
2972 }
2973
2974 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
2975 {
2976 Node *grandparentNode = static_cast<Node*>(parent.internalPointer());
2977 Node *parentNode = root;
2978 if (parent.isValid()) {
2979 if (grandparentNode->isDead)
2980 qFatal(msg: "%s: grandparentNode is dead!", Q_FUNC_INFO);
2981 parentNode = grandparentNode->children[parent.row()];
2982 if (parentNode->isDead)
2983 qFatal(msg: "%s: grandparentNode is dead!", Q_FUNC_INFO);
2984 }
2985 return createIndex(arow: row, acolumn: column, adata: parentNode);
2986 }
2987
2988 QModelIndex parent(const QModelIndex &index) const override
2989 {
2990 Node *parent = static_cast<Node*>(index.internalPointer());
2991 Node *grandparent = parent->parent;
2992 if (!grandparent)
2993 return QModelIndex();
2994 return createIndex(arow: grandparent->children.indexOf(t: parent), acolumn: 0, adata: grandparent);
2995 }
2996
2997 QVariant data(const QModelIndex &idx, int role) const override
2998 {
2999 if (crash) {
3000 QTest::qFail(statementStr: "Should not get here...", __FILE__, __LINE__);
3001 return QVariant();
3002 }
3003 if (idx.isValid() && role == Qt::DisplayRole) {
3004 Node *parentNode = root;
3005 if (idx.isValid()) {
3006 parentNode = static_cast<Node*>(idx.internalPointer());
3007 if (parentNode->isDead)
3008 qFatal(msg: "%s: grandparentNode is dead!", Q_FUNC_INFO);
3009 }
3010 return QLatin1Char('[') + QString::number(idx.row()) + QLatin1Char(',')
3011 + QString::number(idx.column()) + QLatin1Char(',')
3012 + QLatin1String(parentNode->isDead ? "dead" : "alive") + QLatin1Char(']');
3013 }
3014 return QVariant();
3015 }
3016};
3017
3018void tst_QTreeView::evilModel_data()
3019{
3020 QTest::addColumn<bool>(name: "visible");
3021 QTest::newRow(dataTag: "visible") << false;
3022 QTest::newRow(dataTag: "visible") << true;
3023}
3024
3025void tst_QTreeView::evilModel()
3026{
3027 QFETCH(bool, visible);
3028 // init
3029 TreeView view;
3030 EvilModel model;
3031 view.setModel(&model);
3032 view.setVisible(visible);
3033 QPersistentModelIndex firstLevel = model.index(row: 0, column: 0);
3034 QPersistentModelIndex secondLevel = model.index(row: 1, column: 0, parent: firstLevel);
3035 QPersistentModelIndex thirdLevel = model.index(row: 2, column: 0, parent: secondLevel);
3036 view.setExpanded(index: firstLevel, expand: true);
3037 view.setExpanded(index: secondLevel, expand: true);
3038 view.setExpanded(index: thirdLevel, expand: true);
3039 model.change();
3040
3041 // tests
3042 view.setRowHidden(row: 0, parent: firstLevel, hide: true);
3043 model.change();
3044
3045 view.setFirstColumnSpanned(row: 1, parent: QModelIndex(), span: true);
3046 model.change();
3047
3048 view.expand(index: secondLevel);
3049 model.change();
3050
3051 view.collapse(index: secondLevel);
3052 model.change();
3053
3054 view.isExpanded(index: secondLevel);
3055 view.setCurrentIndex(firstLevel);
3056 model.change();
3057
3058 view.keyboardSearch(search: "foo");
3059 model.change();
3060
3061 view.visualRect(index: secondLevel);
3062 model.change();
3063
3064 view.scrollTo(index: thirdLevel);
3065 model.change();
3066
3067 view.update(); // will not do anything since view is not visible
3068 model.change();
3069
3070 QTest::mouseDClick(widget: view.viewport(), button: Qt::LeftButton);
3071 model.change();
3072
3073 view.indexAt(p: QPoint(10, 10));
3074 model.change();
3075
3076 view.indexAbove(index: model.index(row: 2, column: 0));
3077 model.change();
3078
3079 view.indexBelow(index: model.index(row: 1, column: 0));
3080 model.change();
3081
3082 QRect rect(0, 0, 10, 10);
3083 view.setSelection(rect, command: QItemSelectionModel::Select);
3084 model.change();
3085
3086 view.moveCursor(cursorAction: QTreeView::MoveDown, modifiers: Qt::NoModifier);
3087 model.change();
3088
3089 view.resizeColumnToContents(column: 1);
3090 model.change();
3091
3092 view.QAbstractItemView::sizeHintForColumn(column: 1);
3093 model.change();
3094
3095 view.rowHeight(index: secondLevel);
3096 model.change();
3097
3098 view.setRootIsDecorated(true);
3099 model.change();
3100
3101 view.setItemsExpandable(false);
3102 model.change();
3103
3104 view.columnViewportPosition(column: 0);
3105 model.change();
3106
3107 view.columnWidth(column: 0);
3108 model.change();
3109
3110 view.setColumnWidth(column: 0, width: 30);
3111 model.change();
3112
3113 view.columnAt(x: 15);
3114 model.change();
3115
3116 view.isColumnHidden(column: 1);
3117 model.change();
3118
3119 view.setColumnHidden(column: 2, hide: true);
3120 model.change();
3121
3122 view.isHeaderHidden();
3123 model.change();
3124
3125 view.setHeaderHidden(true);
3126 model.change();
3127
3128 view.isRowHidden(row: 2, parent: secondLevel);
3129 model.change();
3130
3131 view.setRowHidden(row: 3, parent: secondLevel, hide: true);
3132 model.change();
3133
3134 view.isFirstColumnSpanned(row: 3, parent: thirdLevel);
3135 model.change();
3136
3137 view.setSortingEnabled(true);
3138 model.change();
3139
3140 view.isSortingEnabled();
3141 model.change();
3142
3143 view.setAnimated(true);
3144 model.change();
3145
3146 view.isAnimated();
3147 model.change();
3148
3149 view.setAllColumnsShowFocus(true);
3150 model.change();
3151
3152 view.allColumnsShowFocus();
3153 model.change();
3154
3155 view.doItemsLayout();
3156 model.change();
3157
3158 view.reset();
3159 model.change();
3160
3161 view.sortByColumn(column: 1, order: Qt::AscendingOrder);
3162 model.change();
3163
3164 view.dataChanged(topLeft: secondLevel, bottomRight: secondLevel);
3165 model.change();
3166
3167 view.hideColumn(column: 1);
3168 model.change();
3169
3170 view.showColumn(column: 1);
3171 model.change();
3172
3173 view.resizeColumnToContents(column: 1);
3174 model.change();
3175
3176 view.sortByColumn(column: 1, order: Qt::DescendingOrder);
3177 model.change();
3178
3179 view.selectAll();
3180 model.change();
3181
3182 view.expandAll();
3183 model.change();
3184
3185 view.collapseAll();
3186 model.change();
3187
3188 view.expandToDepth(depth: 3);
3189 model.change();
3190
3191 view.setRootIndex(secondLevel);
3192
3193 model.setCrash();
3194 view.setModel(nullptr);
3195}
3196
3197void tst_QTreeView::indexRowSizeHint()
3198{
3199 QStandardItemModel model(10, 1);
3200 QTreeView view;
3201
3202 view.setModel(&model);
3203
3204 QModelIndex index = model.index(row: 5, column: 0);
3205 QPushButton *w = new QPushButton("Test");
3206 view.setIndexWidget(index, widget: w);
3207
3208 view.show();
3209
3210 QCOMPARE(view.indexRowSizeHint(index), w->sizeHint().height());
3211}
3212
3213void tst_QTreeView::filterProxyModelCrash()
3214{
3215 QStandardItemModel model;
3216 QList<QStandardItem *> items;
3217 for (int i = 0; i < 100; i++)
3218 items << new QStandardItem(QLatin1String("item ") + QString::number(i));
3219 model.appendColumn(items);
3220
3221 QSortFilterProxyModel proxy;
3222 proxy.setSourceModel(&model);
3223
3224 TreeView view;
3225 view.setModel(&proxy);
3226 view.show();
3227 QVERIFY(QTest::qWaitForWindowExposed(&view));
3228 proxy.invalidate();
3229 view.verticalScrollBar()->setValue(15);
3230 QTest::qWait(ms: 20);
3231
3232 proxy.invalidate();
3233 view.update(); //used to crash
3234 QTRY_VERIFY(view.wasPainted);
3235}
3236
3237void tst_QTreeView::renderToPixmap_data()
3238{
3239 QTest::addColumn<int>(name: "row");
3240 QTest::newRow(dataTag: "row-0") << 0;
3241 QTest::newRow(dataTag: "row-1") << 1;
3242}
3243
3244void tst_QTreeView::renderToPixmap()
3245{
3246 QFETCH(int, row);
3247 QTreeView view;
3248 QStandardItemModel model;
3249
3250 model.appendRow(aitem: new QStandardItem("Spanning"));
3251 model.appendRow(items: { new QStandardItem("Not"), new QStandardItem("Spanning") });
3252
3253 view.setModel(&model);
3254 view.setFirstColumnSpanned(row: 0, parent: QModelIndex(), span: true);
3255
3256#ifdef QT_BUILD_INTERNAL
3257 {
3258 // We select the index at row=0 because it spans the
3259 // column (regression test for an assert)
3260 // We select the index at row=1 for coverage.
3261 QItemSelection sel(model.index(row,column: 0), model.index(row,column: 1));
3262 QRect rect;
3263 view.d_func()->renderToPixmap(indexes: sel.indexes(), r: &rect);
3264 }
3265#endif
3266}
3267
3268void tst_QTreeView::styleOptionViewItem()
3269{
3270 class MyDelegate : public QStyledItemDelegate
3271 {
3272 static QString posToString(QStyleOptionViewItem::ViewItemPosition pos)
3273 {
3274 static const char* s_pos[] = { "Invalid", "Beginning", "Middle", "End", "OnlyOne" };
3275 return s_pos[pos];
3276 }
3277 public:
3278 using QStyledItemDelegate::QStyledItemDelegate;
3279 void paint(QPainter *painter, const QStyleOptionViewItem &option,
3280 const QModelIndex &index) const override
3281 {
3282 QStyleOptionViewItem opt(option);
3283 initStyleOption(option: &opt, index);
3284
3285 QVERIFY(!opt.text.isEmpty());
3286 QCOMPARE(opt.index, index);
3287 //qDebug() << index << opt.text;
3288
3289 if (allCollapsed) {
3290 QCOMPARE(!opt.features.testFlag(QStyleOptionViewItem::Alternate),
3291 !(index.row() % 2));
3292 }
3293 QCOMPARE(!opt.features.testFlag(QStyleOptionViewItem::HasCheckIndicator),
3294 !opt.text.contains("Checkable"));
3295
3296 const QString posStr(posToString(pos: opt.viewItemPosition));
3297 if (opt.text.contains(s: "Beginning"))
3298 QCOMPARE(posStr, posToString(QStyleOptionViewItem::Beginning));
3299
3300 if (opt.text.contains(s: "Middle"))
3301 QCOMPARE(posStr, posToString(QStyleOptionViewItem::Middle));
3302
3303 if (opt.text.contains(s: "End"))
3304 QCOMPARE(posStr, posToString(QStyleOptionViewItem::End));
3305
3306 if (opt.text.contains(s: "OnlyOne"))
3307 QCOMPARE(posStr, posToString(QStyleOptionViewItem::OnlyOne));
3308
3309 if (opt.text.contains(s: "Checked"))
3310 QCOMPARE(opt.checkState, Qt::Checked);
3311 else
3312 QCOMPARE(opt.checkState, Qt::Unchecked);
3313
3314 QCOMPARE(!opt.state.testFlag(QStyle::State_Children),
3315 !opt.text.contains("HasChildren"));
3316 QCOMPARE(opt.state.testFlag(QStyle::State_Sibling),
3317 !opt.text.contains("Last"));
3318
3319 QVERIFY(!opt.text.contains("Assert"));
3320
3321 QStyledItemDelegate::paint(painter, option, index);
3322 count++;
3323 }
3324 mutable int count = 0;
3325 bool allCollapsed = false;
3326 };
3327
3328 QTreeView view;
3329 QStandardItemModel model;
3330 view.setModel(&model);
3331 MyDelegate delegate;
3332 view.setItemDelegate(&delegate);
3333 model.appendRow(items: { new QStandardItem("Beginning"),
3334 new QStandardItem("Hidden"),
3335 new QStandardItem("Middle"),
3336 new QStandardItem("Middle"),
3337 new QStandardItem("End") });
3338 QStandardItem *par1 = new QStandardItem("Beginning HasChildren");
3339 model.appendRow(items: { par1,
3340 new QStandardItem("Hidden"),
3341 new QStandardItem("Middle HasChildren"),
3342 new QStandardItem("Middle HasChildren"),
3343 new QStandardItem("End HasChildren") });
3344 model.appendRow(items: { new QStandardItem("OnlyOne"),
3345 new QStandardItem("Hidden"),
3346 new QStandardItem("Assert"),
3347 new QStandardItem("Assert"),
3348 new QStandardItem("Assert") });
3349 QStandardItem *checkable = new QStandardItem("Checkable");
3350 checkable->setCheckable(true);
3351 QStandardItem *checked = new QStandardItem("Checkable Checked");
3352 checked->setCheckable(true);
3353 checked->setCheckState(Qt::Checked);
3354 model.appendRow(items: { new QStandardItem("Beginning"),
3355 new QStandardItem("Hidden"),
3356 checkable, checked,
3357 new QStandardItem("End") });
3358 model.appendRow(items: { new QStandardItem("Beginning Last"),
3359 new QStandardItem("Hidden"),
3360 new QStandardItem("Middle Last"),
3361 new QStandardItem("Middle Last"),
3362 new QStandardItem("End Last") });
3363 par1->appendRow(aitems: { new QStandardItem("Beginning"),
3364 new QStandardItem("Hidden"),
3365 new QStandardItem("Middle"),
3366 new QStandardItem("Middle"),
3367 new QStandardItem("End") });
3368 QStandardItem *par2 = new QStandardItem("Beginning HasChildren");
3369 par1->appendRow(aitems: { par2,
3370 new QStandardItem("Hidden"),
3371 new QStandardItem("Middle HasChildren"),
3372 new QStandardItem("Middle HasChildren"),
3373 new QStandardItem("End HasChildren") });
3374 par2->appendRow(aitems: { new QStandardItem("Beginning Last"),
3375 new QStandardItem("Hidden"),
3376 new QStandardItem("Middle Last"),
3377 new QStandardItem("Middle Last"),
3378 new QStandardItem("End Last") });
3379 QStandardItem *par3 = new QStandardItem("Beginning Last");
3380 par1->appendRow(aitems: { par3, new QStandardItem("Hidden"),
3381 new QStandardItem("Middle Last"),
3382 new QStandardItem("Middle Last"),
3383 new QStandardItem("End Last") });
3384 par3->appendRow(aitems: { new QStandardItem("Assert"),
3385 new QStandardItem("Hidden"),
3386 new QStandardItem("Assert"),
3387 new QStandardItem("Assert"),
3388 new QStandardItem("Asser") });
3389 view.setRowHidden(row: 0, parent: par3->index(), hide: true);
3390 par1->appendRow(aitems: { new QStandardItem("Assert"),
3391 new QStandardItem("Hidden"),
3392 new QStandardItem("Assert"),
3393 new QStandardItem("Assert"),
3394 new QStandardItem("Asser") });
3395 view.setRowHidden(row: 3, parent: par1->index(), hide: true);
3396
3397 view.setColumnHidden(column: 1, hide: true);
3398 const int visibleColumns = 4;
3399 const int modelColumns = 5;
3400
3401 view.header()->swapSections(first: 2, second: 3);
3402 view.setFirstColumnSpanned(row: 2, parent: QModelIndex(), span: true);
3403 view.setAlternatingRowColors(true);
3404
3405#ifdef QT_BUILD_INTERNAL
3406 {
3407 // Test the rendering to pixmap before painting the widget.
3408 // The rendering to pixmap should not depend on having been
3409 // painted already yet.
3410 delegate.count = 0;
3411 QItemSelection sel(model.index(row: 0,column: 0), model.index(row: 0,column: modelColumns-1));
3412 QRect rect;
3413 view.d_func()->renderToPixmap(indexes: sel.indexes(), r: &rect);
3414 if (delegate.count != visibleColumns) {
3415 qDebug() << rect << view.rect() << view.isVisible();
3416 }
3417 QTRY_COMPARE(delegate.count, visibleColumns);
3418 }
3419#endif
3420
3421 delegate.count = 0;
3422 delegate.allCollapsed = true;
3423 view.showMaximized();
3424 QVERIFY(QTest::qWaitForWindowExposed(&view));
3425 QTRY_VERIFY(delegate.count >= 13);
3426 delegate.count = 0;
3427 delegate.allCollapsed = false;
3428 view.expandAll();
3429 QTRY_VERIFY(delegate.count >= 13);
3430 delegate.count = 0;
3431 view.collapse(index: par2->index());
3432 QTRY_VERIFY(delegate.count >= 4);
3433
3434 // test that the rendering of drag pixmap sets the correct options too (QTBUG-15834)
3435#ifdef QT_BUILD_INTERNAL
3436 delegate.count = 0;
3437 QItemSelection sel(model.index(row: 0,column: 0), model.index(row: 0,column: modelColumns-1));
3438 QRect rect;
3439 view.d_func()->renderToPixmap(indexes: sel.indexes(), r: &rect);
3440 if (delegate.count != visibleColumns) {
3441 qDebug() << rect << view.rect() << view.isVisible();
3442 }
3443 QTRY_COMPARE(delegate.count, visibleColumns);
3444#endif
3445
3446 //test dynamic models
3447 {
3448 delegate.count = 0;
3449 QStandardItemModel model2;
3450 QStandardItem *item0 = new QStandardItem("OnlyOne Last");
3451 model2.appendRow(aitem: item0);
3452 view.setModel(&model2);
3453 QTRY_VERIFY(delegate.count >= 1);
3454
3455 QStandardItem *item00 = new QStandardItem("OnlyOne Last");
3456 item0->appendRow(aitem: item00);
3457 item0->setText("OnlyOne Last HasChildren");
3458 delegate.count = 0;
3459 view.expandAll();
3460 QTRY_VERIFY(delegate.count >= 2);
3461
3462 QStandardItem *item1 = new QStandardItem("OnlyOne Last");
3463 delegate.count = 0;
3464 item0->setText("OnlyOne HasChildren");
3465 model2.appendRow(aitem: item1);
3466 QTRY_VERIFY(delegate.count >= 3);
3467
3468 QStandardItem *item01 = new QStandardItem("OnlyOne Last");
3469 delegate.count = 0;
3470 item00->setText("OnlyOne");
3471 item0->appendRow(aitem: item01);
3472 QTRY_VERIFY(delegate.count >= 4);
3473
3474 QStandardItem *item000 = new QStandardItem("OnlyOne Last");
3475 delegate.count = 0;
3476 item00->setText("OnlyOne HasChildren");
3477 item00->appendRow(aitem: item000);
3478 QTRY_VERIFY(delegate.count >= 5);
3479
3480 delegate.count = 0;
3481 item0->removeRow(row: 0);
3482 QTRY_VERIFY(delegate.count >= 3);
3483
3484 item00 = new QStandardItem("OnlyOne");
3485 item0->insertRow(arow: 0, aitem: item00);
3486
3487 delegate.count = 0;
3488 view.expandAll();
3489 QTRY_VERIFY(delegate.count >= 4);
3490
3491 delegate.count = 0;
3492 item0->removeRow(row: 1);
3493 item00->setText("OnlyOne Last");
3494 QTRY_VERIFY(delegate.count >= 3);
3495
3496 delegate.count = 0;
3497 item0->removeRow(row: 0);
3498 item0->setText("OnlyOne");
3499 QTRY_VERIFY(delegate.count >= 2);
3500
3501 //with hidden items
3502 item0->setText("OnlyOne HasChildren");
3503 item00 = new QStandardItem("OnlyOne");
3504 item0->appendRow(aitem: item00);
3505 item01 = new QStandardItem("Assert");
3506 item0->appendRow(aitem: item01);
3507 view.setRowHidden(row: 1, parent: item0->index(), hide: true);
3508 view.expandAll();
3509 QStandardItem *item02 = new QStandardItem("OnlyOne Last");
3510 item0->appendRow(aitem: item02);
3511 delegate.count = 0;
3512 QTRY_VERIFY(delegate.count >= 4);
3513
3514 item0->removeRow(row: 2);
3515 item00->setText("OnlyOne Last");
3516 delegate.count = 0;
3517 QTRY_VERIFY(delegate.count >= 3);
3518
3519 item00->setText("OnlyOne");
3520 item0->insertRow(arow: 2, aitem: new QStandardItem("OnlyOne Last"));
3521 view.collapse(index: item0->index());
3522 item0->removeRow(row: 0);
3523 delegate.count = 0;
3524 QTRY_VERIFY(delegate.count >= 2);
3525
3526 item0->removeRow(row: 1);
3527 item0->setText("OnlyOne");
3528 delegate.count = 0;
3529 QTRY_VERIFY(delegate.count >= 2);
3530 }
3531}
3532
3533class task174627_TreeView : public QTreeView
3534{
3535 Q_OBJECT
3536protected slots:
3537 void currentChanged(const QModelIndex &current, const QModelIndex &) override
3538 { emit signalCurrentChanged(current); }
3539signals:
3540 void signalCurrentChanged(const QModelIndex &);
3541};
3542
3543void tst_QTreeView::task174627_moveLeftToRoot()
3544{
3545 QStandardItemModel model;
3546 QStandardItem *item1 = new QStandardItem(QString("item 1"));
3547 model.invisibleRootItem()->appendRow(aitem: item1);
3548 QStandardItem *item2 = new QStandardItem(QString("item 2"));
3549 item1->appendRow(aitem: item2);
3550
3551 task174627_TreeView view;
3552 view.setModel(&model);
3553 view.setRootIndex(item1->index());
3554 view.setCurrentIndex(item2->index());
3555
3556 QSignalSpy spy(&view, &task174627_TreeView::signalCurrentChanged);
3557 QTest::keyClick(widget: &view, key: Qt::Key_Left);
3558 QCOMPARE(spy.count(), 0);
3559}
3560
3561void tst_QTreeView::task171902_expandWith1stColHidden()
3562{
3563 //the task was: if the first column of a treeview is hidden, the expanded state is not correctly restored
3564 QStandardItemModel model;
3565 QStandardItem root("root"), root2("root"),
3566 subitem("subitem"), subitem2("subitem"),
3567 subsubitem("subsubitem"), subsubitem2("subsubitem");
3568
3569 model.appendRow(items: { &root, &root2 });
3570 root.appendRow(aitems: { &subitem, &subitem2 });
3571 subitem.appendRow(aitems: { &subsubitem, &subsubitem2 });
3572
3573 QTreeView view;
3574 view.setModel(&model);
3575
3576 view.setColumnHidden(column: 0, hide: true);
3577 view.expandAll();
3578 view.collapse(index: root.index());
3579 view.expand(index: root.index());
3580
3581 QCOMPARE(view.isExpanded(root.index()), true);
3582 QCOMPARE(view.isExpanded(subitem.index()), true);
3583
3584}
3585
3586void tst_QTreeView::task203696_hidingColumnsAndRowsn()
3587{
3588 QTreeView view;
3589 QStandardItemModel model(0, 3);
3590 for (int i = 0; i < 3; ++i) {
3591 const QString prefix = QLatin1String("row ") + QString::number(i) + QLatin1String(" col ");
3592 model.insertRow(arow: model.rowCount());
3593 for (int j = 0; j < model.columnCount(); ++j)
3594 model.setData(index: model.index(row: i, column: j), value: prefix + QString::number(j));
3595 }
3596 view.setModel(&model);
3597 view.show();
3598 view.setColumnHidden(column: 0, hide: true);
3599 view.setRowHidden(row: 0, parent: QModelIndex(), hide: true);
3600 QCOMPARE(view.indexAt(QPoint(0, 0)), model.index(1, 1));
3601}
3602
3603
3604void tst_QTreeView::addRowsWhileSectionsAreHidden()
3605{
3606 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
3607 QSKIP("Wayland: This fails. Figure out why.");
3608
3609 QTreeView view;
3610 for (int pass = 1; pass <= 2; ++pass) {
3611 QStandardItemModel *model = new QStandardItemModel(6, pass, &view);
3612 view.setModel(model);
3613 view.show();
3614 QVERIFY(QTest::qWaitForWindowActive(&view));
3615
3616 for (int i = 0; i < 3; ++i)
3617 {
3618 model->insertRow(arow: model->rowCount());
3619 const QString prefix = QLatin1String("row ") + QString::number(i) + QLatin1String(" col ");
3620 for (int j = 0; j < model->columnCount(); ++j)
3621 model->setData(index: model->index(row: i, column: j), value: prefix + QString::number(j));
3622 }
3623 for (int col = 0; col < pass; ++col)
3624 view.setColumnHidden(column: col, hide: true);
3625 for (int i = 3; i < 6; ++i)
3626 {
3627 model->insertRow(arow: model->rowCount());
3628 const QString prefix = QLatin1String("row ") + QString::number(i) + QLatin1String(" col ");
3629 for (int j = 0; j < model->columnCount(); ++j)
3630 model->setData(index: model->index(row: i, column: j), value: prefix + QString::number(j));
3631 }
3632 for (int col = 0; col < pass; ++col)
3633 view.setColumnHidden(column: col, hide: false);
3634
3635 auto allVisualRectsValid = [](QTreeView *view, QStandardItemModel *model) {
3636 for (int i = 0; i < 6; ++i) {
3637 if (!view->visualRect(index: model->index(row: i, column: 0)).isValid())
3638 return false;
3639 }
3640 return true;
3641 };
3642 QTRY_VERIFY(allVisualRectsValid(&view, model));
3643
3644 delete model;
3645 }
3646}
3647
3648void tst_QTreeView::task216717_updateChildren()
3649{
3650 class Tree : public QTreeWidget
3651 {
3652 protected:
3653 void paintEvent(QPaintEvent *e) override
3654 {
3655 QTreeWidget::paintEvent(event: e);
3656 refreshed = true;
3657 }
3658 public:
3659 bool refreshed = false;
3660 } tree;
3661 tree.show();
3662 QVERIFY(QTest::qWaitForWindowExposed(&tree));
3663 tree.refreshed = false;
3664 QTreeWidgetItem *parent = new QTreeWidgetItem({ "parent" });
3665 tree.addTopLevelItem(item: parent);
3666 QTRY_VERIFY(tree.refreshed);
3667 tree.refreshed = false;
3668 parent->addChild(child: new QTreeWidgetItem({ "child" }));
3669 QTRY_VERIFY(tree.refreshed);
3670
3671}
3672
3673void tst_QTreeView::task220298_selectColumns()
3674{
3675 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
3676 QSKIP("Wayland: This fails. Figure out why.");
3677
3678 //this is a very simple 3x3 model where the internalId of the index are different for each cell
3679 class Model : public QAbstractTableModel
3680 {
3681 public:
3682 int columnCount(const QModelIndex & parent = QModelIndex()) const override
3683 { return parent.isValid() ? 0 : 3; }
3684 int rowCount(const QModelIndex & parent = QModelIndex()) const override
3685 { return parent.isValid() ? 0 : 3; }
3686
3687 QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override
3688 {
3689 if (role == Qt::DisplayRole) {
3690 return QVariant(QString::number(index.column()) + QLatin1Char('-')
3691 + QString::number(index.row()));
3692 }
3693 return QVariant();
3694 }
3695
3696 QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override
3697 {
3698 return hasIndex(row, column, parent) ? createIndex(arow: row, acolumn: column, aid: quintptr(column * 10 + row)) : QModelIndex();
3699 }
3700 };
3701
3702 TreeView view;
3703 Model model;
3704 view.setModel(&model);
3705 view.show();
3706 QVERIFY(QTest::qWaitForWindowActive(&view));
3707 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {},
3708 pos: view.visualRect(index: view.model()->index(row: 1, column: 1)).center());
3709 QTRY_VERIFY(view.selectedIndexes().contains(view.model()->index(1, 2)));
3710 QVERIFY(view.selectedIndexes().contains(view.model()->index(1, 1)));
3711 QVERIFY(view.selectedIndexes().contains(view.model()->index(1, 0)));
3712}
3713
3714
3715void tst_QTreeView::task224091_appendColumns()
3716{
3717 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
3718 QSKIP("Wayland: This fails. Figure out why.");
3719
3720 QStandardItemModel *model = new QStandardItemModel();
3721 QWidget* topLevel= new QWidget;
3722 setFrameless(topLevel);
3723 QTreeView *treeView = new QTreeView(topLevel);
3724 treeView->setModel(model);
3725 topLevel->show();
3726 treeView->resize(w: 50, h: 50);
3727 QApplication::setActiveWindow(topLevel);
3728 QVERIFY(QTest::qWaitForWindowActive(topLevel));
3729
3730 QVERIFY(!treeView->verticalScrollBar()->isVisible());
3731
3732 QList<QStandardItem *> projlist;
3733 for (int k = 0; k < 10; ++k)
3734 projlist.append(t: new QStandardItem(QLatin1String("Top Level ") + QString::number(k)));
3735 model->appendColumn(items: projlist);
3736 model->invisibleRootItem()->appendRow(aitem: new QStandardItem("end"));
3737
3738 QTRY_VERIFY(treeView->verticalScrollBar()->isVisible());
3739
3740 delete topLevel;
3741 delete model;
3742}
3743
3744void tst_QTreeView::task211293_removeRootIndex()
3745{
3746 QTreeView view;
3747 QStandardItemModel model;
3748 QStandardItem *A1 = new QStandardItem("A1");
3749 QStandardItem *B11 = new QStandardItem("B1.1");
3750 QStandardItem *C111 = new QStandardItem("C1.1.1");
3751 QStandardItem *C112 = new QStandardItem("C1.1.2");
3752 QStandardItem *C113 = new QStandardItem("C1.1.3");
3753 QStandardItem *D1131 = new QStandardItem("D1.1.3.1");
3754 QStandardItem *E11311 = new QStandardItem("E1.1.3.1.1");
3755 QStandardItem *E11312 = new QStandardItem("E1.1.3.1.2");
3756 QStandardItem *E11313 = new QStandardItem("E1.1.3.1.3");
3757 QStandardItem *E11314 = new QStandardItem("E1.1.3.1.4");
3758 QStandardItem *D1132 = new QStandardItem("D1.1.3.2");
3759 QStandardItem *E11321 = new QStandardItem("E1.1.3.2.1");
3760 D1132->appendRow(aitem: E11321);
3761 D1131->appendRow(aitem: E11311);
3762 D1131->appendRow(aitem: E11312);
3763 D1131->appendRow(aitem: E11313);
3764 D1131->appendRow(aitem: E11314);
3765 C113->appendRow(aitem: D1131);
3766 C113->appendRow(aitem: D1132);
3767 B11->appendRow(aitem: C111);
3768 B11->appendRow(aitem: C112);
3769 B11->appendRow(aitem: C113);
3770 A1->appendRow(aitem: B11);
3771 model.appendRow(aitem: A1);
3772 view.setModel(&model);
3773 view.setRootIndex(model.indexFromItem(item: B11));
3774 view.setExpanded(index: model.indexFromItem(item: B11), expand: true);
3775 view.setCurrentIndex(model.indexFromItem(item: E11314));
3776 view.setExpanded(index: model.indexFromItem(item: E11314), expand: true);
3777 view.show();
3778 QVERIFY(QTest::qWaitForWindowExposed(&view));
3779 QVERIFY(model.removeRows(0, 1));
3780}
3781
3782void tst_QTreeView::task225539_deleteModel()
3783{
3784 QTreeView treeView;
3785 treeView.show();
3786 QStandardItemModel *model = new QStandardItemModel(&treeView);
3787
3788 QStandardItem *parentItem = model->invisibleRootItem();
3789 QStandardItem *item = new QStandardItem(QString("item"));
3790 parentItem->appendRow(aitem: item);
3791
3792 treeView.setModel(model);
3793
3794 QCOMPARE(item->index(), treeView.indexAt(QPoint()));
3795
3796 delete model;
3797
3798 QVERIFY(!treeView.indexAt(QPoint()).isValid());
3799}
3800
3801void tst_QTreeView::task230123_setItemsExpandable()
3802{
3803 //let's check that we prevent the expansion inside a treeview
3804 //when the property is set.
3805 QTreeWidget tree;
3806
3807 QTreeWidgetItem root;
3808 QTreeWidgetItem child;
3809 root.addChild(child: &child);
3810 tree.addTopLevelItem(item: &root);
3811
3812 tree.setCurrentItem(&root);
3813
3814 tree.setItemsExpandable(false);
3815
3816 QTest::keyClick(widget: &tree, key: Qt::Key_Plus);
3817 QVERIFY(!root.isExpanded());
3818
3819 QTest::keyClick(widget: &tree, key: Qt::Key_Right);
3820 QVERIFY(!root.isExpanded());
3821
3822 tree.setItemsExpandable(true);
3823
3824 QTest::keyClick(widget: &tree, key: Qt::Key_Plus);
3825 QVERIFY(root.isExpanded());
3826
3827 QTest::keyClick(widget: &tree, key: Qt::Key_Minus);
3828 QVERIFY(!root.isExpanded());
3829
3830 QTest::keyClick(widget: &tree, key: Qt::Key_Right);
3831 QVERIFY(root.isExpanded());
3832
3833 QTest::keyClick(widget: &tree, key: Qt::Key_Left);
3834 QVERIFY(!root.isExpanded());
3835
3836 QTest::keyClick(widget: &tree, key: Qt::Key_Right);
3837 QVERIFY(root.isExpanded());
3838
3839 const bool navToChild = tree.style()->styleHint(stylehint: QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, opt: nullptr, widget: &tree);
3840 QTest::keyClick(widget: &tree, key: Qt::Key_Right);
3841 QCOMPARE(tree.currentItem(), navToChild ? &child : &root);
3842
3843 QTest::keyClick(widget: &tree, key: Qt::Key_Right);
3844 //it should not be expanded: it has no leaf
3845 QCOMPARE(child.isExpanded(), false);
3846
3847 QTest::keyClick(widget: &tree, key: Qt::Key_Left);
3848 QCOMPARE(tree.currentItem(), &root);
3849
3850 QTest::keyClick(widget: &tree, key: Qt::Key_Left);
3851 QVERIFY(!root.isExpanded());
3852}
3853
3854void tst_QTreeView::task202039_closePersistentEditor()
3855{
3856 QStandardItemModel model(1, 1);
3857 QTreeView view;
3858 view.setModel(&model);
3859
3860 QModelIndex current = model.index(row: 0,column: 0);
3861 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: view.visualRect(index: current).center());
3862 QTest::mouseDClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: view.visualRect(index: current).center());
3863 QCOMPARE(view.currentIndex(), current);
3864 QVERIFY(view.indexWidget(current));
3865
3866 view.closePersistentEditor(index: current);
3867 QVERIFY(!view.indexWidget(current));
3868
3869 //here was the bug: closing the persistent editor would not reset the state
3870 //and it was impossible to go into editinon again
3871 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: view.visualRect(index: current).center());
3872 QTest::mouseDClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: view.visualRect(index: current).center());
3873 QCOMPARE(view.currentIndex(), current);
3874 QVERIFY(view.indexWidget(current));
3875}
3876
3877void tst_QTreeView::task238873_avoidAutoReopening()
3878{
3879 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
3880 QSKIP("Wayland: This fails. Figure out why.");
3881
3882 QStandardItemModel model;
3883
3884 QStandardItem item0("row 0");
3885 QStandardItem item1("row 1");
3886 QStandardItem item2("row 2");
3887 QStandardItem item3("row 3");
3888 model.appendColumn( items: QList<QStandardItem*>() << &item0 << &item1 << &item2 << &item3);
3889
3890 QStandardItem child("child");
3891 item1.appendRow( aitem: &child);
3892
3893 QTreeView view;
3894 view.setModel(&model);
3895 view.show();
3896 view.expandAll();
3897 QVERIFY(QTest::qWaitForWindowActive(&view));
3898
3899 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: view.visualRect(index: child.index()).center());
3900 QTRY_COMPARE(view.currentIndex(), child.index());
3901
3902 view.setExpanded(index: item1.index(), expand: false);
3903
3904 QTRY_VERIFY(!view.isExpanded(item1.index()));
3905}
3906
3907void tst_QTreeView::task244304_clickOnDecoration()
3908{
3909 QTreeView view;
3910 QStandardItemModel model;
3911 QStandardItem item0("row 0");
3912 QStandardItem item00("row 0");
3913 item0.appendRow(aitem: &item00);
3914 QStandardItem item1("row 1");
3915 model.appendColumn(items: { &item0, &item1 });
3916 view.setModel(&model);
3917
3918 QVERIFY(!view.currentIndex().isValid());
3919 QRect rect = view.visualRect(index: item0.index());
3920 //we click on the decoration
3921 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {},
3922 pos: rect.topLeft() + QPoint(-rect.left() / 2, rect.height() / 2));
3923 QVERIFY(!view.currentIndex().isValid());
3924 QVERIFY(view.isExpanded(item0.index()));
3925
3926 rect = view.visualRect(index: item1.index());
3927 //the item has no decoration, it should get selected
3928 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {},
3929 pos: rect.topLeft() + QPoint(-rect.left() / 2, rect.height() / 2));
3930 QCOMPARE(view.currentIndex(), item1.index());
3931}
3932
3933void tst_QTreeView::task246536_scrollbarsNotWorking()
3934{
3935 class MyObject : public QObject
3936 {
3937 public:
3938 using QObject::QObject;
3939 bool eventFilter(QObject*, QEvent *e) override
3940 {
3941 if (e->type() == QEvent::Paint)
3942 count++;
3943
3944 return false;
3945 }
3946 int count = 0;
3947 };
3948 QTreeView tree;
3949 MyObject o;
3950 tree.viewport()->installEventFilter(filterObj: &o);
3951 QStandardItemModel model;
3952 tree.setModel(&model);
3953 tree.show();
3954 QVERIFY(QTest::qWaitForWindowExposed(&tree));
3955 QList<QStandardItem *> items;
3956 for (int i = 0; i < 100; ++i)
3957 items << new QStandardItem(QLatin1String("item ") + QString::number(i));
3958 o.count = 0;
3959 model.invisibleRootItem()->appendColumn(aitems: items);
3960 QTRY_VERIFY(o.count > 0);
3961 o.count = 0;
3962 tree.verticalScrollBar()->setValue(50);
3963 QTRY_VERIFY(o.count > 0);
3964}
3965
3966void tst_QTreeView::task250683_wrongSectionSize()
3967{
3968 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
3969 QSKIP("Wayland: This fails. Figure out why.");
3970
3971 QStandardItemModel model;
3972 populateFakeDirModel(model: &model);
3973
3974 QTreeView treeView;
3975 treeView.header()->setSectionResizeMode(QHeaderView::ResizeToContents);
3976 treeView.setModel(&model);
3977 treeView.setColumnHidden(column: 2, hide: true);
3978 treeView.setColumnHidden(column: 3, hide: true);
3979
3980 treeView.show();
3981 QVERIFY(QTest::qWaitForWindowActive(&treeView));
3982
3983 QCOMPARE(treeView.header()->sectionSize(0) + treeView.header()->sectionSize(1), treeView.viewport()->width());
3984}
3985
3986void tst_QTreeView::task239271_addRowsWithFirstColumnHidden()
3987{
3988 class MyDelegate : public QStyledItemDelegate
3989 {
3990 public:
3991 void paint(QPainter *painter, const QStyleOptionViewItem &option,
3992 const QModelIndex &index) const override
3993 {
3994 paintedIndexes << index;
3995 QStyledItemDelegate::paint(painter, option, index);
3996 }
3997 mutable QSet<QModelIndex> paintedIndexes;
3998 };
3999
4000 QTreeView view;
4001 QStandardItemModel model;
4002 view.setModel(&model);
4003 MyDelegate delegate;
4004 view.setItemDelegate(&delegate);
4005 QStandardItem root0("root0"), root1("root1");
4006 model.invisibleRootItem()->appendRow(aitems: QList<QStandardItem*>() << &root0 << &root1);
4007 QStandardItem sub0("sub0"), sub00("sub00");
4008 root0.appendRow(aitems: QList<QStandardItem*>() << &sub0 << &sub00);
4009 view.expand(index: root0.index());
4010
4011 view.hideColumn(column: 0);
4012 view.show();
4013 QVERIFY(QTest::qWaitForWindowExposed(&view));
4014 delegate.paintedIndexes.clear();
4015 QStandardItem sub1("sub1"), sub11("sub11");
4016 root0.appendRow(aitems: QList<QStandardItem*>() << &sub1 << &sub11);
4017
4018 //items in the 2nd column should have been painted
4019 QTRY_VERIFY(!delegate.paintedIndexes.isEmpty());
4020 QVERIFY(delegate.paintedIndexes.contains(sub00.index()));
4021 QVERIFY(delegate.paintedIndexes.contains(sub11.index()));
4022}
4023
4024void tst_QTreeView::task254234_proxySort()
4025{
4026 //based on tst_QTreeView::sortByColumn
4027 // it used not to work when setting the source of a proxy after enabling sorting
4028 QTreeView view;
4029 QStandardItemModel model(4, 2);
4030 model.setItem(row: 0, column: 0, item: new QStandardItem("b"));
4031 model.setItem(row: 1, column: 0, item: new QStandardItem("d"));
4032 model.setItem(row: 2, column: 0, item: new QStandardItem("c"));
4033 model.setItem(row: 3, column: 0, item: new QStandardItem("a"));
4034 model.setItem(row: 0, column: 1, item: new QStandardItem("e"));
4035 model.setItem(row: 1, column: 1, item: new QStandardItem("g"));
4036 model.setItem(row: 2, column: 1, item: new QStandardItem("h"));
4037 model.setItem(row: 3, column: 1, item: new QStandardItem("f"));
4038
4039 view.sortByColumn(column: 1, order: Qt::DescendingOrder);
4040 view.setSortingEnabled(true);
4041
4042 QSortFilterProxyModel proxy;
4043 proxy.setDynamicSortFilter(true);
4044 view.setModel(&proxy);
4045 proxy.setSourceModel(&model);
4046 QCOMPARE(view.header()->sortIndicatorSection(), 1);
4047 QCOMPARE(view.model()->data(view.model()->index(0, 1)).toString(), QString::fromLatin1("h"));
4048 QCOMPARE(view.model()->data(view.model()->index(1, 1)).toString(), QString::fromLatin1("g"));
4049}
4050
4051void tst_QTreeView::task248022_changeSelection()
4052{
4053 //we check that changing the selection between the mouse press and the mouse release
4054 //works correctly
4055 TreeView view;
4056 const QStringList list({"1", "2"});
4057 QStringListModel model(list);
4058 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
4059 view.setModel(&model);
4060 connect(sender: view.selectionModel(), signal: &QItemSelectionModel::selectionChanged,
4061 receiver: &view, slot: &TreeView::handleSelectionChanged);
4062 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {},
4063 pos: view.visualRect(index: model.index(row: 1)).center());
4064 QCOMPARE(view.selectionModel()->selectedIndexes().count(), list.count());
4065}
4066
4067void tst_QTreeView::task245654_changeModelAndExpandAll()
4068{
4069 QTreeView view;
4070 QScopedPointer<QStandardItemModel> model(new QStandardItemModel);
4071 QStandardItem *top = new QStandardItem("top");
4072 QStandardItem *sub = new QStandardItem("sub");
4073 top->appendRow(aitem: sub);
4074 model->appendRow(aitem: top);
4075 view.setModel(model.data());
4076 view.expandAll();
4077 view.show();
4078 QVERIFY(QTest::qWaitForWindowExposed(&view));
4079 QTRY_VERIFY(view.isExpanded(top->index()));
4080
4081 //now let's try to delete the model
4082 //then repopulate and expand again
4083 model.reset(other: new QStandardItemModel);
4084 top = new QStandardItem("top");
4085 sub = new QStandardItem("sub");
4086 top->appendRow(aitem: sub);
4087 model->appendRow(aitem: top);
4088 view.setModel(model.data());
4089 view.expandAll();
4090 QTRY_VERIFY(view.isExpanded(top->index()));
4091}
4092
4093void tst_QTreeView::doubleClickedWithSpans()
4094{
4095 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
4096 QSKIP("Wayland: This fails. Figure out why.");
4097
4098 QTreeView view;
4099 QStandardItemModel model(1, 2);
4100 view.setModel(&model);
4101 view.setFirstColumnSpanned(row: 0, parent: QModelIndex(), span: true);
4102 view.show();
4103 QApplication::setActiveWindow(&view);
4104 QVERIFY(QTest::qWaitForWindowActive(&view));
4105 QVERIFY(view.isActiveWindow());
4106
4107 QPoint p(10, 10);
4108 QCOMPARE(view.indexAt(p), model.index(0, 0));
4109 QSignalSpy spy(&view, &QAbstractItemView::doubleClicked);
4110 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
4111 QTest::mouseDClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
4112 QTest::mouseRelease(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
4113 QCOMPARE(spy.count(), 1);
4114
4115 //let's click on the 2nd column
4116 p.setX(p.x() + view.header()->sectionSize(logicalIndex: 0));
4117 QCOMPARE(view.indexAt(p), model.index(0, 0));
4118
4119 //end the previous edition
4120 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
4121 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
4122 QTest::mouseDClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
4123 QTest::mouseRelease(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
4124 QTRY_COMPARE(spy.count(), 2);
4125}
4126
4127void tst_QTreeView::taskQTBUG_6450_selectAllWith1stColumnHidden()
4128{
4129 QTreeWidget tree;
4130 tree.setSelectionMode(QAbstractItemView::MultiSelection);
4131 tree.setColumnCount(2);
4132 QList<QTreeWidgetItem *> items;
4133 const int nrRows = 10;
4134 for (int i = 0; i < nrRows; ++i) {
4135 const QString text = QLatin1String("item: ") + QString::number(i);
4136 items.append(t: new QTreeWidgetItem(static_cast<QTreeWidget *>(nullptr),
4137 QStringList(text)));
4138 items.last()->setText(column: 1, atext: QString("is an item"));
4139 }
4140 tree.insertTopLevelItems(index: 0, items);
4141
4142 tree.hideColumn(column: 0);
4143 tree.selectAll();
4144
4145 QVERIFY(tree.selectionModel()->hasSelection());
4146 for (int i = 0; i < nrRows; ++i)
4147 QVERIFY(tree.selectionModel()->isRowSelected(i, QModelIndex()));
4148}
4149
4150class TreeViewQTBUG_9216 : public QTreeView
4151{
4152 Q_OBJECT
4153public:
4154 void paintEvent(QPaintEvent *event) override
4155 {
4156 if (doCompare)
4157 QCOMPARE(event->rect(), viewport()->rect());
4158 QTreeView::paintEvent(event);
4159 painted++;
4160 }
4161 int painted = 0;
4162 bool doCompare = false;
4163};
4164
4165void tst_QTreeView::taskQTBUG_9216_setSizeAndUniformRowHeightsWrongRepaint()
4166{
4167 QStandardItemModel model(10, 10, this);
4168 for (int row = 0; row < 10; row++) {
4169 const QString prefix = QLatin1String("row ") + QString::number(row) + QLatin1String(", col ");
4170 for (int col = 0; col < 10; col++)
4171 model.setItem(row, column: col, item: new QStandardItem(prefix + QString::number(col)));
4172 }
4173 TreeViewQTBUG_9216 view;
4174 view.setUniformRowHeights(true);
4175 view.setModel(&model);
4176 view.painted = 0;
4177 view.doCompare = false;
4178 view.show();
4179 QVERIFY(QTest::qWaitForWindowExposed(&view));
4180 QTRY_VERIFY(view.painted > 0);
4181
4182 QTest::qWait(ms: 100); // This one is needed to make the test fail before the patch.
4183 view.painted = 0;
4184 view.doCompare = true;
4185 model.setData(index: model.index(row: 0, column: 0), value: QVariant(QSize(50, 50)), role: Qt::SizeHintRole);
4186 QTRY_VERIFY(view.painted > 0);
4187}
4188
4189void tst_QTreeView::keyboardNavigationWithDisabled()
4190{
4191 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
4192 QSKIP("Wayland: This fails. Figure out why.");
4193
4194 QWidget topLevel;
4195 QTreeView view(&topLevel);
4196 QStandardItemModel model(90, 0);
4197 for (int i = 0; i < 90; i ++) {
4198 model.setItem(arow: i, aitem: new QStandardItem(QString::number(i)));
4199 model.item(row: i)->setEnabled(i % 6 == 0);
4200 }
4201 view.setModel(&model);
4202
4203 view.resize(w: 200, h: view.visualRect(index: model.index(row: 0,column: 0)).height()*10);
4204 topLevel.show();
4205 QApplication::setActiveWindow(&topLevel);
4206 QVERIFY(QTest::qWaitForWindowActive(&topLevel));
4207 QVERIFY(topLevel.isActiveWindow());
4208
4209 view.setCurrentIndex(model.index(row: 1, column: 0));
4210 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Up);
4211 QCOMPARE(view.currentIndex(), model.index(0, 0));
4212 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Down);
4213 QCOMPARE(view.currentIndex(), model.index(6, 0));
4214 QTest::keyClick(widget: view.viewport(), key: Qt::Key_PageDown);
4215 QCOMPARE(view.currentIndex(), model.index(18, 0));
4216 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Down);
4217 QCOMPARE(view.currentIndex(), model.index(24, 0));
4218 QTest::keyClick(widget: view.viewport(), key: Qt::Key_PageUp);
4219 QCOMPARE(view.currentIndex(), model.index(12, 0));
4220 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Up);
4221 QCOMPARE(view.currentIndex(), model.index(6, 0));
4222 // QTBUG-44746 - when first/last item is disabled,
4223 // Key_PageUp/Down/Home/End will not work as expected.
4224 model.item(row: 0)->setEnabled(false);
4225 model.item(row: 1)->setEnabled(true);
4226 model.item(row: 2)->setEnabled(true);
4227 model.item(row: model.rowCount() - 1)->setEnabled(false);
4228 model.item(row: model.rowCount() - 2)->setEnabled(true);
4229 model.item(row: model.rowCount() - 3)->setEnabled(true);
4230 // PageUp
4231 view.setCurrentIndex(model.index(row: 2, column: 0));
4232 QCOMPARE(view.currentIndex(), model.index(2, 0));
4233 QTest::keyClick(widget: view.viewport(), key: Qt::Key_PageUp);
4234 QCOMPARE(view.currentIndex(), model.index(1, 0));
4235 // PageDown
4236 view.setCurrentIndex(model.index(row: model.rowCount() - 3, column: 0));
4237 QCOMPARE(view.currentIndex(), model.index(model.rowCount() - 3, 0));
4238 QTest::keyClick(widget: view.viewport(), key: Qt::Key_PageDown);
4239 QCOMPARE(view.currentIndex(), model.index(model.rowCount() - 2, 0));
4240 // Key_Home
4241 QTest::keyClick(widget: view.viewport(), key: Qt::Key_Home);
4242 QCOMPARE(view.currentIndex(), model.index(1, 0));
4243 // Key_End
4244 QTest::keyClick(widget: view.viewport(), key: Qt::Key_End);
4245 QCOMPARE(view.currentIndex(), model.index(model.rowCount() - 2, 0));
4246}
4247
4248class RemoveColumnOne : public QSortFilterProxyModel
4249{
4250 Q_OBJECT
4251public:
4252 bool filterAcceptsColumn(int source_column, const QModelIndex &) const override
4253 {
4254 if (m_removeColumn)
4255 return source_column != 1;
4256 return true;
4257 }
4258 void removeColumn()
4259 {
4260 m_removeColumn = true;
4261 invalidate();
4262 }
4263private:
4264 bool m_removeColumn = false;
4265};
4266
4267
4268void tst_QTreeView::saveRestoreState()
4269{
4270 QStandardItemModel model;
4271 for (int i = 0; i < 100; i++) {
4272 model.appendRow(items: {new QStandardItem(QStringLiteral("item ") + QString::number(i)),
4273 new QStandardItem(QStringLiteral("hidden by proxy")),
4274 new QStandardItem(QStringLiteral("hidden by user")) });
4275 }
4276 QCOMPARE(model.columnCount(), 3);
4277
4278 RemoveColumnOne proxy;
4279 proxy.setSourceModel(&model);
4280 QCOMPARE(proxy.columnCount(), 3);
4281
4282 QTreeView view;
4283 view.setModel(&proxy);
4284 view.resize(w: 500, h: 500);
4285 view.show();
4286 view.header()->hideSection(alogicalIndex: 2);
4287 QVERIFY(view.header()->isSectionHidden(2));
4288 proxy.removeColumn();
4289 QCOMPARE(proxy.columnCount(), 2);
4290 QVERIFY(view.header()->isSectionHidden(1));
4291 const QByteArray data = view.header()->saveState();
4292
4293 QTreeView view2;
4294 view2.setModel(&proxy);
4295 view2.resize(w: 500, h: 500);
4296 view2.show();
4297 view2.header()->restoreState(state: data);
4298 QVERIFY(view2.header()->isSectionHidden(1));
4299}
4300
4301class Model_11466 : public QAbstractItemModel
4302{
4303 Q_OBJECT
4304public:
4305 Model_11466(QObject *parent = nullptr) : QAbstractItemModel(parent)
4306 , m_selectionModel(new QItemSelectionModel(this, this))
4307 {
4308 connect(sender: m_selectionModel, signal: &QItemSelectionModel::currentChanged,
4309 receiver: this, slot: &Model_11466::slotCurrentChanged);
4310 }
4311
4312 int rowCount(const QModelIndex &parent) const override
4313 {
4314 if (parent.isValid())
4315 return (parent.internalId() == 0) ? 4 : 0;
4316 return 2; // two top level items
4317 }
4318
4319 int columnCount(const QModelIndex & /* parent */) const override
4320 {
4321 return 2;
4322 }
4323
4324 QVariant data(const QModelIndex &index, int role) const override
4325 {
4326 if (role == Qt::DisplayRole && index.isValid()) {
4327 qint64 parentRowPlusOne = qint64(index.internalId());
4328 QString str;
4329 QTextStream stream(&str);
4330 if (parentRowPlusOne > 0)
4331 stream << parentRowPlusOne << " -> " << index.row() << " : " << index.column();
4332 else
4333 stream << index.row() << " : " << index.column();
4334 return QVariant(str);
4335 }
4336 return QVariant();
4337 }
4338
4339 QModelIndex parent(const QModelIndex &index) const override
4340 {
4341 if (index.isValid()) {
4342 qint64 parentRowPlusOne = qint64(index.internalId());
4343 if (parentRowPlusOne > 0) {
4344 int row = static_cast<int>(parentRowPlusOne - 1);
4345 return createIndex(arow: row, acolumn: 0);
4346 }
4347 }
4348 return QModelIndex();
4349 }
4350
4351 void bindView(QTreeView *view)
4352 {
4353 // sets the view to this model with a shared selection model
4354 QItemSelectionModel *oldModel = view->selectionModel();
4355 if (oldModel != m_selectionModel)
4356 delete oldModel;
4357 view->setModel(this); // this creates a new selection model for the view, but we don't want it either ...
4358 oldModel = view->selectionModel();
4359 view->setSelectionModel(m_selectionModel);
4360 delete oldModel;
4361 }
4362
4363 QModelIndex index(int row, int column, const QModelIndex &parent) const override
4364 {
4365 return createIndex(arow: row, acolumn: column, aid: parent.isValid() ? quintptr(parent.row() + 1) : quintptr(0));
4366 }
4367
4368public slots:
4369 void slotCurrentChanged(const QModelIndex &current,const QModelIndex &)
4370 {
4371 if (m_block)
4372 return;
4373
4374 if (current.isValid()) {
4375 int selectedRow = current.row();
4376 const quintptr parentRowPlusOne = current.internalId();
4377
4378 for (int i = 0; i < 2; ++i) {
4379 // announce the removal of all non top level items
4380 beginRemoveRows(parent: createIndex(arow: i, acolumn: 0), first: 0, last: 3);
4381 // nothing to actually do for the removal
4382 endRemoveRows();
4383
4384 // put them back in again
4385 beginInsertRows(parent: createIndex(arow: i, acolumn: 0), first: 0, last: 3);
4386 // nothing to actually do for the insertion
4387 endInsertRows();
4388 }
4389 // reselect the current item ...
4390 QModelIndex selectedIndex = createIndex(arow: selectedRow, acolumn: 0, aid: parentRowPlusOne);
4391
4392 m_block = true; // recursion block
4393 m_selectionModel->select(index: selectedIndex, command: QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current|QItemSelectionModel::Rows);
4394 m_selectionModel->setCurrentIndex(index: selectedIndex, command: QItemSelectionModel::NoUpdate);
4395 m_block = false;
4396 } else {
4397 m_selectionModel->clear();
4398 }
4399 }
4400
4401private:
4402 bool m_block = false;
4403 QItemSelectionModel *m_selectionModel;
4404};
4405
4406void tst_QTreeView::taskQTBUG_11466_keyboardNavigationRegression()
4407{
4408 QTreeView treeView;
4409 treeView.setSelectionBehavior(QAbstractItemView::SelectRows);
4410 treeView.setSelectionMode(QAbstractItemView::SingleSelection);
4411 Model_11466 model(&treeView);
4412 model.bindView(view: &treeView);
4413 treeView.expandAll();
4414 treeView.show();
4415 QVERIFY(QTest::qWaitForWindowExposed(&treeView));
4416
4417 QTest::keyPress(widget: treeView.viewport(), key: Qt::Key_Down);
4418 QTRY_COMPARE(treeView.currentIndex(), treeView.selectionModel()->selection().indexes().first());
4419}
4420
4421void tst_QTreeView::taskQTBUG_13567_removeLastItemRegression()
4422{
4423 QtTestModel model(200, 1);
4424
4425 QTreeView view;
4426 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
4427 view.setModel(&model);
4428 view.show();
4429 QVERIFY(QTest::qWaitForWindowExposed(&view));
4430
4431 view.scrollToBottom();
4432 QTest::qWait(ms: 10);
4433 CHECK_VISIBLE(199, 0);
4434
4435 view.setCurrentIndex(model.index(row: 199, column: 0));
4436 model.removeLastRow();
4437 QTRY_COMPARE(view.currentIndex(), model.index(198, 0));
4438 CHECK_VISIBLE(198, 0);
4439}
4440
4441// From QTBUG-25333 (QTreeWidget drag crashes when there was a hidden item in tree)
4442// The test passes simply if it doesn't crash, hence there are no calls
4443// to QCOMPARE() or QVERIFY().
4444// Note: define QT_BUILD_INTERNAL to run this test
4445void tst_QTreeView::taskQTBUG_25333_adjustViewOptionsForIndex()
4446{
4447 QTreeView view;
4448 QStandardItemModel model;
4449 QStandardItem *item1 = new QStandardItem("Item1");
4450 QStandardItem *item2 = new QStandardItem("Item2");
4451 QStandardItem *item3 = new QStandardItem("Item3");
4452 QStandardItem *data1 = new QStandardItem("Data1");
4453 QStandardItem *data2 = new QStandardItem("Data2");
4454 QStandardItem *data3 = new QStandardItem("Data3");
4455
4456 // Create a treeview
4457 model.appendRow(items: { item1, data1 });
4458 model.appendRow(items: { item2, data2 });
4459 model.appendRow(items: { item3, data3 });
4460
4461 view.setModel(&model);
4462
4463 // Hide a row
4464 view.setRowHidden(row: 1, parent: QModelIndex(), hide: true);
4465 view.expandAll();
4466
4467 view.show();
4468
4469#ifdef QT_BUILD_INTERNAL
4470 {
4471 QStyleOptionViewItem option;
4472
4473 view.d_func()->adjustViewOptionsForIndex(option: &option, current: model.indexFromItem(item: item1));
4474
4475 view.d_func()->adjustViewOptionsForIndex(option: &option, current: model.indexFromItem(item: item3));
4476 }
4477#endif
4478
4479}
4480
4481void tst_QTreeView::taskQTBUG_18539_emitLayoutChanged()
4482{
4483 qRegisterMetaType<QList<QPersistentModelIndex>>();
4484 qRegisterMetaType<QAbstractItemModel::LayoutChangeHint>();
4485
4486 QTreeView view;
4487
4488 QStandardItem* item = new QStandardItem("Orig");
4489 QStandardItem* child = new QStandardItem("Child");
4490 item->setChild(row: 0, column: 0, item: child);
4491
4492 QStandardItemModel model;
4493 model.appendRow(aitem: item);
4494
4495 view.setModel(&model);
4496
4497 QStandardItem* replacementItem = new QStandardItem("Replacement");
4498 QStandardItem* replacementChild = new QStandardItem("ReplacementChild");
4499
4500 replacementItem->setChild(row: 0, column: 0, item: replacementChild);
4501
4502 QSignalSpy beforeSpy(&model, &QAbstractItemModel::layoutAboutToBeChanged);
4503 QSignalSpy afterSpy(&model, &QAbstractItemModel::layoutChanged);
4504
4505 QSignalSpy beforeRISpy(&model, &QAbstractItemModel::rowsAboutToBeInserted);
4506 QSignalSpy afterRISpy(&model, &QAbstractItemModel::rowsInserted);
4507
4508 QSignalSpy beforeRRSpy(&model, &QAbstractItemModel::rowsAboutToBeRemoved);
4509 QSignalSpy afterRRSpy(&model, &QAbstractItemModel::rowsRemoved);
4510
4511 model.setItem(row: 0, column: 0, item: replacementItem);
4512
4513 QCOMPARE(beforeSpy.size(), 1);
4514 QCOMPARE(afterSpy.size(), 1);
4515
4516 QCOMPARE(beforeRISpy.size(), 0);
4517 QCOMPARE(afterRISpy.size(), 0);
4518
4519 QCOMPARE(beforeRISpy.size(), 0);
4520 QCOMPARE(afterRISpy.size(), 0);
4521}
4522
4523void tst_QTreeView::taskQTBUG_8176_emitOnExpandAll()
4524{
4525 QTreeWidget tw;
4526 QTreeWidgetItem *item = new QTreeWidgetItem(&tw, QStringList(QString("item 1")));
4527 QTreeWidgetItem *item2 = new QTreeWidgetItem(item, QStringList(QString("item 2")));
4528 new QTreeWidgetItem(item2, QStringList(QString("item 3")));
4529 new QTreeWidgetItem(item2, QStringList(QString("item 4")));
4530 QTreeWidgetItem *item5 = new QTreeWidgetItem(&tw, QStringList(QString("item 5")));
4531 new QTreeWidgetItem(item5, QStringList(QString("item 6")));
4532 QSignalSpy spy(&tw, &QTreeView::expanded);
4533
4534 // expand all
4535 tw.expandAll();
4536 QCOMPARE(spy.size(), 6);
4537 spy.clear();
4538 tw.collapseAll();
4539 item2->setExpanded(true);
4540 spy.clear();
4541 tw.expandAll();
4542 QCOMPARE(spy.size(), 5);
4543
4544 // collapse all
4545 QSignalSpy spy2(&tw, &QTreeView::collapsed);
4546 tw.collapseAll();
4547 QCOMPARE(spy2.size(), 6);
4548 tw.expandAll();
4549 item2->setExpanded(false);
4550 spy2.clear();
4551 tw.collapseAll();
4552 QCOMPARE(spy2.size(), 5);
4553
4554 // expand to depth
4555 item2->setExpanded(true);
4556 spy.clear();
4557 spy2.clear();
4558 tw.expandToDepth(depth: 0);
4559
4560 QCOMPARE(spy.size(), 2); // item and item5 are expanded
4561 QCOMPARE(spy2.size(), 1); // item2 is collapsed
4562}
4563
4564void tst_QTreeView::testInitialFocus()
4565{
4566 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
4567 QSKIP("Wayland: This fails. Figure out why.");
4568
4569 QTreeWidget treeWidget;
4570 treeWidget.setColumnCount(5);
4571 new QTreeWidgetItem(&treeWidget, QString("1;2;3;4;5").split(sep: QLatin1Char(';')));
4572 treeWidget.setTreePosition(2);
4573 treeWidget.header()->hideSection(alogicalIndex: 0); // make sure we skip hidden section(s)
4574 treeWidget.header()->swapSections(first: 1, second: 2); // make sure that we look for first visual index (and not first logical)
4575 treeWidget.show();
4576 QVERIFY(QTest::qWaitForWindowExposed(&treeWidget));
4577 QTRY_COMPARE(treeWidget.currentIndex().column(), 2);
4578}
4579
4580#if QT_CONFIG(animation)
4581void tst_QTreeView::quickExpandCollapse()
4582{
4583 //this unit tests makes sure the state after the animation is restored correctly
4584 //after starting a 2nd animation while the first one was still on-going
4585 //this tests that the stateBeforeAnimation is not set to AnimatingState
4586 QTreeView tree;
4587 tree.setAnimated(true);
4588 QStandardItemModel model;
4589 QStandardItem *root = new QStandardItem("root");
4590 root->appendRow(aitem: new QStandardItem("subnode"));
4591 model.appendRow(aitem: root);
4592 tree.setModel(&model);
4593
4594 QModelIndex rootIndex = root->index();
4595 QVERIFY(rootIndex.isValid());
4596
4597 tree.show();
4598 QVERIFY(QTest::qWaitForWindowExposed(&tree));
4599
4600 const QAbstractItemView::State initialState = tree.state();
4601
4602 tree.expand(index: rootIndex);
4603 QCOMPARE(tree.state(), QTreeView::AnimatingState);
4604
4605 tree.collapse(index: rootIndex);
4606 QCOMPARE(tree.state(), QTreeView::AnimatingState);
4607
4608 //the animation lasts for 250ms max so 5000 (default) should be enough
4609 QTRY_COMPARE(tree.state(), initialState);
4610}
4611#endif // animation
4612
4613void tst_QTreeView::taskQTBUG_37813_crash()
4614{
4615 // QTBUG_37813: Crash in visual / logical index mapping in QTreeViewPrivate::adjustViewOptionsForIndex()
4616 // when hiding/moving columns. It is reproduceable with a QTreeWidget only.
4617#ifdef QT_BUILD_INTERNAL
4618 QTreeWidget treeWidget;
4619 treeWidget.setDragEnabled(true);
4620 treeWidget.setColumnCount(2);
4621 QList<QTreeWidgetItem *> items;
4622 for (int r = 0; r < 2; ++r) {
4623 const QString prefix = QLatin1String("Row ") + QString::number(r) + QLatin1String(" Column ");
4624 QTreeWidgetItem *item = new QTreeWidgetItem();
4625 for (int c = 0; c < treeWidget.columnCount(); ++c)
4626 item->setText(column: c, atext: prefix + QString::number(c));
4627 items.append(t: item);
4628 }
4629 treeWidget.addTopLevelItems(items);
4630 treeWidget.setColumnHidden(column: 0, hide: true);
4631 treeWidget.header()->moveSection(from: 0, to: 1);
4632 QItemSelection sel(treeWidget.model()->index(row: 0, column: 0), treeWidget.model()->index(row: 0, column: 1));
4633 QRect rect;
4634 QAbstractItemViewPrivate *av = static_cast<QAbstractItemViewPrivate*>(qt_widget_private(widget: &treeWidget));
4635 const QPixmap pixmap = av->renderToPixmap(indexes: sel.indexes(), r: &rect);
4636 QVERIFY(pixmap.size().isValid());
4637#endif // QT_BUILD_INTERNAL
4638}
4639
4640// QTBUG-45697: Using a QTreeView with a multi-column model filtered by QSortFilterProxyModel,
4641// when sorting the source model while the widget is not yet visible and showing the widget
4642// later on, corruption occurs in QTreeView.
4643class Qtbug45697TestWidget : public QWidget
4644{
4645 Q_OBJECT
4646public:
4647 static const int columnCount = 3;
4648
4649 explicit Qtbug45697TestWidget(QWidget *parent = nullptr);
4650 int timerTick() const { return m_timerTick; }
4651
4652public slots:
4653 void slotTimer();
4654
4655private:
4656 QTreeView *m_treeView;
4657 QStandardItemModel *m_model;
4658 QSortFilterProxyModel *m_sortFilterProxyModel;
4659 int m_timerTick = 0;
4660};
4661
4662Qtbug45697TestWidget::Qtbug45697TestWidget(QWidget *parent)
4663 : QWidget(parent), m_treeView(new QTreeView(this))
4664 , m_model(new QStandardItemModel(0, Qtbug45697TestWidget::columnCount, this))
4665 , m_sortFilterProxyModel(new QSortFilterProxyModel(this))
4666 {
4667 QVBoxLayout *vBoxLayout = new QVBoxLayout(this);
4668 vBoxLayout->addWidget(m_treeView);
4669
4670 for (char sortChar = 'z'; sortChar >= 'a' ; --sortChar) {
4671 QList<QStandardItem *> items;
4672 for (int column = 0; column < Qtbug45697TestWidget::columnCount; ++column) {
4673 const QString text = QLatin1Char(sortChar) + QLatin1String(" ") + QString::number(column);
4674 items.append(t: new QStandardItem(text));
4675 }
4676 m_model->appendRow(items);
4677 }
4678
4679 m_sortFilterProxyModel->setSourceModel(m_model);
4680 m_treeView->setModel(m_sortFilterProxyModel);
4681
4682 QHeaderView *headerView = m_treeView->header();
4683 for (int s = 1, lastSection = headerView->count() - 1; s < lastSection; ++s)
4684 headerView->setSectionResizeMode(logicalIndex: s, mode: QHeaderView::ResizeToContents);
4685
4686 QTimer *timer = new QTimer(this);
4687 timer->setInterval(50);
4688 connect(sender: timer, signal: &QTimer::timeout, receiver: this, slot: &Qtbug45697TestWidget::slotTimer);
4689 timer->start();
4690}
4691
4692void Qtbug45697TestWidget::slotTimer()
4693{
4694 switch (m_timerTick++) {
4695 case 0:
4696 m_model->sort(column: 0);
4697 break;
4698 case 1:
4699 show();
4700 break;
4701 default:
4702 close();
4703 break;
4704 }
4705}
4706
4707void tst_QTreeView::taskQTBUG_45697_crash()
4708{
4709 Qtbug45697TestWidget testWidget;
4710 testWidget.setWindowTitle(QTest::currentTestFunction());
4711 testWidget.resize(w: 400, h: 400);
4712 testWidget.move(QGuiApplication::primaryScreen()->availableGeometry().topLeft() + QPoint(100, 100));
4713 QTRY_VERIFY(testWidget.timerTick() >= 2);
4714}
4715
4716void tst_QTreeView::taskQTBUG_7232_AllowUserToControlSingleStep()
4717{
4718 // When we set the scrollMode to ScrollPerPixel it will adjust the scrollbars singleStep automatically
4719 // Setting a singlestep on a scrollbar should however imply that the user takes control.
4720 // Setting a singlestep to -1 return to an automatic control of the singleStep.
4721 QTreeWidget t;
4722 t.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
4723 t.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
4724 t.setColumnCount(2);
4725 QTreeWidgetItem *mainItem = new QTreeWidgetItem(&t, QStringList() << "Root");
4726 for (int i = 0; i < 200; ++i) {
4727 QTreeWidgetItem *item = new QTreeWidgetItem(mainItem, QStringList(QString("Item")));
4728 new QTreeWidgetItem(item, QStringList() << "Child" << "1");
4729 new QTreeWidgetItem(item, QStringList() << "Child" << "2");
4730 new QTreeWidgetItem(item, QStringList() << "Child" << "3");
4731 }
4732 t.expandAll();
4733
4734 t.setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
4735 t.setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
4736
4737 t.setGeometry(ax: 200, ay: 200, aw: 200, ah: 200);
4738 int vStep1 = t.verticalScrollBar()->singleStep();
4739 int hStep1 = t.horizontalScrollBar()->singleStep();
4740 QVERIFY(vStep1 > 1);
4741 QVERIFY(hStep1 > 1);
4742
4743 t.verticalScrollBar()->setSingleStep(1);
4744 t.setGeometry(ax: 300, ay: 300, aw: 300, ah: 300);
4745 QCOMPARE(t.verticalScrollBar()->singleStep(), 1);
4746
4747 t.horizontalScrollBar()->setSingleStep(1);
4748 t.setGeometry(ax: 400, ay: 400, aw: 400, ah: 400);
4749 QCOMPARE(t.horizontalScrollBar()->singleStep(), 1);
4750
4751 t.setGeometry(ax: 200, ay: 200, aw: 200, ah: 200);
4752 t.verticalScrollBar()->setSingleStep(-1);
4753 t.horizontalScrollBar()->setSingleStep(-1);
4754 QCOMPARE(vStep1, t.verticalScrollBar()->singleStep());
4755 QCOMPARE(hStep1, t.horizontalScrollBar()->singleStep());
4756}
4757
4758void tst_QTreeView::statusTip_data()
4759{
4760 QTest::addColumn<bool>(name: "intermediateParent");
4761 QTest::newRow(dataTag: "noIntermediate") << false;
4762 QTest::newRow(dataTag: "intermediate") << true;
4763}
4764
4765void tst_QTreeView::statusTip()
4766{
4767 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
4768 QSKIP("Wayland: This fails. Figure out why.");
4769
4770 QFETCH(bool, intermediateParent);
4771 QMainWindow mw;
4772 QtTestModel model(5, 5);
4773 model.statusTipsEnabled = true;
4774 QTreeView *view = new QTreeView;
4775 view->setModel(&model);
4776 view->viewport()->setMouseTracking(true);
4777 view->header()->viewport()->setMouseTracking(true);
4778 if (intermediateParent) {
4779 QWidget *inter = new QWidget;
4780 QVBoxLayout *vbox = new QVBoxLayout;
4781 inter->setLayout(vbox);
4782 vbox->addWidget(view);
4783 mw.setCentralWidget(inter);
4784 } else {
4785 mw.setCentralWidget(view);
4786 }
4787 mw.statusBar();
4788 mw.setGeometry(QRect(QPoint(QApplication::desktop()->geometry().center() - QPoint(250, 250)),
4789 QSize(500, 500)));
4790 mw.show();
4791 QApplication::setActiveWindow(&mw);
4792 QVERIFY(QTest::qWaitForWindowActive(&mw));
4793 // Ensure it is moved away first and then moved to the relevant section
4794 QTest::mouseMove(window: mw.windowHandle(), pos: view->mapTo(&mw, view->rect().bottomLeft() + QPoint(20, 20)));
4795 QPoint centerPoint = view->viewport()->mapTo(&mw, view->visualRect(index: model.index(row: 0, column: 0)).center());
4796 QTest::mouseMove(window: mw.windowHandle(), pos: centerPoint);
4797 QTRY_COMPARE(mw.statusBar()->currentMessage(), QLatin1String("[0,0,0] -- Status"));
4798 centerPoint = view->viewport()->mapTo(&mw, view->visualRect(index: model.index(row: 0, column: 1)).center());
4799 QTest::mouseMove(window: mw.windowHandle(), pos: centerPoint);
4800 QTRY_COMPARE(mw.statusBar()->currentMessage(), QLatin1String("[0,1,0] -- Status"));
4801 centerPoint = view->header()->viewport()->mapTo(&mw,
4802 QPoint(view->header()->sectionViewportPosition(logicalIndex: 0) + view->header()->sectionSize(logicalIndex: 0) / 2,
4803 view->header()->height() / 2));
4804 QTest::mouseMove(window: mw.windowHandle(), pos: centerPoint);
4805 QTRY_COMPARE(mw.statusBar()->currentMessage(), QLatin1String("Header 0 -- Status"));
4806}
4807
4808class FetchMoreModel : public QStandardItemModel
4809{
4810 Q_OBJECT
4811public:
4812 FetchMoreModel(QObject *parent = nullptr) : QStandardItemModel(parent)
4813 {
4814 for (int i = 0; i < 20; ++i) {
4815 QStandardItem *item = new QStandardItem("Row");
4816 item->appendRow(aitem: new QStandardItem("Child"));
4817 appendRow(aitem: item);
4818 }
4819 }
4820 bool canFetchMore(const QModelIndex &parent) const override
4821 {
4822 if (!canFetchReady || !parent.isValid())
4823 return false;
4824 if (!parent.parent().isValid())
4825 return rowCount(parent) < 20;
4826 return false;
4827 }
4828 void fetchMore(const QModelIndex &parent) override
4829 {
4830 QStandardItem *item = itemFromIndex(index: parent);
4831 for (int i = 0; i < 19; ++i)
4832 item->appendRow(aitem: new QStandardItem(QStringLiteral("New Child ") + QString::number(i)));
4833 }
4834 bool canFetchReady = false;
4835};
4836
4837void tst_QTreeView::fetchMoreOnScroll()
4838{
4839 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
4840 QSKIP("Wayland: This fails. Figure out why.");
4841
4842 QTreeView tw;
4843 FetchMoreModel im;
4844 tw.setModel(&im);
4845 tw.show();
4846 tw.expandAll();
4847 QVERIFY(QTest::qWaitForWindowActive(&tw));
4848 // Now we can allow the fetch to happen
4849 im.canFetchReady = true;
4850 tw.verticalScrollBar()->setValue(tw.verticalScrollBar()->maximum());
4851 // The item should have now fetched the other children, thus bringing the count to 20
4852 QCOMPARE(im.item(19)->rowCount(), 20);
4853}
4854
4855static void fillModeltaskQTBUG_8376(QAbstractItemModel &model)
4856{
4857 model.insertRow(arow: 0);
4858 model.insertColumn(acolumn: 0);
4859 model.insertColumn(acolumn: 1);
4860 QModelIndex index = model.index(row: 0, column: 0);
4861 model.setData(index, value: "Level0");
4862 {
4863 model.insertRow(arow: 0, aparent: index);
4864 model.insertRow(arow: 1, aparent: index);
4865 model.insertColumn(acolumn: 0, aparent: index);
4866 model.insertColumn(acolumn: 1, aparent: index);
4867
4868 QModelIndex idx;
4869 idx = model.index(row: 0, column: 0, parent: index);
4870 model.setData(index: idx, value: "Level1");
4871
4872 idx = model.index(row: 0, column: 1, parent: index);
4873 model.setData(index: idx, value: "very\nvery\nhigh\ncell");
4874 }
4875}
4876
4877void tst_QTreeView::taskQTBUG_8376()
4878{
4879 QTreeView tv;
4880 QStandardItemModel model;
4881 fillModeltaskQTBUG_8376(model);
4882 tv.setModel(&model);
4883 tv.expandAll(); // init layout
4884
4885 QModelIndex idxLvl0 = model.index(row: 0, column: 0);
4886 QModelIndex idxLvl1 = model.index(row: 0, column: 1, parent: idxLvl0);
4887 const int rowHeightLvl0 = tv.rowHeight(index: idxLvl0);
4888 const int rowHeightLvl1Visible = tv.rowHeight(index: idxLvl1);
4889 QVERIFY(rowHeightLvl0 < rowHeightLvl1Visible);
4890
4891 tv.hideColumn(column: 1);
4892 const int rowHeightLvl1Hidden = tv.rowHeight(index: idxLvl1);
4893 QCOMPARE(rowHeightLvl0, rowHeightLvl1Hidden);
4894
4895 tv.showColumn(column: 1);
4896 const int rowHeightLvl1Visible2 = tv.rowHeight(index: idxLvl1);
4897 QCOMPARE(rowHeightLvl1Visible, rowHeightLvl1Visible2);
4898}
4899
4900void tst_QTreeView::taskQTBUG_61476()
4901{
4902 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
4903 QSKIP("Wayland: This fails. Figure out why.");
4904
4905 // This checks that if a user clicks on an item to collapse it that it
4906 // does not edit (in this case change the check state) the item that is
4907 // now over the mouse just because it got a release event
4908 QTreeView tv;
4909 QStandardItemModel model;
4910 QStandardItem *lastTopLevel = nullptr;
4911 {
4912 for (int i = 0; i < 4; ++i) {
4913 QStandardItem *item = new QStandardItem(QLatin1String("Row Item"));
4914 item->setCheckable(true);
4915 item->setCheckState(Qt::Checked);
4916 model.appendRow(aitem: item);
4917 lastTopLevel = item;
4918 for (int j = 0; j < 2; ++j) {
4919 QStandardItem *childItem = new QStandardItem(QLatin1String("Child row Item"));
4920 childItem->setCheckable(true);
4921 childItem->setCheckState(Qt::Checked);
4922 item->appendRow(aitem: childItem);
4923 QStandardItem *grandChild = new QStandardItem(QLatin1String("Grand child row Item"));
4924 grandChild->setCheckable(true);
4925 grandChild->setCheckState(Qt::Checked);
4926 childItem->appendRow(aitem: grandChild);
4927 }
4928 }
4929 }
4930 tv.setModel(&model);
4931 tv.expandAll();
4932 // We need it to be this size so that the effect of the collapsing will
4933 // cause the parent item to move to be under the cursor
4934 tv.resize(w: 200, h: 200);
4935 tv.show();
4936 QVERIFY(QTest::qWaitForWindowActive(&tv));
4937 tv.verticalScrollBar()->setValue(tv.verticalScrollBar()->maximum());
4938
4939 // We want to press specifically right around where a checkbox for the
4940 // parent item could be when collapsing
4941 QTreeViewPrivate *priv = static_cast<QTreeViewPrivate*>(qt_widget_private(widget: &tv));
4942 const QModelIndex mi = lastTopLevel->child(row: 0)->index();
4943 const QRect rect = priv->itemDecorationRect(index: mi);
4944 const QPoint pos = rect.center();
4945
4946 QTest::mousePress(widget: tv.viewport(), button: Qt::LeftButton, stateKey: {}, pos);
4947 const bool expandsOnPress =
4948 (tv.style()->styleHint(stylehint: QStyle::SH_ListViewExpand_SelectMouseType, opt: nullptr, widget: &tv) == QEvent::MouseButtonPress);
4949 if (expandsOnPress)
4950 QTRY_VERIFY(!tv.isExpanded(mi));
4951
4952 QTest::mouseRelease(widget: tv.viewport(), button: Qt::LeftButton, stateKey: {}, pos);
4953 QTRY_VERIFY(!tv.isExpanded(mi));
4954 QCOMPARE(lastTopLevel->checkState(), Qt::Checked);
4955
4956 // Test that it does not toggle the check state of a previously selected item when collapsing an
4957 // item causes it to position the item under the mouse to be the decoration for the selected item
4958 tv.expandAll();
4959 tv.verticalScrollBar()->setValue(tv.verticalScrollBar()->maximum());
4960 // It is not enough to programmatically select the item, we need to have it clicked on
4961 QTest::mouseClick(widget: tv.viewport(), button: Qt::LeftButton, stateKey: {}, pos: tv.visualRect(index: lastTopLevel->index()).center());
4962 QTest::mousePress(widget: tv.viewport(), button: Qt::LeftButton, stateKey: {}, pos);
4963 if (expandsOnPress)
4964 QTRY_VERIFY(!tv.isExpanded(mi));
4965 QTest::mouseRelease(widget: tv.viewport(), button: Qt::LeftButton, stateKey: nullptr, pos);
4966 QTRY_VERIFY(!tv.isExpanded(mi));
4967 QCOMPARE(lastTopLevel->checkState(), Qt::Checked);
4968}
4969
4970void tst_QTreeView::taskQTBUG_42469_crash()
4971{
4972 QTreeWidget treeWidget;
4973 QTreeWidgetItem *itemOne = new QTreeWidgetItem(QStringList("item1"));
4974 QTreeWidgetItem *itemTwo = new QTreeWidgetItem(QStringList("item2"));
4975 treeWidget.addTopLevelItem(item: itemOne);
4976 treeWidget.addTopLevelItem(item: itemTwo);
4977 treeWidget.topLevelItem(index: 1)->addChild(child: new QTreeWidgetItem(QStringList("child1")));
4978
4979 treeWidget.setAnimated(true);
4980 QObject::connect(sender: &treeWidget, signal: &QTreeWidget::itemExpanded, slot: [&](QTreeWidgetItem* p_item) {
4981 auto tempCount = treeWidget.topLevelItemCount();
4982 for (int j = 0; j < tempCount; ++j)
4983 if (treeWidget.topLevelItem(index: j) != p_item) {
4984 auto temp = treeWidget.topLevelItem(index: j);
4985 temp->setHidden(true);
4986 }
4987 });
4988
4989 treeWidget.show();
4990 itemTwo->setExpanded(true);
4991}
4992
4993void tst_QTreeView::fetchUntilScreenFull()
4994{
4995 class TreeModel : public QAbstractItemModel
4996 {
4997 public:
4998 const int maxChildren = 49;
4999 explicit TreeModel(QObject* parent = nullptr) : QAbstractItemModel(parent)
5000 {
5001 QVariant rootData1("Parent Col 1");
5002 QVariant rootData2("Parent Col 2");
5003 QVector<QVariant> rootData;
5004 rootData.append(t: rootData1);
5005 rootData.append(t: rootData2);
5006
5007 m_root = new TreeItem(rootData, nullptr);
5008
5009 QVariant childData1("Col 1");
5010 QVariant childData2("Col 2");
5011 QVector<QVariant> childData;
5012 childData.append(t: childData1);
5013 childData.append(t: childData2);
5014
5015 TreeItem* item_1 = new TreeItem(childData, m_root);
5016 m_root->children.append(t: item_1);
5017
5018 TreeItem* item_2 = new TreeItem(childData, item_1);
5019 item_1->children.append(t: item_2);
5020 }
5021
5022 QModelIndex index(const int row, const int column,
5023 const QModelIndex& parent = QModelIndex()) const override
5024 {
5025 if (!hasIndex(row, column, parent))
5026 return QModelIndex();
5027
5028 TreeItem* parentItem =
5029 parent.isValid() ? static_cast<TreeItem*>(parent.internalPointer()) : m_root;
5030 TreeItem* childItem = parentItem->children.at(i: row);
5031 return createIndex(arow: row, acolumn: column, adata: childItem);
5032 }
5033
5034 int rowCount(const QModelIndex& parent) const override
5035 {
5036 if (parent.column() > 0)
5037 return 0;
5038
5039 TreeItem* parentItem = parent.isValid() ? static_cast<TreeItem*>(parent.internalPointer())
5040 : m_root;
5041 return parentItem->children.count();
5042 }
5043
5044 int columnCount(const QModelIndex&) const override { return 2; }
5045
5046 QModelIndex parent(const QModelIndex& childIndex) const override
5047 {
5048 if (!childIndex.isValid())
5049 return QModelIndex();
5050
5051 TreeItem* parentItem =
5052 static_cast<TreeItem*>(childIndex.internalPointer())->parent;
5053 return parentItem == m_root ? QModelIndex()
5054 : createIndex(arow: parentItem->rowInParent(), acolumn: 0, adata: parentItem);
5055 }
5056
5057 QVariant data(const QModelIndex& index, const int role) const override
5058 {
5059 if (!index.isValid() || role != Qt::DisplayRole)
5060 return QVariant();
5061
5062 TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
5063 return item->data.at(i: index.column());
5064 }
5065
5066 bool canFetchMore(const QModelIndex& parent) const override
5067 {
5068 if (!parent.isValid()) {
5069 return false;
5070 } else {
5071 TreeItem* item = static_cast<TreeItem*>(parent.internalPointer());
5072 return item->children.size() < maxChildren;
5073 }
5074 }
5075
5076 void fetchMore(const QModelIndex& parent) override
5077 {
5078 if (!parent.isValid())
5079 return;
5080
5081 fetchMoreCount++;
5082 TreeItem* parentItem = static_cast<TreeItem*>(parent.internalPointer());
5083 int childCount = parentItem->children.size();
5084
5085 beginInsertRows(parent, first: childCount, last: childCount);
5086
5087 QVariant childData1("Col 1");
5088 QVariant childData2("Col 2");
5089 QVector<QVariant> childData;
5090 childData.append(t: childData1);
5091 childData.append(t: childData2);
5092 TreeItem* newChild = new TreeItem(childData, parentItem);
5093 parentItem->children.append(t: newChild);
5094
5095 endInsertRows();
5096 }
5097
5098 int fetchMoreCount = 0;
5099 private:
5100 struct TreeItem
5101 {
5102 TreeItem(const QVector<QVariant>& values, TreeItem* parent)
5103 : data(values), parent(parent)
5104 {
5105 }
5106 ~TreeItem() { qDeleteAll(c: children); }
5107 int rowInParent() const
5108 {
5109 if (parent)
5110 return parent->children.indexOf(t: const_cast<TreeItem*>(this));
5111 return 0;
5112 }
5113 QVector<QVariant> data;
5114 QVector<TreeItem*> children;
5115 TreeItem* parent = nullptr;
5116 };
5117 TreeItem* m_root;
5118 };
5119
5120 QTreeView tv;
5121 TreeModel model;
5122 tv.setModel(&model);
5123
5124 const int itemHeight = tv.sizeHintForRow(row: 0);
5125 tv.resize(w: 250, h: itemHeight * 10);
5126 tv.show();
5127 QVERIFY(QTest::qWaitForWindowExposed(&tv));
5128
5129 tv.expand(index: model.index(row: 0, column: 0));
5130 const int viewportHeight = tv.viewport()->height();
5131 const int itemCount = viewportHeight / itemHeight;
5132 const int minFetchCount = itemCount - 1;
5133 const int maxFetchCount = itemCount + 1;
5134
5135 const bool expectedItemNumberFetched = model.fetchMoreCount >= minFetchCount
5136 && model.fetchMoreCount <= maxFetchCount;
5137 if (!expectedItemNumberFetched)
5138 qDebug() << model.fetchMoreCount << minFetchCount << maxFetchCount;
5139 QVERIFY(expectedItemNumberFetched);
5140}
5141
5142
5143QTEST_MAIN(tst_QTreeView)
5144#include "tst_qtreeview.moc"
5145

source code of qtbase/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp