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 | |
53 | using namespace QTestPrivate; |
54 | |
55 | #if QT_CONFIG(draganddrop) |
56 | Q_DECLARE_METATYPE(QAbstractItemView::DragDropMode) |
57 | #endif |
58 | Q_DECLARE_METATYPE(QAbstractItemView::EditTriggers) |
59 | Q_DECLARE_METATYPE(QAbstractItemView::EditTrigger) |
60 | |
61 | using IntBounds = std::numeric_limits<int>; |
62 | static 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 | |
80 | class TreeView : public QTreeView |
81 | { |
82 | Q_OBJECT |
83 | public: |
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; |
93 | public 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 | |
104 | class tst_QTreeView : public QObject |
105 | { |
106 | Q_OBJECT |
107 | |
108 | public slots: |
109 | void selectionOrderTest(); |
110 | |
111 | private 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 | |
249 | class QtTestModel: public QAbstractItemModel |
250 | { |
251 | Q_OBJECT |
252 | public: |
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 (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 |
417 | void 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 | |
482 | void 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 | |
564 | void 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 | |
576 | void 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 | |
599 | void 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 | |
625 | void 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 | |
637 | void 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 | |
656 | void 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 | |
691 | void 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 | |
712 | void 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 | |
728 | void 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 | |
795 | void 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 | |
859 | void 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 | |
869 | void 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 | |
905 | class RepaintTreeView : public QTreeView |
906 | { |
907 | public: |
908 | using QTreeView::QTreeView; |
909 | bool repainted = false; |
910 | |
911 | protected: |
912 | void paintEvent(QPaintEvent *event) override |
913 | { repainted = true; QTreeView::paintEvent(event); } |
914 | }; |
915 | |
916 | void 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 | |
948 | void 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 | |
982 | void 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 | |
1031 | void 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 | |
1060 | void 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 | |
1117 | void 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 | |
1244 | void 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 | |
1275 | void 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 | |
1310 | void 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 | |
1329 | void 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 | |
1344 | void tst_QTreeView::() |
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 | |
1365 | void 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 | |
1398 | void 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 | |
1459 | void 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 | |
1468 | void 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 | |
1477 | void 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 | |
1486 | void 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 | |
1502 | void 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 | |
1519 | void 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 | |
1558 | void 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 | |
1565 | void 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 | |
1696 | static 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 | |
1718 | void 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 | |
1788 | void 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 | |
1801 | void 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 | |
1883 | class Dmodel : public QtTestModel |
1884 | { |
1885 | Q_OBJECT |
1886 | public: |
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 | |
1896 | void tst_QTreeView::() |
1897 | { |
1898 | Dmodel model(10, 10); |
1899 | |
1900 | QTreeView view; |
1901 | QHeaderView * = 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 | |
1911 | void 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 | |
1925 | void 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 | |
1989 | class TestDelegate : public QStyledItemDelegate |
1990 | { |
1991 | Q_OBJECT |
1992 | public: |
1993 | using QStyledItemDelegate::QStyledItemDelegate; |
1994 | QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override |
1995 | { return QSize(200, 50); } |
1996 | }; |
1997 | |
1998 | typedef QVector<QPoint> PointList; |
1999 | |
2000 | void 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 | |
2038 | void 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 | |
2073 | void 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 | |
2112 | void 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 | |
2149 | void 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 | |
2176 | void 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 | |
2223 | void 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 | |
2257 | void tst_QTreeView::() |
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 | |
2273 | void 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 | |
2284 | void 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 | |
2330 | void 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 | |
2365 | void 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 | |
2396 | void 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 | |
2414 | void 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 | |
2432 | void 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 | |
2459 | void 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 | |
2506 | void tst_QTreeView::selectionOrderTest() |
2507 | { |
2508 | QVERIFY(static_cast<QItemSelectionModel*>(sender())->currentIndex().row() != -1); |
2509 | } |
2510 | |
2511 | void 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 |
2543 | void 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 | |
2606 | void 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 | |
2631 | void 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 | |
2640 | void 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 | |
2657 | void 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 | |
2683 | void 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 | |
2695 | void 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 | |
2711 | void tst_QTreeView::() |
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 | |
2723 | class TestTreeViewStyle : public QProxyStyle |
2724 | { |
2725 | Q_OBJECT |
2726 | public: |
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 | |
2739 | void 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(). |
2764 | void 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 | |
2785 | void 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 | |
2807 | void 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 |
2815 | void 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 | */ |
2868 | class EvilModel: public QAbstractItemModel |
2869 | { |
2870 | Q_OBJECT |
2871 | public: |
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 | |
3018 | void 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 | |
3025 | void 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 | |
3197 | void 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 | |
3213 | void 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 | |
3237 | void 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 | |
3244 | void 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 | |
3268 | void 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 | |
3533 | class task174627_TreeView : public QTreeView |
3534 | { |
3535 | Q_OBJECT |
3536 | protected slots: |
3537 | void currentChanged(const QModelIndex ¤t, const QModelIndex &) override |
3538 | { emit signalCurrentChanged(current); } |
3539 | signals: |
3540 | void signalCurrentChanged(const QModelIndex &); |
3541 | }; |
3542 | |
3543 | void 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 | |
3561 | void 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 | |
3586 | void 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 | |
3604 | void 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 | |
3648 | void 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 | |
3673 | void 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 | |
3715 | void 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 | |
3744 | void 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 | |
3782 | void 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 | |
3801 | void 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 | |
3854 | void 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 | |
3877 | void 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 | |
3907 | void 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 | |
3933 | void 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 | |
3966 | void 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 | |
3986 | void 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 | |
4024 | void 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 | |
4051 | void 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 | |
4067 | void 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 | |
4093 | void 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 | |
4127 | void 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 | |
4150 | class TreeViewQTBUG_9216 : public QTreeView |
4151 | { |
4152 | Q_OBJECT |
4153 | public: |
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 | |
4165 | void 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 | |
4189 | void 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 | |
4248 | class RemoveColumnOne : public QSortFilterProxyModel |
4249 | { |
4250 | Q_OBJECT |
4251 | public: |
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 | } |
4263 | private: |
4264 | bool m_removeColumn = false; |
4265 | }; |
4266 | |
4267 | |
4268 | void 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 | |
4301 | class Model_11466 : public QAbstractItemModel |
4302 | { |
4303 | Q_OBJECT |
4304 | public: |
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 | |
4368 | public slots: |
4369 | void slotCurrentChanged(const QModelIndex ¤t,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 | |
4401 | private: |
4402 | bool m_block = false; |
4403 | QItemSelectionModel *m_selectionModel; |
4404 | }; |
4405 | |
4406 | void 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 | |
4421 | void 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 |
4445 | void 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 | |
4481 | void 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 | |
4523 | void 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 | |
4564 | void 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) |
4581 | void 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 | |
4613 | void 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. |
4643 | class Qtbug45697TestWidget : public QWidget |
4644 | { |
4645 | Q_OBJECT |
4646 | public: |
4647 | static const int columnCount = 3; |
4648 | |
4649 | explicit Qtbug45697TestWidget(QWidget *parent = nullptr); |
4650 | int timerTick() const { return m_timerTick; } |
4651 | |
4652 | public slots: |
4653 | void slotTimer(); |
4654 | |
4655 | private: |
4656 | QTreeView *m_treeView; |
4657 | QStandardItemModel *m_model; |
4658 | QSortFilterProxyModel *m_sortFilterProxyModel; |
4659 | int m_timerTick = 0; |
4660 | }; |
4661 | |
4662 | Qtbug45697TestWidget::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 * = 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 | |
4692 | void 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 | |
4707 | void 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 | |
4716 | void 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 | |
4758 | void 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 | |
4765 | void 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 | |
4808 | class FetchMoreModel : public QStandardItemModel |
4809 | { |
4810 | Q_OBJECT |
4811 | public: |
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 | |
4837 | void 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 | |
4855 | static 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 | |
4877 | void 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 | |
4900 | void 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 | |
4970 | void 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 | |
4993 | void 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 | |
5143 | QTEST_MAIN(tst_QTreeView) |
5144 | #include "tst_qtreeview.moc" |
5145 | |