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 <QColumnView> |
30 | #include <QScrollBar> |
31 | #include <QSignalSpy> |
32 | #include <QStringListModel> |
33 | #include <QStyledItemDelegate> |
34 | #include <QTest> |
35 | #include <QtTest/private/qtesthelpers_p.h> |
36 | #include <QtWidgets/private/qcolumnviewgrip_p.h> |
37 | #include "../../../../shared/fakedirmodel.h" |
38 | |
39 | #define ANIMATION_DELAY 300 |
40 | |
41 | class tst_QColumnView : public QObject |
42 | { |
43 | Q_OBJECT |
44 | public: |
45 | tst_QColumnView(); |
46 | |
47 | private slots: |
48 | void initTestCase(); |
49 | void init(); |
50 | void rootIndex(); |
51 | void grips(); |
52 | void isIndexHidden(); |
53 | void indexAt(); |
54 | void scrollContentsBy_data(); |
55 | void scrollContentsBy(); |
56 | void scrollTo_data(); |
57 | void scrollTo(); |
58 | void moveCursor_data(); |
59 | void moveCursor(); |
60 | void selectAll(); |
61 | void clicked(); |
62 | void selectedColumns(); |
63 | void setSelection(); |
64 | void setSelectionModel(); |
65 | void visualRegionForSelection(); |
66 | |
67 | void dynamicModelChanges(); |
68 | |
69 | // grip |
70 | void moveGrip_basic(); |
71 | void moveGrip_data(); |
72 | void moveGrip(); |
73 | void doubleClick(); |
74 | void gripMoved(); |
75 | |
76 | void preview(); |
77 | void swapPreview(); |
78 | void sizes(); |
79 | void rowDelegate(); |
80 | void resize(); |
81 | void changeSameColumn(); |
82 | void parentCurrentIndex_data(); |
83 | void parentCurrentIndex(); |
84 | void pullRug_data(); |
85 | void pullRug(); |
86 | |
87 | protected slots: |
88 | void setPreviewWidget(); |
89 | |
90 | private: |
91 | QStandardItemModel m_fakeDirModel; |
92 | QModelIndex m_fakeDirHomeIndex; |
93 | }; |
94 | |
95 | class TreeModel : public QStandardItemModel |
96 | { |
97 | Q_OBJECT |
98 | public: |
99 | TreeModel() |
100 | { |
101 | for (int j = 0; j < 10; ++j) { |
102 | QStandardItem *parentItem = invisibleRootItem(); |
103 | for (int i = 0; i < 10; ++i) { |
104 | const QString iS = QString::number(i); |
105 | const QString itemText = QLatin1String("item " ) + iS; |
106 | QStandardItem *item = new QStandardItem(itemText); |
107 | parentItem->appendRow(aitem: item); |
108 | QStandardItem *item2 = new QStandardItem(itemText); |
109 | parentItem->appendRow(aitem: item2); |
110 | item2->appendRow(aitem: new QStandardItem(itemText)); |
111 | parentItem->appendRow(aitem: new QStandardItem(QLatin1String("file " ) + iS)); |
112 | parentItem = item; |
113 | } |
114 | } |
115 | } |
116 | |
117 | inline QModelIndex firstLevel() { return index(row: 0, column: 0, parent: QModelIndex()); } |
118 | inline QModelIndex secondLevel() { return index(row: 0, column: 0, parent: firstLevel()); } |
119 | inline QModelIndex thirdLevel() { return index(row: 0, column: 0, parent: secondLevel()); } |
120 | }; |
121 | |
122 | class ColumnView : public QColumnView |
123 | { |
124 | Q_OBJECT |
125 | public: |
126 | using QColumnView::QColumnView; |
127 | using QColumnView::horizontalOffset; |
128 | using QColumnView::clicked; |
129 | using QColumnView::isIndexHidden; |
130 | using QColumnView::moveCursor; |
131 | using QColumnView::scrollContentsBy; |
132 | using QColumnView::setSelection; |
133 | using QColumnView::visualRegionForSelection; |
134 | |
135 | friend class tst_QColumnView; |
136 | |
137 | QVector<QPointer<QAbstractItemView>> createdColumns; |
138 | |
139 | protected: |
140 | QAbstractItemView *createColumn(const QModelIndex &index) override |
141 | { |
142 | QAbstractItemView *view = QColumnView::createColumn(rootIndex: index); |
143 | QPointer<QAbstractItemView> savedView = view; |
144 | createdColumns.append(t: savedView); |
145 | return view; |
146 | } |
147 | }; |
148 | |
149 | tst_QColumnView::tst_QColumnView() |
150 | { |
151 | QStandardItem *homeItem = populateFakeDirModel(model: &m_fakeDirModel); |
152 | m_fakeDirHomeIndex = m_fakeDirModel.indexFromItem(item: homeItem); |
153 | } |
154 | |
155 | void tst_QColumnView::initTestCase() |
156 | { |
157 | QVERIFY(m_fakeDirHomeIndex.isValid()); |
158 | QVERIFY(m_fakeDirModel.rowCount(m_fakeDirHomeIndex) > 1); // Needs some entries in 'home'. |
159 | } |
160 | |
161 | void tst_QColumnView::init() |
162 | { |
163 | QGuiApplication::setLayoutDirection(Qt::LeftToRight); |
164 | } |
165 | |
166 | void tst_QColumnView::rootIndex() |
167 | { |
168 | ColumnView view; |
169 | // no model |
170 | view.setRootIndex(QModelIndex()); |
171 | |
172 | TreeModel model; |
173 | view.setModel(&model); |
174 | |
175 | // A top level index |
176 | QModelIndex drive = model.firstLevel(); |
177 | QVERIFY(view.visualRect(drive).isValid()); |
178 | view.setRootIndex(QModelIndex()); |
179 | QCOMPARE(view.horizontalOffset(), 0); |
180 | QCOMPARE(view.rootIndex(), QModelIndex()); |
181 | QVERIFY(view.visualRect(drive).isValid()); |
182 | |
183 | // A item under the rootIndex exists |
184 | QModelIndex home = model.thirdLevel(); |
185 | QModelIndex homeFile = model.index(row: 0, column: 0, parent: home); |
186 | int i = 0; |
187 | while (i < model.rowCount(parent: home) - 1 && !model.hasChildren(parent: homeFile)) |
188 | homeFile = model.index(row: ++i, column: 0, parent: home); |
189 | view.setRootIndex(home); |
190 | QCOMPARE(view.horizontalOffset(), 0); |
191 | QCOMPARE(view.rootIndex(), home); |
192 | QVERIFY(!view.visualRect(drive).isValid()); |
193 | QVERIFY(!view.visualRect(home).isValid()); |
194 | if (homeFile.isValid()) |
195 | QVERIFY(view.visualRect(homeFile).isValid()); |
196 | |
197 | // set root when there already is one and everything should still be ok |
198 | view.setRootIndex(home); |
199 | view.setCurrentIndex(homeFile); |
200 | view.scrollTo(index: model.index(row: 0,column: 0, parent: homeFile)); |
201 | QCOMPARE(view.horizontalOffset(), 0); |
202 | QCOMPARE(view.rootIndex(), home); |
203 | QVERIFY(!view.visualRect(drive).isValid()); |
204 | QVERIFY(!view.visualRect(home).isValid()); |
205 | if (homeFile.isValid()) |
206 | QVERIFY(view.visualRect(homeFile).isValid()); |
207 | |
208 | // |
209 | homeFile = model.thirdLevel(); |
210 | home = homeFile.parent(); |
211 | view.setRootIndex(home); |
212 | view.setCurrentIndex(homeFile); |
213 | view.show(); |
214 | i = 0; |
215 | QModelIndex two = model.index(row: 0, column: 0, parent: homeFile); |
216 | while (i < model.rowCount(parent: homeFile) - 1 && !model.hasChildren(parent: two)) |
217 | two = model.index(row: ++i, column: 0, parent: homeFile); |
218 | QTest::qWait(ANIMATION_DELAY); |
219 | view.setCurrentIndex(two); |
220 | view.scrollTo(index: two); |
221 | QTest::qWait(ANIMATION_DELAY); |
222 | QVERIFY(two.isValid()); |
223 | QVERIFY(view.horizontalOffset() != 0); |
224 | |
225 | view.setRootIndex(homeFile); |
226 | QCOMPARE(view.horizontalOffset(), 0); |
227 | } |
228 | |
229 | void tst_QColumnView::grips() |
230 | { |
231 | QColumnView view; |
232 | view.setModel(&m_fakeDirModel); |
233 | QCOMPARE(view.resizeGripsVisible(), true); |
234 | |
235 | view.setResizeGripsVisible(true); |
236 | QCOMPARE(view.resizeGripsVisible(), true); |
237 | |
238 | { |
239 | const QObjectList list = view.viewport()->children(); |
240 | for (QObject *obj : list) { |
241 | if (QAbstractItemView *view = qobject_cast<QAbstractItemView*>(object: obj)) |
242 | QVERIFY(view->cornerWidget() != nullptr); |
243 | } |
244 | } |
245 | view.setResizeGripsVisible(false); |
246 | QCOMPARE(view.resizeGripsVisible(), false); |
247 | |
248 | { |
249 | const QObjectList list = view.viewport()->children(); |
250 | for (QObject *obj : list) { |
251 | if (QAbstractItemView *view = qobject_cast<QAbstractItemView*>(object: obj)) { |
252 | if (view->isVisible()) |
253 | QVERIFY(!view->cornerWidget()); |
254 | } |
255 | } |
256 | } |
257 | |
258 | view.setResizeGripsVisible(true); |
259 | QCOMPARE(view.resizeGripsVisible(), true); |
260 | } |
261 | |
262 | void tst_QColumnView::isIndexHidden() |
263 | { |
264 | ColumnView view; |
265 | QModelIndex idx; |
266 | QCOMPARE(view.isIndexHidden(idx), false); |
267 | view.setModel(&m_fakeDirModel); |
268 | QCOMPARE(view.isIndexHidden(idx), false); |
269 | } |
270 | |
271 | void tst_QColumnView::indexAt() |
272 | { |
273 | QColumnView view; |
274 | QCOMPARE(view.indexAt(QPoint(0,0)), QModelIndex()); |
275 | view.setModel(&m_fakeDirModel); |
276 | |
277 | QModelIndex homeFile = m_fakeDirModel.index(row: 0, column: 0, parent: m_fakeDirHomeIndex); |
278 | if (!homeFile.isValid()) |
279 | return; |
280 | view.setRootIndex(m_fakeDirHomeIndex); |
281 | QRect rect = view.visualRect(index: QModelIndex()); |
282 | QVERIFY(!rect.isValid()); |
283 | rect = view.visualRect(index: homeFile); |
284 | QVERIFY(rect.isValid()); |
285 | |
286 | QModelIndex child; |
287 | for (int i = 0; i < m_fakeDirModel.rowCount(parent: m_fakeDirHomeIndex); ++i) { |
288 | child = m_fakeDirModel.index(row: i, column: 0, parent: m_fakeDirHomeIndex); |
289 | rect = view.visualRect(index: child); |
290 | QVERIFY(rect.isValid()); |
291 | if (i > 0) |
292 | QVERIFY(rect.top() > 0); |
293 | QCOMPARE(view.indexAt(rect.center()), child); |
294 | |
295 | view.selectionModel()->select(index: child, command: QItemSelectionModel::SelectCurrent); |
296 | view.setCurrentIndex(child); |
297 | QTest::qWait(ms: 200); |
298 | |
299 | // test that the second row doesn't start at 0 |
300 | if (m_fakeDirModel.rowCount(parent: child) > 0) { |
301 | child = m_fakeDirModel.index(row: 0, column: 0, parent: child); |
302 | QVERIFY(child.isValid()); |
303 | rect = view.visualRect(index: child); |
304 | QVERIFY(rect.isValid()); |
305 | QVERIFY(rect.left() > 0); |
306 | QCOMPARE(view.indexAt(rect.center()), child); |
307 | break; |
308 | } |
309 | } |
310 | } |
311 | |
312 | void tst_QColumnView::scrollContentsBy_data() |
313 | { |
314 | QTest::addColumn<bool>(name: "reverse" ); |
315 | QTest::newRow(dataTag: "normal" ) << false; |
316 | QTest::newRow(dataTag: "reverse" ) << true; |
317 | } |
318 | |
319 | void tst_QColumnView::scrollContentsBy() |
320 | { |
321 | QFETCH(bool, reverse); |
322 | ColumnView view; |
323 | if (reverse) |
324 | view.setLayoutDirection(Qt::RightToLeft); |
325 | view.scrollContentsBy(dx: -1, dy: -1); |
326 | view.scrollContentsBy(dx: 0, dy: 0); |
327 | |
328 | TreeModel model; |
329 | view.setModel(&model); |
330 | view.scrollContentsBy(dx: 0, dy: 0); |
331 | |
332 | QModelIndex home = model.thirdLevel(); |
333 | view.setCurrentIndex(home); |
334 | QTest::qWait(ANIMATION_DELAY); |
335 | view.scrollContentsBy(dx: 0, dy: 0); |
336 | } |
337 | |
338 | void tst_QColumnView::scrollTo_data() |
339 | { |
340 | QTest::addColumn<bool>(name: "reverse" ); |
341 | QTest::addColumn<bool>(name: "giveFocus" ); |
342 | /// ### add test later for giveFocus == true |
343 | QTest::newRow(dataTag: "normal" ) << false << false; |
344 | QTest::newRow(dataTag: "reverse" ) << true << false; |
345 | } |
346 | |
347 | void tst_QColumnView::scrollTo() |
348 | { |
349 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
350 | QSKIP("Wayland: This fails. Figure out why." ); |
351 | |
352 | QFETCH(bool, reverse); |
353 | QFETCH(bool, giveFocus); |
354 | QWidget topLevel; |
355 | if (reverse) |
356 | topLevel.setLayoutDirection(Qt::RightToLeft); |
357 | ColumnView view(&topLevel); |
358 | view.resize(w: 200, h: 200); |
359 | topLevel.show(); |
360 | topLevel.activateWindow(); |
361 | QTestPrivate::centerOnScreen(w: &topLevel); |
362 | QVERIFY(QTest::qWaitForWindowActive(&topLevel)); |
363 | |
364 | view.scrollTo(index: QModelIndex(), hint: QAbstractItemView::EnsureVisible); |
365 | QCOMPARE(view.horizontalOffset(), 0); |
366 | |
367 | TreeModel model; |
368 | view.setModel(&model); |
369 | view.scrollTo(index: QModelIndex(), hint: QAbstractItemView::EnsureVisible); |
370 | |
371 | QModelIndex home; |
372 | home = model.index(row: 0, column: 0, parent: home); |
373 | home = model.index(row: 0, column: 0, parent: home); |
374 | home = model.index(row: 0, column: 0, parent: home); |
375 | view.scrollTo(index: home, hint: QAbstractItemView::EnsureVisible); |
376 | view.setRootIndex(home); |
377 | |
378 | QModelIndex index = model.index(row: 0, column: 0, parent: home); |
379 | view.scrollTo(index, hint: QAbstractItemView::EnsureVisible); |
380 | QCOMPARE(view.horizontalOffset(), 0); |
381 | |
382 | // Embedded requires that at least one widget have focus |
383 | QWidget w; |
384 | w.show(); |
385 | |
386 | QCOMPARE(view.horizontalOffset(), 0); |
387 | if (giveFocus) |
388 | view.setFocus(Qt::OtherFocusReason); |
389 | else |
390 | view.clearFocus(); |
391 | |
392 | QCOMPARE(view.horizontalOffset(), 0); |
393 | QCoreApplication::processEvents(); |
394 | QCOMPARE(view.horizontalOffset(), 0); |
395 | QTRY_COMPARE(view.hasFocus(), giveFocus); |
396 | // scroll to the right |
397 | int level = 0; |
398 | int last = view.horizontalOffset(); |
399 | while (model.hasChildren(parent: index) && level < 5) { |
400 | view.setCurrentIndex(index); |
401 | QTest::qWait(ANIMATION_DELAY); |
402 | view.scrollTo(index, hint: QAbstractItemView::EnsureVisible); |
403 | QTest::qWait(ANIMATION_DELAY); |
404 | index = model.index(row: 0, column: 0, parent: index); |
405 | level++; |
406 | if (level >= 2) { |
407 | if (!reverse) { |
408 | QTRY_VERIFY(view.horizontalOffset() < 0); |
409 | qDebug() << "last=" << last |
410 | << " ; horizontalOffset= " << view.horizontalOffset(); |
411 | QTRY_VERIFY(last > view.horizontalOffset()); |
412 | } else { |
413 | QTRY_VERIFY(view.horizontalOffset() > 0); |
414 | QTRY_VERIFY(last < view.horizontalOffset()); |
415 | } |
416 | } |
417 | last = view.horizontalOffset(); |
418 | } |
419 | |
420 | // scroll to the left |
421 | int start = level; |
422 | while(index.parent().isValid() && index != view.rootIndex()) { |
423 | view.setCurrentIndex(index); |
424 | QTest::qWait(ANIMATION_DELAY); |
425 | view.scrollTo(index, hint: QAbstractItemView::EnsureVisible); |
426 | index = index.parent(); |
427 | if (start != level) { |
428 | if (!reverse) { |
429 | QTRY_VERIFY(last < view.horizontalOffset()); |
430 | } else { |
431 | if (last <= view.horizontalOffset()) { |
432 | qDebug() << "Test failure. last=" << last |
433 | << " ; horizontalOffset= " << view.horizontalOffset(); |
434 | } |
435 | QTRY_VERIFY(last > view.horizontalOffset()); |
436 | } |
437 | } |
438 | level--; |
439 | last = view.horizontalOffset(); |
440 | } |
441 | // It shouldn't automatically steal focus if it doesn't have it |
442 | QTRY_COMPARE(view.hasFocus(), giveFocus); |
443 | |
444 | // Try scrolling to something that is above the root index |
445 | home = model.index(row: 0, column: 0, parent: QModelIndex()); |
446 | QModelIndex temp = model.index(row: 1, column: 0, parent: home); |
447 | home = model.index(row: 0, column: 0, parent: home); |
448 | home = model.index(row: 0, column: 0, parent: home); |
449 | view.setRootIndex(home); |
450 | view.scrollTo(index: model.index(row: 0, column: 0, parent: home)); |
451 | QTest::qWait(ANIMATION_DELAY); |
452 | view.scrollTo(index: temp); |
453 | } |
454 | |
455 | void tst_QColumnView::moveCursor_data() |
456 | { |
457 | QTest::addColumn<bool>(name: "reverse" ); |
458 | QTest::newRow(dataTag: "normal" ) << false; |
459 | QTest::newRow(dataTag: "reverse" ) << true; |
460 | } |
461 | |
462 | void tst_QColumnView::moveCursor() |
463 | { |
464 | QFETCH(bool, reverse); |
465 | ColumnView view; |
466 | if (reverse) |
467 | view.setLayoutDirection(Qt::RightToLeft); |
468 | // don't crash |
469 | view.moveCursor(cursorAction: ColumnView::MoveUp, modifiers: Qt::NoModifier); |
470 | |
471 | // don't do anything |
472 | QCOMPARE(view.moveCursor(ColumnView::MoveEnd, Qt::NoModifier), QModelIndex()); |
473 | |
474 | view.setModel(&m_fakeDirModel); |
475 | QModelIndex ci = view.currentIndex(); |
476 | QCOMPARE(view.moveCursor(ColumnView::MoveUp, Qt::NoModifier), QModelIndex()); |
477 | QCOMPARE(view.moveCursor(ColumnView::MoveDown, Qt::NoModifier), QModelIndex()); |
478 | |
479 | // left at root |
480 | view.setCurrentIndex(m_fakeDirModel.index(row: 0,column: 0)); |
481 | ColumnView::CursorAction action = reverse ? ColumnView::MoveRight : ColumnView::MoveLeft; |
482 | QCOMPARE(view.moveCursor(action, Qt::NoModifier), m_fakeDirModel.index(0,0)); |
483 | |
484 | // left shouldn't move up |
485 | int i = 0; |
486 | ci = m_fakeDirModel.index(row: 0, column: 0); |
487 | while (i < m_fakeDirModel.rowCount() - 1 && !m_fakeDirModel.hasChildren(parent: ci)) |
488 | ci = m_fakeDirModel.index(row: ++i, column: 0); |
489 | QVERIFY(m_fakeDirModel.hasChildren(ci)); |
490 | view.setCurrentIndex(ci); |
491 | action = reverse ? ColumnView::MoveRight : ColumnView::MoveLeft; |
492 | QCOMPARE(view.moveCursor(action, Qt::NoModifier), ci); |
493 | |
494 | // now move to the left (i.e. move over one column) |
495 | view.setCurrentIndex(m_fakeDirHomeIndex); |
496 | QCOMPARE(view.moveCursor(action, Qt::NoModifier), m_fakeDirHomeIndex.parent()); |
497 | |
498 | // right |
499 | action = reverse ? ColumnView::MoveLeft : ColumnView::MoveRight; |
500 | view.setCurrentIndex(ci); |
501 | QModelIndex mc = view.moveCursor(cursorAction: action, modifiers: Qt::NoModifier); |
502 | QCOMPARE(mc, m_fakeDirModel.index(0,0, ci)); |
503 | |
504 | // for empty directories (no way to go 'right'), next one should move down |
505 | QModelIndex idx = m_fakeDirModel.index(row: 0, column: 0, parent: ci); |
506 | const int rowCount = m_fakeDirModel.rowCount(parent: ci); |
507 | while (m_fakeDirModel.hasChildren(parent: idx) && rowCount > idx.row() + 1) |
508 | idx = idx.sibling(arow: idx.row() + 1, acolumn: idx.column()); |
509 | static const char error[] = "This test requires an empty directory followed by another directory." ; |
510 | QVERIFY2(idx.isValid(), error); |
511 | QVERIFY2(!m_fakeDirModel.hasChildren(idx), error); |
512 | QVERIFY2(idx.row() + 1 < rowCount, error); |
513 | view.setCurrentIndex(idx); |
514 | mc = view.moveCursor(cursorAction: action, modifiers: Qt::NoModifier); |
515 | QCOMPARE(mc, idx.sibling(idx.row() + 1, idx.column())); |
516 | } |
517 | |
518 | void tst_QColumnView::selectAll() |
519 | { |
520 | ColumnView view; |
521 | view.selectAll(); |
522 | |
523 | view.setModel(&m_fakeDirModel); |
524 | view.selectAll(); |
525 | QVERIFY(view.selectionModel()->selectedIndexes().count() >= 0); |
526 | |
527 | view.setCurrentIndex(m_fakeDirHomeIndex); |
528 | view.selectAll(); |
529 | QVERIFY(view.selectionModel()->selectedIndexes().count() > 0); |
530 | |
531 | QModelIndex file; |
532 | for (int i = 0; i < m_fakeDirModel.rowCount(parent: m_fakeDirHomeIndex); ++i) { |
533 | if (!m_fakeDirModel.hasChildren(parent: m_fakeDirModel.index(row: i, column: 0, parent: m_fakeDirHomeIndex))) { |
534 | file = m_fakeDirModel.index(row: i, column: 0, parent: m_fakeDirHomeIndex); |
535 | break; |
536 | } |
537 | } |
538 | view.setCurrentIndex(file); |
539 | view.selectAll(); |
540 | QVERIFY(view.selectionModel()->selectedIndexes().count() > 0); |
541 | |
542 | view.setCurrentIndex(QModelIndex()); |
543 | QCOMPARE(view.selectionModel()->selectedIndexes().count(), 0); |
544 | } |
545 | |
546 | void tst_QColumnView::clicked() |
547 | { |
548 | ColumnView view; |
549 | |
550 | view.setModel(&m_fakeDirModel); |
551 | view.resize(w: 800, h: 300); |
552 | view.show(); |
553 | |
554 | view.setCurrentIndex(m_fakeDirHomeIndex); |
555 | QTest::qWait(ANIMATION_DELAY); |
556 | |
557 | QModelIndex parent = m_fakeDirHomeIndex.parent(); |
558 | QVERIFY(parent.isValid()); |
559 | |
560 | QSignalSpy clickedSpy(&view, &QAbstractItemView::clicked); |
561 | |
562 | QPoint localPoint = view.visualRect(index: m_fakeDirHomeIndex).center(); |
563 | QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: localPoint); |
564 | QCOMPARE(clickedSpy.count(), 1); |
565 | QCoreApplication::processEvents(); |
566 | |
567 | if (sizeof(qreal) != sizeof(double)) |
568 | QSKIP("Skipped due to rounding errors" ); |
569 | |
570 | for (int i = 0; i < view.createdColumns.count(); ++i) { |
571 | QAbstractItemView *column = view.createdColumns.at(i); |
572 | if (column && column->selectionModel() && (column->rootIndex() == m_fakeDirHomeIndex)) |
573 | QVERIFY(column->selectionModel()->selectedIndexes().isEmpty()); |
574 | } |
575 | } |
576 | |
577 | void tst_QColumnView::selectedColumns() |
578 | { |
579 | ColumnView view; |
580 | view.setModel(&m_fakeDirModel); |
581 | view.resize(w: 800,h: 300); |
582 | view.show(); |
583 | |
584 | view.setCurrentIndex(m_fakeDirHomeIndex); |
585 | |
586 | QTest::qWait(ANIMATION_DELAY); |
587 | |
588 | for (int i = 0; i < view.createdColumns.count(); ++i) { |
589 | QAbstractItemView *column = view.createdColumns.at(i); |
590 | if (!column) |
591 | continue; |
592 | if (!column->rootIndex().isValid() || column->rootIndex() == m_fakeDirHomeIndex) |
593 | continue; |
594 | QTRY_VERIFY(column->currentIndex().isValid()); |
595 | } |
596 | } |
597 | |
598 | void tst_QColumnView::setSelection() |
599 | { |
600 | ColumnView view; |
601 | // shouldn't do anything, it falls to the columns to handle this |
602 | QRect r; |
603 | view.setSelection(rect: r, command: QItemSelectionModel::NoUpdate); |
604 | } |
605 | |
606 | void tst_QColumnView::setSelectionModel() |
607 | { |
608 | ColumnView view; |
609 | view.setModel(&m_fakeDirModel); |
610 | view.show(); |
611 | |
612 | view.setCurrentIndex(m_fakeDirHomeIndex); |
613 | QTest::qWait(ANIMATION_DELAY); |
614 | |
615 | QItemSelectionModel *selectionModel = new QItemSelectionModel(&m_fakeDirModel); |
616 | view.setSelectionModel(selectionModel); |
617 | |
618 | bool found = false; |
619 | for (int i = 0; i < view.createdColumns.count(); ++i) { |
620 | if (view.createdColumns.at(i)->selectionModel() == selectionModel) { |
621 | found = true; |
622 | break; |
623 | } |
624 | } |
625 | QVERIFY(found); |
626 | } |
627 | |
628 | void tst_QColumnView::visualRegionForSelection() |
629 | { |
630 | ColumnView view; |
631 | QItemSelection emptyItemSelection; |
632 | QCOMPARE(QRegion(), view.visualRegionForSelection(emptyItemSelection)); |
633 | |
634 | // a region that isn't empty |
635 | view.setModel(&m_fakeDirModel); |
636 | |
637 | |
638 | QItemSelection itemSelection(m_fakeDirModel.index(row: 0, column: 0, parent: m_fakeDirHomeIndex), m_fakeDirModel.index(row: m_fakeDirModel.rowCount(parent: m_fakeDirHomeIndex) - 1, column: 0, parent: m_fakeDirHomeIndex)); |
639 | QVERIFY(QRegion() != view.visualRegionForSelection(itemSelection)); |
640 | } |
641 | |
642 | void tst_QColumnView::moveGrip_basic() |
643 | { |
644 | QColumnView view; |
645 | QColumnViewGrip *grip = new QColumnViewGrip(&view); |
646 | QSignalSpy spy(grip, &QColumnViewGrip::gripMoved); |
647 | view.setCornerWidget(grip); |
648 | int oldX = view.width(); |
649 | grip->moveGrip(offset: 10); |
650 | QCOMPARE(oldX + 10, view.width()); |
651 | grip->moveGrip(offset: -10); |
652 | QCOMPARE(oldX, view.width()); |
653 | grip->moveGrip(offset: -800); |
654 | QVERIFY(view.width() == 0 || view.width() == 1); |
655 | grip->moveGrip(offset: 800); |
656 | view.setMinimumWidth(200); |
657 | grip->moveGrip(offset: -800); |
658 | QCOMPARE(view.width(), 200); |
659 | QCOMPARE(spy.count(), 5); |
660 | } |
661 | |
662 | void tst_QColumnView::moveGrip_data() |
663 | { |
664 | QTest::addColumn<bool>(name: "reverse" ); |
665 | QTest::newRow(dataTag: "normal" ) << false; |
666 | QTest::newRow(dataTag: "reverse" ) << true; |
667 | } |
668 | |
669 | void tst_QColumnView::moveGrip() |
670 | { |
671 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
672 | QSKIP("Wayland: This fails. Figure out why." ); |
673 | |
674 | QFETCH(bool, reverse); |
675 | QWidget topLevel; |
676 | if (reverse) |
677 | topLevel.setLayoutDirection(Qt::RightToLeft); |
678 | ColumnView view(&topLevel); |
679 | TreeModel model; |
680 | view.setModel(&model); |
681 | QModelIndex home = model.thirdLevel(); |
682 | view.setCurrentIndex(home); |
683 | view.resize(w: 640, h: 200); |
684 | topLevel.show(); |
685 | QVERIFY(QTest::qWaitForWindowActive(&topLevel)); |
686 | |
687 | int columnNum = view.createdColumns.count() - 2; |
688 | QVERIFY(columnNum >= 0); |
689 | const QObjectList list = view.createdColumns[columnNum]->children(); |
690 | QColumnViewGrip *grip = nullptr; |
691 | for (QObject *obj : list) { |
692 | if ((grip = qobject_cast<QColumnViewGrip *>(object: obj))) |
693 | break; |
694 | } |
695 | if (!grip) |
696 | return; |
697 | |
698 | QAbstractItemView *column = qobject_cast<QAbstractItemView *>(object: grip->parent()); |
699 | int oldX = column->width(); |
700 | QCOMPARE(view.columnWidths().value(columnNum), oldX); |
701 | grip->moveGrip(offset: 10); |
702 | QCOMPARE(view.columnWidths().value(columnNum), (oldX + (reverse ? -10 : 10))); |
703 | } |
704 | |
705 | void tst_QColumnView::doubleClick() |
706 | { |
707 | QColumnView view; |
708 | QColumnViewGrip *grip = new QColumnViewGrip(&view); |
709 | QSignalSpy spy(grip, &QColumnViewGrip::gripMoved); |
710 | view.setCornerWidget(grip); |
711 | view.resize(w: 200, h: 200); |
712 | QCOMPARE(view.width(), 200); |
713 | QTest::mouseDClick(widget: grip, button: Qt::LeftButton); |
714 | QCOMPARE(view.width(), view.sizeHint().width()); |
715 | QCOMPARE(spy.count(), 1); |
716 | } |
717 | |
718 | void tst_QColumnView::gripMoved() |
719 | { |
720 | QColumnView view; |
721 | QColumnViewGrip *grip = new QColumnViewGrip(&view); |
722 | QSignalSpy spy(grip, &QColumnViewGrip::gripMoved); |
723 | view.setCornerWidget(grip); |
724 | view.move(ax: 300, ay: 300); |
725 | view.resize(w: 200, h: 200); |
726 | QCoreApplication::processEvents(); |
727 | |
728 | int oldWidth = view.width(); |
729 | |
730 | QTest::mousePress(widget: grip, button: Qt::LeftButton, stateKey: {}, pos: QPoint(1, 1)); |
731 | //QTest::mouseMove(grip, QPoint(grip->globalX()+50, y)); |
732 | |
733 | QPoint posNew = QPoint(grip->mapToGlobal(QPoint(1, 1)).x() + 65, 0); |
734 | QMouseEvent *event = new QMouseEvent(QEvent::MouseMove, posNew, posNew, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier); |
735 | QCoreApplication::postEvent(receiver: grip, event); |
736 | QCoreApplication::processEvents(); |
737 | QTest::mouseRelease(widget: grip, button: Qt::LeftButton); |
738 | |
739 | QTRY_COMPARE(spy.count(), 1); |
740 | QCOMPARE(view.width(), oldWidth + 65); |
741 | } |
742 | |
743 | void tst_QColumnView::preview() |
744 | { |
745 | QColumnView view; |
746 | QCOMPARE(view.previewWidget(), nullptr); |
747 | TreeModel model; |
748 | view.setModel(&model); |
749 | QCOMPARE(view.previewWidget(), nullptr); |
750 | QModelIndex home = model.index(row: 0, column: 0); |
751 | QVERIFY(home.isValid()); |
752 | QVERIFY(model.hasChildren(home)); |
753 | view.setCurrentIndex(home); |
754 | QCOMPARE(view.previewWidget(), nullptr); |
755 | |
756 | QModelIndex file; |
757 | QVERIFY(model.rowCount(home) > 0); |
758 | for (int i = 0; i < model.rowCount(parent: home); ++i) { |
759 | if (!model.hasChildren(parent: model.index(row: i, column: 0, parent: home))) { |
760 | file = model.index(row: i, column: 0, parent: home); |
761 | break; |
762 | } |
763 | } |
764 | QVERIFY(file.isValid()); |
765 | view.setCurrentIndex(file); |
766 | QVERIFY(view.previewWidget() != nullptr); |
767 | |
768 | QWidget *previewWidget = new QWidget(&view); |
769 | view.setPreviewWidget(previewWidget); |
770 | QCOMPARE(view.previewWidget(), previewWidget); |
771 | QVERIFY(previewWidget->parent() != &view); |
772 | view.setCurrentIndex(home); |
773 | |
774 | // previewWidget should be marked for deletion |
775 | QWidget *previewWidget2 = new QWidget(&view); |
776 | view.setPreviewWidget(previewWidget2); |
777 | QCOMPARE(view.previewWidget(), previewWidget2); |
778 | } |
779 | |
780 | void tst_QColumnView::swapPreview() |
781 | { |
782 | // swap the preview widget in updatePreviewWidget |
783 | QColumnView view; |
784 | QStringListModel model({ QLatin1String("test" ) }); |
785 | view.setModel(&model); |
786 | view.setCurrentIndex(view.indexAt(point: QPoint(1, 1))); |
787 | connect(sender: &view, signal: &QColumnView::updatePreviewWidget, |
788 | receiver: this, slot: &tst_QColumnView::setPreviewWidget); |
789 | view.setCurrentIndex(view.indexAt(point: QPoint(1, 1))); |
790 | QTest::qWait(ANIMATION_DELAY); |
791 | QCoreApplication::processEvents(); |
792 | } |
793 | |
794 | void tst_QColumnView::setPreviewWidget() |
795 | { |
796 | auto ptr = qobject_cast<QColumnView *>(object: sender()); |
797 | QVERIFY(ptr); |
798 | ptr->setPreviewWidget(new QWidget); |
799 | } |
800 | |
801 | void tst_QColumnView::sizes() |
802 | { |
803 | QColumnView view; |
804 | QCOMPARE(view.columnWidths().count(), 0); |
805 | |
806 | const QList<int> newSizes{ 10, 4, 50, 6 }; |
807 | |
808 | QList<int> visibleSizes; |
809 | view.setColumnWidths(newSizes); |
810 | QCOMPARE(view.columnWidths(), visibleSizes); |
811 | |
812 | view.setModel(&m_fakeDirModel); |
813 | view.setCurrentIndex(m_fakeDirHomeIndex); |
814 | |
815 | QList<int> postSizes = view.columnWidths().mid(pos: 0, alength: newSizes.count()); |
816 | QCOMPARE(postSizes, newSizes.mid(0, postSizes.count())); |
817 | |
818 | QVERIFY(view.columnWidths().count() > 1); |
819 | QList<int> smallerSizes{ 6 }; |
820 | view.setColumnWidths(smallerSizes); |
821 | QList<int> expectedSizes = newSizes; |
822 | expectedSizes[0] = 6; |
823 | postSizes = view.columnWidths().mid(pos: 0, alength: newSizes.count()); |
824 | QCOMPARE(postSizes, expectedSizes.mid(0, postSizes.count())); |
825 | } |
826 | |
827 | void tst_QColumnView::rowDelegate() |
828 | { |
829 | ColumnView view; |
830 | QStyledItemDelegate *d = new QStyledItemDelegate; |
831 | view.setItemDelegateForRow(row: 3, delegate: d); |
832 | |
833 | view.setModel(&m_fakeDirModel); |
834 | for (int i = 0; i < view.createdColumns.count(); ++i) { |
835 | QAbstractItemView *column = view.createdColumns.at(i); |
836 | QCOMPARE(column->itemDelegateForRow(3), d); |
837 | } |
838 | delete d; |
839 | } |
840 | |
841 | void tst_QColumnView::resize() |
842 | { |
843 | QWidget topLevel; |
844 | ColumnView view(&topLevel); |
845 | view.setModel(&m_fakeDirModel); |
846 | view.resize(w: 200, h: 200); |
847 | |
848 | topLevel.show(); |
849 | view.setCurrentIndex(m_fakeDirHomeIndex); |
850 | QTest::qWait(ANIMATION_DELAY); |
851 | view.resize(w: 200, h: 300); |
852 | QTest::qWait(ANIMATION_DELAY); |
853 | |
854 | QVERIFY(view.horizontalScrollBar()->maximum() != 0); |
855 | view.resize(w: view.horizontalScrollBar()->maximum() * 10, h: 300); |
856 | QTest::qWait(ANIMATION_DELAY); |
857 | QVERIFY(view.horizontalScrollBar()->maximum() <= 0); |
858 | } |
859 | |
860 | void tst_QColumnView::changeSameColumn() |
861 | { |
862 | ColumnView view; |
863 | TreeModel model; |
864 | view.setModel(&model); |
865 | QModelIndex second; |
866 | |
867 | QModelIndex home = model.secondLevel(); |
868 | //index(QDir::homePath()); |
869 | view.setCurrentIndex(home); |
870 | for (int i = 0; i < model.rowCount(parent: home.parent()); ++i) { |
871 | QModelIndex idx = model.index(row: i, column: 0, parent: home.parent()); |
872 | if (model.hasChildren(parent: idx) && idx != home) { |
873 | second = idx; |
874 | break; |
875 | } |
876 | } |
877 | QVERIFY(second.isValid()); |
878 | |
879 | const auto old = view.createdColumns; |
880 | view.setCurrentIndex(second); |
881 | |
882 | QCOMPARE(old, view.createdColumns); |
883 | } |
884 | |
885 | void tst_QColumnView::parentCurrentIndex_data() |
886 | { |
887 | QTest::addColumn<int>(name: "firstRow" ); |
888 | QTest::addColumn<int>(name: "secondRow" ); |
889 | QTest::newRow(dataTag: "down" ) << 0 << 1; |
890 | QTest::newRow(dataTag: "up" ) << 1 << 0; |
891 | } |
892 | |
893 | void tst_QColumnView::parentCurrentIndex() |
894 | { |
895 | QFETCH(int, firstRow); |
896 | QFETCH(int, secondRow); |
897 | |
898 | ColumnView view; |
899 | TreeModel model; |
900 | view.setModel(&model); |
901 | view.show(); |
902 | |
903 | QModelIndex first; |
904 | QModelIndex second; |
905 | QModelIndex third; |
906 | first = model.index(row: 0, column: 0, parent: QModelIndex()); |
907 | second = model.index(row: firstRow, column: 0, parent: first); |
908 | third = model.index(row: 0, column: 0, parent: second); |
909 | QVERIFY(first.isValid()); |
910 | QVERIFY(second.isValid()); |
911 | QVERIFY(third.isValid()); |
912 | view.setCurrentIndex(third); |
913 | QTRY_COMPARE(view.createdColumns[0]->currentIndex(), first); |
914 | QTRY_COMPARE(view.createdColumns[1]->currentIndex(), second); |
915 | QTRY_COMPARE(view.createdColumns[2]->currentIndex(), third); |
916 | |
917 | first = model.index(row: 0, column: 0, parent: QModelIndex()); |
918 | second = model.index(row: secondRow, column: 0, parent: first); |
919 | third = model.index(row: 0, column: 0, parent: second); |
920 | QVERIFY(first.isValid()); |
921 | QVERIFY(second.isValid()); |
922 | QVERIFY(third.isValid()); |
923 | view.setCurrentIndex(third); |
924 | QTRY_COMPARE(view.createdColumns[0]->currentIndex(), first); |
925 | QTRY_COMPARE(view.createdColumns[1]->currentIndex(), second); |
926 | |
927 | #ifndef Q_OS_WINRT |
928 | // The next two lines should be removed when QTBUG-22707 is resolved. |
929 | QEXPECT_FAIL("" , "QTBUG-22707" , Abort); |
930 | #endif |
931 | QVERIFY(view.createdColumns[2]); |
932 | |
933 | QTRY_COMPARE(view.createdColumns[2]->currentIndex(), third); |
934 | } |
935 | |
936 | void tst_QColumnView::pullRug_data() |
937 | { |
938 | QTest::addColumn<bool>(name: "removeModel" ); |
939 | QTest::newRow(dataTag: "model" ) << true; |
940 | QTest::newRow(dataTag: "index" ) << false; |
941 | } |
942 | |
943 | void tst_QColumnView::pullRug() |
944 | { |
945 | QFETCH(bool, removeModel); |
946 | ColumnView view; |
947 | TreeModel model; |
948 | view.setModel(&model); |
949 | QModelIndex home = model.thirdLevel(); |
950 | view.setCurrentIndex(home); |
951 | if (removeModel) |
952 | view.setModel(nullptr); |
953 | else |
954 | view.setCurrentIndex(QModelIndex()); |
955 | QTest::qWait(ANIMATION_DELAY); |
956 | // don't crash |
957 | } |
958 | |
959 | void tst_QColumnView::dynamicModelChanges() |
960 | { |
961 | struct MyItemDelegate : public QStyledItemDelegate |
962 | { |
963 | void paint(QPainter *painter, |
964 | const QStyleOptionViewItem &option, |
965 | const QModelIndex &index) const override |
966 | { |
967 | paintedIndexes += index; |
968 | QStyledItemDelegate::paint(painter, option, index); |
969 | } |
970 | |
971 | mutable QSet<QModelIndex> paintedIndexes; |
972 | |
973 | } delegate; |
974 | QStandardItemModel model; |
975 | ColumnView view; |
976 | view.setModel(&model); |
977 | view.setItemDelegate(&delegate); |
978 | QTestPrivate::centerOnScreen(w: &view); |
979 | view.show(); |
980 | |
981 | QStandardItem *item = new QStandardItem(QLatin1String("item" )); |
982 | model.appendRow(aitem: item); |
983 | |
984 | QVERIFY(QTest::qWaitForWindowExposed(&view)); //let the time for painting to occur |
985 | QTRY_COMPARE(delegate.paintedIndexes.count(), 1); |
986 | QCOMPARE(*delegate.paintedIndexes.begin(), model.index(0,0)); |
987 | } |
988 | |
989 | |
990 | QTEST_MAIN(tst_QColumnView) |
991 | #include "tst_qcolumnview.moc" |
992 | |
993 | |