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
41class tst_QColumnView : public QObject
42{
43 Q_OBJECT
44public:
45 tst_QColumnView();
46
47private 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
87protected slots:
88 void setPreviewWidget();
89
90private:
91 QStandardItemModel m_fakeDirModel;
92 QModelIndex m_fakeDirHomeIndex;
93};
94
95class TreeModel : public QStandardItemModel
96{
97 Q_OBJECT
98public:
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
122class ColumnView : public QColumnView
123{
124 Q_OBJECT
125public:
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
139protected:
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
149tst_QColumnView::tst_QColumnView()
150{
151 QStandardItem *homeItem = populateFakeDirModel(model: &m_fakeDirModel);
152 m_fakeDirHomeIndex = m_fakeDirModel.indexFromItem(item: homeItem);
153}
154
155void tst_QColumnView::initTestCase()
156{
157 QVERIFY(m_fakeDirHomeIndex.isValid());
158 QVERIFY(m_fakeDirModel.rowCount(m_fakeDirHomeIndex) > 1); // Needs some entries in 'home'.
159}
160
161void tst_QColumnView::init()
162{
163 QGuiApplication::setLayoutDirection(Qt::LeftToRight);
164}
165
166void 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
229void 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
262void 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
271void 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
312void 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
319void 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
338void 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
347void 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
455void 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
462void 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
518void 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
546void 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
577void 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
598void 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
606void 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
628void 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
642void 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
662void 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
669void 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
705void 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
718void 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
743void 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
780void 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
794void tst_QColumnView::setPreviewWidget()
795{
796 auto ptr = qobject_cast<QColumnView *>(object: sender());
797 QVERIFY(ptr);
798 ptr->setPreviewWidget(new QWidget);
799}
800
801void 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
827void 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
841void 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
860void 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
885void 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
893void 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
936void 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
943void 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
959void 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
990QTEST_MAIN(tst_QColumnView)
991#include "tst_qcolumnview.moc"
992
993

source code of qtbase/tests/auto/widgets/itemviews/qcolumnview/tst_qcolumnview.cpp