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
30#include <QListWidget>
31#include <QScrollBar>
32#include <QSignalSpy>
33#include <QStandardItemModel>
34#include <QStringListModel>
35#include <QStyledItemDelegate>
36#include <QStyleFactory>
37#include <QTest>
38#include <QTimer>
39#include <QtMath>
40
41#include <QtTest/private/qtesthelpers_p.h>
42#include <QtWidgets/private/qlistview_p.h>
43
44using namespace QTestPrivate;
45
46#if defined(Q_OS_WIN)
47# include <windows.h>
48# include <QDialog>
49# include <QGuiApplication>
50# include <QVBoxLayout>
51#include <qpa/qplatformnativeinterface.h>
52#endif // Q_OS_WIN
53
54#if defined(Q_OS_WIN)
55static inline HWND getHWNDForWidget(const QWidget *widget)
56{
57 QWindow *window = widget->windowHandle();
58 return static_cast<HWND> (QGuiApplication::platformNativeInterface()->nativeResourceForWindow("handle", window));
59}
60#endif // Q_OS_WIN
61
62Q_DECLARE_METATYPE(QAbstractItemView::ScrollMode)
63Q_DECLARE_METATYPE(QMargins)
64Q_DECLARE_METATYPE(QSize)
65using IntList = QVector<int>;
66
67static QStringList generateList(const QString &prefix, int size)
68{
69 QStringList result;
70 result.reserve(size);
71 for (int i = 0; i < size; ++i)
72 result.append(t: prefix + QString::number(i));
73 return result;
74}
75
76class PublicListView : public QListView
77{
78public:
79 using QListView::QListView;
80 using QListView::contentsSize;
81 using QListView::moveCursor;
82 using QListView::selectedIndexes;
83 using QListView::setPositionForIndex;
84 using QListView::setSelection;
85 using QListView::setViewportMargins;
86 using QListView::startDrag;
87 using QListView::viewOptions;
88 QRegion getVisualRegionForSelection() const
89 {
90 return QListView::visualRegionForSelection(selection: selectionModel()->selection());
91 }
92
93 friend class tst_QListView;
94};
95
96class tst_QListView : public QObject
97{
98 Q_OBJECT
99
100private slots:
101 void cleanup();
102 void getSetCheck();
103 void noDelegate();
104 void noModel();
105 void emptyModel();
106 void removeRows();
107 void cursorMove();
108 void hideRows();
109 void moveCursor();
110 void moveCursor2();
111 void moveCursor3();
112 void indexAt();
113 void clicked();
114 void singleSelectionRemoveRow();
115 void singleSelectionRemoveColumn();
116 void modelColumn();
117 void hideFirstRow();
118 void batchedMode();
119 void setCurrentIndex();
120 void selection_data();
121 void selection();
122 void scrollTo();
123 void scrollBarRanges();
124 void scrollBarAsNeeded_data();
125 void scrollBarAsNeeded();
126 void moveItems();
127 void wordWrap();
128#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
129 void setCurrentIndexAfterAppendRowCrash();
130#endif
131 void emptyItemSize();
132 void task203585_selectAll();
133 void task228566_infiniteRelayout();
134 void task248430_crashWith0SizedItem();
135 void task250446_scrollChanged();
136 void task196118_visualRegionForSelection();
137 void task254449_draggingItemToNegativeCoordinates();
138 void keyboardSearch();
139 void shiftSelectionWithNonUniformItemSizes();
140 void shiftSelectionWithItemAlignment();
141 void clickOnViewportClearsSelection();
142 void task262152_setModelColumnNavigate();
143 void taskQTBUG_2233_scrollHiddenItems_data();
144 void taskQTBUG_2233_scrollHiddenItems();
145 void taskQTBUG_633_changeModelData();
146 void taskQTBUG_435_deselectOnViewportClick();
147 void taskQTBUG_2678_spacingAndWrappedText();
148 void taskQTBUG_5877_skippingItemInPageDownUp();
149 void taskQTBUG_9455_wrongScrollbarRanges();
150 void styleOptionViewItem();
151 void taskQTBUG_12308_artihmeticException();
152 void taskQTBUG_12308_wrongFlowLayout();
153 void taskQTBUG_21115_scrollToAndHiddenItems_data();
154 void taskQTBUG_21115_scrollToAndHiddenItems();
155 void draggablePaintPairs_data();
156 void draggablePaintPairs();
157 void taskQTBUG_21804_hiddenItemsAndScrollingWithKeys_data();
158 void taskQTBUG_21804_hiddenItemsAndScrollingWithKeys();
159 void spacing_data();
160 void spacing();
161 void testScrollToWithHidden();
162 void testViewOptions();
163 void taskQTBUG_39902_mutualScrollBars_data();
164 void taskQTBUG_39902_mutualScrollBars();
165 void horizontalScrollingByVerticalWheelEvents();
166 void taskQTBUG_7232_AllowUserToControlSingleStep();
167 void taskQTBUG_51086_skippingIndexesInSelectedIndexes();
168 void taskQTBUG_47694_indexOutOfBoundBatchLayout();
169 void itemAlignment();
170 void internalDragDropMove_data();
171 void internalDragDropMove();
172 void scrollOnRemove_data();
173 void scrollOnRemove();
174};
175
176// Testing get/set functions
177void tst_QListView::getSetCheck()
178{
179 QListView obj1;
180 // Movement QListView::movement()
181 // void QListView::setMovement(Movement)
182 obj1.setMovement(QListView::Static);
183 QCOMPARE(QListView::Static, obj1.movement());
184 obj1.setMovement(QListView::Free);
185 QCOMPARE(QListView::Free, obj1.movement());
186 obj1.setMovement(QListView::Snap);
187 QCOMPARE(QListView::Snap, obj1.movement());
188
189 // Flow QListView::flow()
190 // void QListView::setFlow(Flow)
191 obj1.setFlow(QListView::LeftToRight);
192 QCOMPARE(QListView::LeftToRight, obj1.flow());
193 obj1.setFlow(QListView::TopToBottom);
194 QCOMPARE(QListView::TopToBottom, obj1.flow());
195
196 // ResizeMode QListView::resizeMode()
197 // void QListView::setResizeMode(ResizeMode)
198 obj1.setResizeMode(QListView::Fixed);
199 QCOMPARE(QListView::Fixed, obj1.resizeMode());
200 obj1.setResizeMode(QListView::Adjust);
201 QCOMPARE(QListView::Adjust, obj1.resizeMode());
202
203 // LayoutMode QListView::layoutMode()
204 // void QListView::setLayoutMode(LayoutMode)
205 obj1.setLayoutMode(QListView::SinglePass);
206 QCOMPARE(QListView::SinglePass, obj1.layoutMode());
207 obj1.setLayoutMode(QListView::Batched);
208 QCOMPARE(QListView::Batched, obj1.layoutMode());
209
210 // int QListView::spacing()
211 // void QListView::setSpacing(int)
212 obj1.setSpacing(0);
213 QCOMPARE(0, obj1.spacing());
214 obj1.setSpacing(std::numeric_limits<int>::min());
215 QCOMPARE(std::numeric_limits<int>::min(), obj1.spacing());
216 obj1.setSpacing(std::numeric_limits<int>::max());
217 QCOMPARE(std::numeric_limits<int>::max(), obj1.spacing());
218
219 // ViewMode QListView::viewMode()
220 // void QListView::setViewMode(ViewMode)
221 obj1.setViewMode(QListView::ListMode);
222 QCOMPARE(QListView::ListMode, obj1.viewMode());
223 obj1.setViewMode(QListView::IconMode);
224 QCOMPARE(QListView::IconMode, obj1.viewMode());
225
226 // int QListView::modelColumn()
227 // void QListView::setModelColumn(int)
228 obj1.setModelColumn(0);
229 QCOMPARE(0, obj1.modelColumn());
230 obj1.setModelColumn(std::numeric_limits<int>::min());
231 QCOMPARE(0, obj1.modelColumn()); // Less than 0 => 0
232 obj1.setModelColumn(std::numeric_limits<int>::max());
233 QCOMPARE(0, obj1.modelColumn()); // No model => 0
234
235 // bool QListView::uniformItemSizes()
236 // void QListView::setUniformItemSizes(bool)
237 obj1.setUniformItemSizes(false);
238 QCOMPARE(false, obj1.uniformItemSizes());
239 obj1.setUniformItemSizes(true);
240 QCOMPARE(true, obj1.uniformItemSizes());
241
242 // make sure setViewMode() doesn't reset resizeMode
243 obj1.clearPropertyFlags();
244 obj1.setResizeMode(QListView::Adjust);
245 obj1.setViewMode(QListView::IconMode);
246 QCOMPARE(obj1.resizeMode(), QListView::Adjust);
247
248 obj1.setWordWrap(false);
249 QCOMPARE(false, obj1.wordWrap());
250 obj1.setWordWrap(true);
251 QCOMPARE(true, obj1. wordWrap());
252}
253
254class QtTestModel: public QAbstractListModel
255{
256 Q_OBJECT
257public:
258 QtTestModel(int rc, int cc, QObject *parent = nullptr)
259 : QAbstractListModel(parent), rCount(rc), cCount(cc) {}
260 int rowCount(const QModelIndex &) const override { return rCount; }
261 int columnCount(const QModelIndex &) const override { return cCount; }
262
263 QVariant data(const QModelIndex &idx, int role) const override
264 {
265 if (!m_icon.isNull() && role == Qt::DecorationRole)
266 return m_icon;
267 if (role != Qt::DisplayRole)
268 return QVariant();
269
270 if (idx.row() < 0 || idx.column() < 0 || idx.column() >= cCount
271 || idx.row() >= rCount) {
272 wrongIndex = true;
273 qWarning(msg: "got invalid modelIndex %d/%d", idx.row(), idx.column());
274 }
275 return QString::number(idx.row()) + QLatin1Char('/') + QString::number(idx.column());
276 }
277
278 void removeLastRow()
279 {
280 beginRemoveRows(parent: QModelIndex(), first: rCount - 2, last: rCount - 1);
281 --rCount;
282 endRemoveRows();
283 }
284
285 void removeAllRows()
286 {
287 beginRemoveRows(parent: QModelIndex(), first: 0, last: rCount - 1);
288 rCount = 0;
289 endRemoveRows();
290 }
291
292 void setDataIcon(const QIcon &icon)
293 {
294 m_icon = icon;
295 }
296
297 QIcon m_icon;
298 int rCount, cCount;
299 mutable bool wrongIndex = false;
300};
301
302class ScrollPerItemListView : public QListView
303{
304 Q_OBJECT
305public:
306 explicit ScrollPerItemListView(QWidget *parent = nullptr)
307 : QListView(parent)
308 {
309 // Force per item scroll mode since it comes from the style by default
310 setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
311 setHorizontalScrollMode(QAbstractItemView::ScrollPerItem);
312 }
313};
314
315void tst_QListView::cleanup()
316{
317 QTRY_VERIFY(QApplication::topLevelWidgets().isEmpty());
318}
319
320void tst_QListView::noDelegate()
321{
322 QtTestModel model(10, 10);
323 QListView view;
324 view.setModel(&model);
325 view.setItemDelegate(nullptr);
326 view.show();
327 QVERIFY(QTest::qWaitForWindowExposed(&view));
328}
329
330void tst_QListView::noModel()
331{
332 QListView view;
333 view.show();
334 view.setRowHidden(row: 0, hide: true);
335 // no model -> not able to hide a row
336 QVERIFY(!view.isRowHidden(0));
337}
338
339void tst_QListView::emptyModel()
340{
341 QtTestModel model(0, 0);
342 QListView view;
343 view.setModel(&model);
344 view.show();
345 QVERIFY(!model.wrongIndex);
346}
347
348void tst_QListView::removeRows()
349{
350 QtTestModel model(10, 10);
351 QListView view;
352 view.setModel(&model);
353 view.show();
354
355 model.removeLastRow();
356 QVERIFY(!model.wrongIndex);
357
358 model.removeAllRows();
359 QVERIFY(!model.wrongIndex);
360}
361
362void tst_QListView::cursorMove()
363{
364 int rows = 6*6;
365 int columns = 6;
366
367 QStandardItemModel model(rows, columns);
368 QWidget topLevel;
369 QListView view(&topLevel);
370 view.setModel(&model);
371
372 for (int j = 0; j < columns; ++j) {
373 const QString postfix = QLatin1Char(',') + QString::number(j) + QLatin1Char(']');
374 view.setModelColumn(j);
375 for (int i = 0; i < rows; ++i) {
376 QModelIndex index = model.index(row: i, column: j);
377 model.setData(index, value: QLatin1Char('[') + QString::number(i) + postfix);
378 view.setCurrentIndex(index);
379 QTRY_COMPARE(view.currentIndex(), index);
380 }
381 }
382
383 QSize cellsize(60, 25);
384 int gap = 1; // compensate for the scrollbars
385 int displayColumns = 6;
386
387 view.resize(w: (displayColumns + gap) * cellsize.width(),
388 h: int((ceil(x: double(rows) / displayColumns) + gap) * cellsize.height()));
389 view.setResizeMode(QListView::Adjust);
390 view.setGridSize(cellsize);
391 view.setViewMode(QListView::IconMode);
392 view.doItemsLayout();
393 topLevel.show();
394
395 static const Qt::Key keymoves[] {
396 Qt::Key_Up, Qt::Key_Up, Qt::Key_Right, Qt::Key_Right, Qt::Key_Up,
397 Qt::Key_Left, Qt::Key_Left, Qt::Key_Up, Qt::Key_Down, Qt::Key_Up,
398 Qt::Key_Up, Qt::Key_Up, Qt::Key_Up, Qt::Key_Up, Qt::Key_Up,
399 Qt::Key_Left, Qt::Key_Left, Qt::Key_Up, Qt::Key_Down
400 };
401
402 int lastRow = rows / displayColumns - 1;
403 int lastColumn = displayColumns - 1;
404
405 int displayRow = lastRow;
406 int displayColumn = lastColumn - (rows % displayColumns);
407
408 QCoreApplication::processEvents();
409 for (Qt::Key key : keymoves) {
410 QTest::keyClick(widget: &view, key);
411 switch (key) {
412 case Qt::Key_Up:
413 displayRow = qMax(a: 0, b: displayRow - 1);
414 break;
415 case Qt::Key_Down:
416 displayRow = qMin(a: rows / displayColumns - 1, b: displayRow + 1);
417 break;
418 case Qt::Key_Left:
419 if (displayColumn > 0) {
420 displayColumn--;
421 } else {
422 if (displayRow > 0) {
423 displayRow--;
424 displayColumn = lastColumn;
425 }
426 }
427 break;
428 case Qt::Key_Right:
429 if (displayColumn < lastColumn) {
430 displayColumn++;
431 } else {
432 if (displayRow < lastRow) {
433 displayRow++;
434 displayColumn = 0;
435 }
436 }
437 break;
438 default:
439 QVERIFY(false);
440 }
441
442 QCoreApplication::processEvents();
443
444 int row = displayRow * displayColumns + displayColumn;
445 int column = columns - 1;
446 QModelIndex index = model.index(row, column);
447 QCOMPARE(view.currentIndex().row(), row);
448 QCOMPARE(view.currentIndex().column(), column);
449 QCOMPARE(view.currentIndex(), index);
450 }
451}
452
453void tst_QListView::hideRows()
454{
455 QtTestModel model(10, 10);
456 QListView view;
457 view.setModel(&model);
458 view.show();
459
460 // hide then show
461 QVERIFY(!view.isRowHidden(2));
462 view.setRowHidden(row: 2, hide: true);
463 QVERIFY(view.isRowHidden(2));
464 view.setRowHidden(row: 2, hide: false);
465 QVERIFY(!view.isRowHidden(2));
466
467 // re show same row
468 QVERIFY(!view.isRowHidden(2));
469 view.setRowHidden(row: 2, hide: false);
470 QVERIFY(!view.isRowHidden(2));
471
472 // double hidding
473 QVERIFY(!view.isRowHidden(2));
474 view.setRowHidden(row: 2, hide: true);
475 QVERIFY(view.isRowHidden(2));
476 view.setRowHidden(row: 2, hide: true);
477 QVERIFY(view.isRowHidden(2));
478 view.setRowHidden(row: 2, hide: false);
479 QVERIFY(!view.isRowHidden(2));
480
481 // show in per-item mode, then hide the first row
482 view.setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
483 QVERIFY(!view.isRowHidden(0));
484 view.setRowHidden(row: 0, hide: true);
485 QVERIFY(view.isRowHidden(0));
486 view.setRowHidden(row: 0, hide: false);
487 QVERIFY(!view.isRowHidden(0));
488
489 QStandardItemModel sim;
490 QStandardItem *root = new QStandardItem("Root row");
491 for (int i = 0;i < 5; i++)
492 root->appendRow(aitem: new QStandardItem(QLatin1String("Row ") + QString::number(i)));
493 sim.appendRow(aitem: root);
494 view.setModel(&sim);
495 view.setRootIndex(root->index());
496 QVERIFY(!view.isRowHidden(0));
497 view.setRowHidden(row: 0, hide: true);
498 QVERIFY(view.isRowHidden(0));
499 view.setRowHidden(row: 0, hide: false);
500 QVERIFY(!view.isRowHidden(0));
501}
502
503
504void tst_QListView::moveCursor()
505{
506 QtTestModel model(10, 10);
507 QListView view;
508 view.setModel(&model);
509
510 QTest::keyClick(widget: &view, key: Qt::Key_Down);
511
512 view.setModel(nullptr);
513 view.setModel(&model);
514 view.setRowHidden(row: 0, hide: true);
515
516 QTest::keyClick(widget: &view, key: Qt::Key_Down);
517 QCOMPARE(view.selectionModel()->currentIndex(), model.index(1, 0));
518}
519
520void tst_QListView::moveCursor2()
521{
522 QtTestModel model(100, 1);
523 QPixmap pm(32, 32);
524 pm.fill(fillColor: Qt::green);
525 model.setDataIcon(QIcon(pm));
526
527 PublicListView vu;
528 vu.setModel(&model);
529 vu.setIconSize(QSize(36,48));
530 vu.setGridSize(QSize(34,56));
531 //Standard framesize is 1. If Framesize > 2 increase size
532 int frameSize = qApp->style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth);
533 vu.resize(w: 300 + frameSize * 2, h: 300);
534 vu.setFlow(QListView::LeftToRight);
535 vu.setMovement(QListView::Static);
536 vu.setWrapping(true);
537 vu.setViewMode(QListView::IconMode);
538 vu.setLayoutMode(QListView::Batched);
539 vu.show();
540 vu.selectionModel()->setCurrentIndex(index: model.index(row: 0,column: 0), command: QItemSelectionModel::SelectCurrent);
541 QCoreApplication::processEvents();
542
543 QModelIndex idx = vu.moveCursor(cursorAction: PublicListView::MoveHome, modifiers: Qt::NoModifier);
544 QCOMPARE(idx, model.index(0, 0));
545 idx = vu.moveCursor(cursorAction: PublicListView::MoveDown, modifiers: Qt::NoModifier);
546 QCOMPARE(idx, model.index(8, 0));
547}
548
549void tst_QListView::moveCursor3()
550{
551 //this tests is for task 159792
552 //it tests that navigation works even with non uniform item sizes
553 QListView view;
554 QStandardItemModel model(0, 1);
555 QStandardItem *i1 = new QStandardItem("First item, long name");
556 QStandardItem *i2 = new QStandardItem("2nd item");
557 QStandardItem *i3 = new QStandardItem("Third item, long name");
558 i1->setSizeHint(QSize(200, 32));
559 model.appendRow(aitem: i1);
560 model.appendRow(aitem: i2);
561 model.appendRow(aitem: i3);
562 view.setModel(&model);
563
564 view.setCurrentIndex(model.index(row: 0, column: 0));
565
566 QCOMPARE(view.selectionModel()->currentIndex(), model.index(0, 0));
567 QTest::keyClick(widget: &view, key: Qt::Key_Down);
568 QCOMPARE(view.selectionModel()->currentIndex(), model.index(1, 0));
569 QTest::keyClick(widget: &view, key: Qt::Key_Down);
570 QCOMPARE(view.selectionModel()->currentIndex(), model.index(2, 0));
571 QTest::keyClick(widget: &view, key: Qt::Key_Up);
572 QCOMPARE(view.selectionModel()->currentIndex(), model.index(1, 0));
573 QTest::keyClick(widget: &view, key: Qt::Key_Up);
574 QCOMPARE(view.selectionModel()->currentIndex(), model.index(0, 0));
575}
576
577
578class QListViewShowEventListener : public QListView
579{
580 Q_OBJECT
581public:
582 using QListView::QListView;
583 void showEvent(QShowEvent *e) override
584 {
585 QListView::showEvent(event: e);
586 int columnwidth = sizeHintForColumn(column: 0);
587 QSize sz = sizeHintForIndex(index: model()->index(row: 0, column: 0));
588
589 // This should retrieve a model index in the 2nd section
590 m_index = indexAt(p: QPoint(columnwidth +2, sz.height() / 2));
591 m_shown = true;
592 }
593
594 QModelIndex m_index;
595 bool m_shown = false;
596
597};
598
599void tst_QListView::indexAt()
600{
601 QtTestModel model(2, 1);
602 QListView view;
603 view.setModel(&model);
604 view.setViewMode(QListView::ListMode);
605 view.setFlow(QListView::TopToBottom);
606
607 QSize sz = view.sizeHintForIndex(index: model.index(row: 0, column: 0));
608 QModelIndex index;
609 index = view.indexAt(p: QPoint(20, 0));
610 QVERIFY(index.isValid());
611 QCOMPARE(index.row(), 0);
612
613 index = view.indexAt(p: QPoint(20, sz.height()));
614 QVERIFY(index.isValid());
615 QCOMPARE(index.row(), 1);
616
617 index = view.indexAt(p: QPoint(20, 2 * sz.height()));
618 QVERIFY(!index.isValid());
619
620 // Check when peeking out of the viewport bounds
621 index = view.indexAt(p: QPoint(view.viewport()->rect().width(), 0));
622 QVERIFY(!index.isValid());
623 index = view.indexAt(p: QPoint(-1, 0));
624 QVERIFY(!index.isValid());
625 index = view.indexAt(p: QPoint(20, view.viewport()->rect().height()));
626 QVERIFY(!index.isValid());
627 index = view.indexAt(p: QPoint(20, -1));
628 QVERIFY(!index.isValid());
629
630 model.rCount = 30;
631 QListViewShowEventListener view2;
632 // Set the height to a small enough value so that it wraps to a new section.
633 view2.resize(w: 300, h: 100);
634 view2.setModel(&model);
635 view2.setFlow(QListView::TopToBottom);
636 view2.setViewMode(QListView::ListMode);
637 view2.setWrapping(true);
638 // We really want to make sure it is shown, because the layout won't be known until it is shown
639 view2.show();
640 QVERIFY(QTest::qWaitForWindowExposed(&view2));
641 QTRY_VERIFY(view2.m_shown);
642
643 QVERIFY(view2.m_index.isValid());
644 QVERIFY(view2.m_index.row() != 0);
645}
646
647void tst_QListView::clicked()
648{
649 QtTestModel model(10, 2);
650 QListView view;
651 view.setModel(&model);
652 view.show();
653 QVERIFY(QTest::qWaitForWindowExposed(&view));
654
655 QModelIndex firstIndex = model.index(row: 0, column: 0, parent: QModelIndex());
656 QVERIFY(firstIndex.isValid());
657 int itemHeight = view.visualRect(index: firstIndex).height();
658 view.resize(w: 200, h: itemHeight * (model.rCount + 1));
659
660 for (int i = 0; i < model.rCount; ++i) {
661 QPoint p(5, 1 + itemHeight * i);
662 QModelIndex index = view.indexAt(p);
663 if (!index.isValid())
664 continue;
665 QSignalSpy spy(&view, &QListView::clicked);
666 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
667 QCOMPARE(spy.count(), 1);
668 }
669}
670
671void tst_QListView::singleSelectionRemoveRow()
672{
673 QStringListModel model({"item1", "item2", "item3", "item4"});
674 QListView view;
675 view.setModel(&model);
676 view.show();
677
678 QModelIndex index;
679 view.setCurrentIndex(model.index(row: 1));
680 index = view.currentIndex();
681 QCOMPARE(view.model()->data(index).toString(), QString("item2"));
682
683 model.removeRow(arow: 1);
684 index = view.currentIndex();
685 QCOMPARE(view.model()->data(index).toString(), QString("item3"));
686
687 model.removeRow(arow: 0);
688 index = view.currentIndex();
689 QCOMPARE(view.model()->data(index).toString(), QString("item3"));
690}
691
692void tst_QListView::singleSelectionRemoveColumn()
693{
694 int numCols = 3;
695 int numRows = 3;
696 QStandardItemModel model(numCols, numRows);
697 for (int r = 0; r < numRows; ++r) {
698 const QString prefix = QString::number(r) + QLatin1Char(',');
699 for (int c = 0; c < numCols; ++c)
700 model.setData(index: model.index(row: r, column: c), value: prefix + QString::number(c));
701 }
702
703 QListView view;
704 view.setModel(&model);
705 view.show();
706
707 QModelIndex index;
708 view.setCurrentIndex(model.index(row: 1, column: 1));
709 index = view.currentIndex();
710 QCOMPARE(view.model()->data(index).toString(), QString("1,1"));
711
712 model.removeColumn(acolumn: 1);
713 index = view.currentIndex();
714 QCOMPARE(view.model()->data(index).toString(), QString("1,0"));
715
716 model.removeColumn(acolumn: 0);
717 index = view.currentIndex();
718 QCOMPARE(view.model()->data(index).toString(), QString("1,2"));
719}
720
721void tst_QListView::modelColumn()
722{
723 int numCols = 3;
724 int numRows = 3;
725 QStandardItemModel model(numCols, numRows);
726 for (int r = 0; r < numRows; ++r) {
727 const QString prefix = QString::number(r) + QLatin1Char(',');
728 for (int c = 0; c < numCols; ++c)
729 model.setData(index: model.index(row: r, column: c), value: prefix + QString::number(c));
730 }
731
732 QListView view;
733 view.setModel(&model);
734
735
736 //
737 // Set and get with a valid model
738 //
739
740 // Default is column 0
741 QCOMPARE(view.modelColumn(), 0);
742
743 view.setModelColumn(0);
744 QCOMPARE(view.modelColumn(), 0);
745 view.setModelColumn(1);
746 QCOMPARE(view.modelColumn(), 1);
747 view.setModelColumn(2);
748 QCOMPARE(view.modelColumn(), 2);
749
750 // Out of bound cases should not modify the modelColumn
751 view.setModelColumn(-1);
752 QCOMPARE(view.modelColumn(), 2);
753 view.setModelColumn(std::numeric_limits<int>::max());
754 QCOMPARE(view.modelColumn(), 2);
755
756
757 // See if it displays the right column using indexAt()...
758 view.resize(w: 400,h: 400);
759 view.show();
760
761 for (int c = 0; c < 3; ++c) {
762 view.setModelColumn(c);
763 int startrow = 0;
764 for (int y = 0; y < view.height(); ++y) {
765 QModelIndex idx = view.indexAt( p: QPoint(1, y) );
766 if (idx.row() == startrow + 1) ++startrow;
767 else if (idx.row() == -1) break;
768 QCOMPARE(idx.row(), startrow);
769 QCOMPARE(idx.column(), c);
770 }
771 QCOMPARE(startrow, 2);
772 }
773}
774
775void tst_QListView::hideFirstRow()
776{
777 QStringListModel model(generateList(prefix: QLatin1String("item"), size: 100));
778
779 QListView view;
780 view.setModel(&model);
781 view.setUniformItemSizes(true);
782 view.setRowHidden(row: 0, hide: true);
783 view.show();
784 QVERIFY(QTest::qWaitForWindowExposed(&view));
785}
786
787static int modelIndexCount(const QAbstractItemView *view)
788{
789 QBitArray ba;
790 for (int y = 0, height = view->height(); y < height; ++y) {
791 const QModelIndex idx = view->indexAt( point: QPoint(1, y) );
792 if (!idx.isValid())
793 break;
794 if (idx.row() >= ba.size())
795 ba.resize(size: idx.row() + 1);
796 ba.setBit(i: idx.row(), val: true);
797 }
798 return ba.size();
799}
800
801void tst_QListView::batchedMode()
802{
803 const int rowCount = 3;
804
805 QStringListModel model(generateList(prefix: QLatin1String("item "), size: rowCount));
806
807 QListView view;
808 view.setWindowTitle(QTest::currentTestFunction());
809 view.setModel(&model);
810 view.setUniformItemSizes(true);
811 view.setViewMode(QListView::ListMode);
812 view.setLayoutMode(QListView::Batched);
813 view.setBatchSize(2);
814 view.resize(w: 200, h: 400);
815 view.show();
816 QVERIFY(QTest::qWaitForWindowExposed(&view));
817
818 QTRY_COMPARE(modelIndexCount(&view), rowCount);
819
820 // Test the dynamic listview too.
821 view.setViewMode(QListView::IconMode);
822 view.setLayoutMode(QListView::Batched);
823 view.setFlow(QListView::TopToBottom);
824 view.setBatchSize(2);
825
826 QTRY_COMPARE(modelIndexCount(&view), rowCount);
827}
828
829void tst_QListView::setCurrentIndex()
830{
831 QStringListModel model(generateList(prefix: QLatin1String("item "), size: 20));
832
833 ScrollPerItemListView view;
834 view.setModel(&model);
835 view.resize(w: 220,h: 182);
836 view.show();
837
838 for (int pass = 0; pass < 2; ++pass) {
839 view.setFlow(pass == 0 ? QListView::TopToBottom : QListView::LeftToRight);
840 QScrollBar *sb = pass == 0 ? view.verticalScrollBar() : view.horizontalScrollBar();
841 for (const QSize &gridSize : {QSize(), QSize(200, 38)}) {
842 if (pass == 1 && !gridSize.isValid()) // the width of an item varies, so it might jump two times
843 continue;
844 view.setGridSize(gridSize);
845
846 QCoreApplication::processEvents();
847 int offset = sb->value();
848
849 // first "scroll" down, verify that we scroll one step at a time
850 int i = 0;
851 for (i = 0; i < 20; ++i) {
852 QModelIndex idx = model.index(row: i,column: 0);
853 view.setCurrentIndex(idx);
854 if (offset != sb->value()) {
855 // If it has scrolled, it should have scrolled only by one.
856 QCOMPARE(sb->value(), offset + 1);
857 ++offset;
858 }
859 }
860
861 --i; // item 20 does not exist
862 // and then "scroll" up, verify that we scroll one step at a time
863 for (; i >= 0; --i) {
864 QModelIndex idx = model.index(row: i,column: 0);
865 view.setCurrentIndex(idx);
866 if (offset != sb->value()) {
867 // If it has scrolled, it should have scrolled only by one.
868 QCOMPARE(sb->value(), offset - 1);
869 --offset;
870 }
871 }
872 }
873 }
874 while (model.rowCount()) {
875 view.setCurrentIndex(model.index(row: model.rowCount() - 1, column: 0));
876 model.removeRow(arow: model.rowCount() - 1);
877 }
878}
879
880class TestDelegate : public QStyledItemDelegate
881{
882public:
883 explicit TestDelegate(QObject *parent, const QSize &sizeHint = QSize(50, 50))
884 : QStyledItemDelegate(parent), m_sizeHint(sizeHint) {}
885 QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override { return m_sizeHint; }
886
887 const QSize m_sizeHint;
888};
889
890void tst_QListView::selection_data()
891{
892 QTest::addColumn<int>(name: "itemCount");
893 QTest::addColumn<QListView::ViewMode>(name: "viewMode");
894 QTest::addColumn<QListView::Flow>(name: "flow");
895 QTest::addColumn<bool>(name: "wrapping");
896 QTest::addColumn<int>(name: "spacing");
897 QTest::addColumn<QSize>(name: "gridSize");
898 QTest::addColumn<IntList>(name: "hiddenRows");
899 QTest::addColumn<QRect>(name: "selectionRect");
900 QTest::addColumn<IntList>(name: "expectedItems");
901
902 QTest::newRow(dataTag: "select all")
903 << 4 // itemCount
904 << QListView::ListMode
905 << QListView::TopToBottom
906 << false // wrapping
907 << 0 // spacing
908 << QSize() // gridSize
909 << IntList() // hiddenRows
910 << QRect(0, 0, 10, 200) // selection rectangle
911 << (IntList() << 0 << 1 << 2 << 3); // expected items
912
913 QTest::newRow(dataTag: "select below, (on viewport)")
914 << 4 // itemCount
915 << QListView::ListMode
916 << QListView::TopToBottom
917 << false // wrapping
918 << 0 // spacing
919 << QSize() // gridSize
920 << IntList() // hiddenRows
921 << QRect(10, 250, 1, 1) // selection rectangle
922 << IntList(); // expected items
923
924 QTest::newRow(dataTag: "select below 2, (on viewport)")
925 << 4 // itemCount
926 << QListView::ListMode
927 << QListView::TopToBottom
928 << true // wrapping
929 << 0 // spacing
930 << QSize() // gridSize
931 << IntList() // hiddenRows
932 << QRect(10, 250, 1, 1) // selection rectangle
933 << IntList(); // expected items
934
935 QTest::newRow(dataTag: "select to the right, (on viewport)")
936 << 40 // itemCount
937 << QListView::ListMode
938 << QListView::TopToBottom
939 << true // wrapping
940 << 0 // spacing
941 << QSize() // gridSize
942 << IntList() // hiddenRows
943 << QRect(300, 10, 1, 1) // selection rectangle
944 << IntList(); // expected items
945
946 QTest::newRow(dataTag: "select to the right 2, (on viewport)")
947 << 40 // itemCount
948 << QListView::ListMode
949 << QListView::TopToBottom
950 << true // wrapping
951 << 0 // spacing
952 << QSize() // gridSize
953 << IntList() // hiddenRows
954 << QRect(300, 0, 1, 300) // selection rectangle
955 << IntList(); // expected items
956
957 QTest::newRow(dataTag: "select inside contents, (on viewport)")
958 << 35 // itemCount
959 << QListView::ListMode
960 << QListView::TopToBottom
961 << true // wrapping
962 << 0 // spacing
963 << QSize() // gridSize
964 << IntList() // hiddenRows
965 << QRect(175, 275, 1, 1) // selection rectangle
966 << IntList(); // expected items
967
968 QTest::newRow(dataTag: "select a tall rect in LeftToRight flow, wrap items")
969 << 70 // itemCount
970 << QListView::ListMode
971 << QListView::LeftToRight
972 << true // wrapping
973 << 0 // spacing
974 << QSize() // gridSize
975 << IntList() // hiddenRows
976 << QRect(90, 90, 1, 100) // selection rectangle
977 << (IntList() // expected items
978 << 11 << 12 << 13 << 14 << 15 << 16 << 17 << 18 << 19
979 << 20 << 21 << 22 << 23 << 24 << 25 << 26 << 27 << 28 << 29
980 << 30 << 31);
981
982 QTest::newRow(dataTag: "select a wide rect in LeftToRight, wrap items")
983 << 70 // itemCount
984 << QListView::ListMode
985 << QListView::LeftToRight
986 << true // wrapping
987 << 0 // spacing
988 << QSize() // gridSize
989 << IntList() // hiddenRows
990 << QRect(90, 90, 200, 1) // selection rectangle
991 << (IntList() // expected items
992 << 11 << 12 << 13 << 14 << 15);
993
994 QTest::newRow(dataTag: "select a wide negative rect in LeftToRight flow, wrap items")
995 << 70 // itemCount
996 << QListView::ListMode
997 << QListView::LeftToRight
998 << true // wrapping
999 << 0 // spacing
1000 << QSize() // gridSize
1001 << IntList() // hiddenRows
1002 << QRect(290, 90, -200, 1) // selection rectangle
1003 << (IntList() // expected items
1004 << 11 << 12 << 13 << 14 << 15);
1005
1006 QTest::newRow(dataTag: "select a tall rect in TopToBottom flow, wrap items")
1007 << 70 // itemCount
1008 << QListView::ListMode
1009 << QListView::TopToBottom
1010 << true // wrapping
1011 << 0 // spacing
1012 << QSize() // gridSize
1013 << IntList() // hiddenRows
1014 << QRect(90, 90, 1, 100) // selection rectangle
1015 << (IntList() // expected items
1016 << 11
1017 << 12
1018 << 13);
1019
1020 QTest::newRow(dataTag: "select a tall negative rect in TopToBottom flow, wrap items")
1021 << 70 // itemCount
1022 << QListView::ListMode
1023 << QListView::TopToBottom
1024 << true // wrapping
1025 << 0 // spacing
1026 << QSize() // gridSize
1027 << IntList() // hiddenRows
1028 << QRect(90, 190, 1, -100) // selection rectangle
1029 << (IntList() // expected items
1030 << 11
1031 << 12
1032 << 13);
1033
1034 QTest::newRow(dataTag: "select a wide rect in TopToBottom, wrap items")
1035 << 70 // itemCount
1036 << QListView::ListMode
1037 << QListView::TopToBottom
1038 << true // wrapping
1039 << 0 // spacing
1040 << QSize() // gridSize
1041 << IntList() // hiddenRows
1042 << QRect(90, 90, 100, 1) // selection rectangle
1043 << (IntList() // expected items
1044 << 20 << 30
1045 << 11 << 21 << 31
1046 << 12 << 22
1047 << 13 << 23
1048 << 14 << 24
1049 << 15 << 25
1050 << 16 << 26
1051 << 17 << 27
1052 << 18 << 28
1053 << 19 << 29);
1054}
1055
1056void tst_QListView::selection()
1057{
1058 QFETCH(int, itemCount);
1059 QFETCH(QListView::ViewMode, viewMode);
1060 QFETCH(QListView::Flow, flow);
1061 QFETCH(bool, wrapping);
1062 QFETCH(int, spacing);
1063 QFETCH(QSize, gridSize);
1064 QFETCH(const IntList, hiddenRows);
1065 QFETCH(QRect, selectionRect);
1066 QFETCH(const IntList, expectedItems);
1067
1068 QWidget topLevel;
1069 PublicListView v(&topLevel);
1070 QtTestModel model(itemCount, 1);
1071
1072 // avoid scrollbar size mismatches among different styles
1073 v.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1074 v.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1075
1076 v.setItemDelegate(new TestDelegate(&v));
1077 v.setModel(&model);
1078 v.setViewMode(viewMode);
1079 v.setFlow(flow);
1080 v.setWrapping(wrapping);
1081 v.setResizeMode(QListView::Adjust);
1082 v.setSpacing(spacing);
1083 if (gridSize.isValid())
1084 v.setGridSize(gridSize);
1085 for (int row : hiddenRows)
1086 v.setRowHidden(row, hide: true);
1087
1088 v.resize(w: 525, h: 525);
1089
1090 topLevel.show();
1091 QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
1092
1093 v.setSelection(rect: selectionRect, command: QItemSelectionModel::ClearAndSelect);
1094
1095 const QModelIndexList selected = v.selectionModel()->selectedIndexes();
1096 QCOMPARE(selected.count(), expectedItems.count());
1097 for (const auto &idx : selected)
1098 QVERIFY(expectedItems.contains(idx.row()));
1099}
1100
1101void tst_QListView::scrollTo()
1102{
1103 QWidget topLevel;
1104 setFrameless(&topLevel);
1105 ScrollPerItemListView lv(&topLevel);
1106 QStringListModel model(&lv);
1107 QStringList list;
1108 list << "Short item 1";
1109 list << "Short item 2";
1110 list << "Short item 3";
1111 list << "Short item 4";
1112 list << "Short item 5";
1113 list << "Short item 6";
1114 list << "Begin This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1115 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1116 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1117 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1118 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1119 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1120 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1121 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1122 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1123 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1124 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1125 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1126 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1127 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1128 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\n"
1129 "This is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item\nThis is a very long item End\n";
1130 list << "Short item";
1131 list << "Short item";
1132 list << "Short item";
1133 list << "Short item";
1134 list << "Short item";
1135 list << "Short item";
1136 list << "Short item";
1137 list << "Short item";
1138 model.setStringList(list);
1139 lv.setModel(&model);
1140 lv.setFixedSize(w: 110, h: 200);
1141
1142 topLevel.show();
1143 QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
1144
1145 //by default, the list view scrolls per item and has no wrapping
1146 QModelIndex index = model.index(row: 6, column: 0);
1147
1148 //we save the size of the item for later comparisons
1149 const QSize itemsize = lv.visualRect(index).size();
1150 QVERIFY(itemsize.height() > lv.height());
1151 QVERIFY(itemsize.width() > lv.width());
1152
1153 //we click the item
1154 QPoint p = lv.visualRect(index).center();
1155 QTest::mouseClick(widget: lv.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
1156 //let's wait because the scrolling is delayed
1157 QTRY_COMPARE(lv.visualRect(index).y(), 0);
1158
1159 //we scroll down. As the item is to tall for the view, it will disappear
1160 QTest::keyClick(widget: lv.viewport(), key: Qt::Key_Down, modifier: Qt::NoModifier);
1161 QTRY_COMPARE(lv.visualRect(index).y(), -itemsize.height());
1162
1163 QTest::keyClick(widget: lv.viewport(), key: Qt::Key_Up, modifier: Qt::NoModifier);
1164 QTRY_COMPARE(lv.visualRect(index).y(), 0);
1165
1166 //Let's enable wrapping
1167
1168 lv.setWrapping(true);
1169 lv.horizontalScrollBar()->setValue(0); //let's scroll to the beginning
1170
1171 //we click the item
1172 p = lv.visualRect(index).center();
1173 QTest::mouseClick(widget: lv.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
1174 QTRY_COMPARE(lv.visualRect(index).x(), 0);
1175
1176 //we scroll right. As the item is too wide for the view, it will disappear
1177 QTest::keyClick(widget: lv.viewport(), key: Qt::Key_Right, modifier: Qt::NoModifier);
1178 QTRY_COMPARE(lv.visualRect(index).x(), -itemsize.width());
1179
1180 QTest::keyClick(widget: lv.viewport(), key: Qt::Key_Left, modifier: Qt::NoModifier);
1181 QTRY_COMPARE(lv.visualRect(index).x(), 0);
1182
1183 lv.setWrapping(false);
1184 QCoreApplication::processEvents(); //let the layout happen
1185
1186 //Let's try with scrolling per pixel
1187 lv.setHorizontalScrollMode(QListView::ScrollPerPixel);
1188 lv.verticalScrollBar()->setValue(0); //scrolls back to the first item
1189
1190 //we click the item
1191 p = lv.visualRect(index).center();
1192 QTest::mouseClick(widget: lv.viewport(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p);
1193 //let's wait because the scrolling is delayed
1194 QTest::qWait(ms: QApplication::doubleClickInterval() + 150);
1195 QTRY_COMPARE(lv.visualRect(index).y(), 0);
1196
1197 //we scroll down. As the item is too tall for the view, it will partially disappear
1198 QTest::keyClick(widget: lv.viewport(), key: Qt::Key_Down, modifier: Qt::NoModifier);
1199 QVERIFY(lv.visualRect(index).y() < 0);
1200
1201 QTest::keyClick(widget: lv.viewport(), key: Qt::Key_Up, modifier: Qt::NoModifier);
1202 QCOMPARE(lv.visualRect(index).y(), 0);
1203}
1204
1205
1206void tst_QListView::scrollBarRanges()
1207{
1208 const int rowCount = 10;
1209 const int rowHeight = 20;
1210
1211 QWidget topLevel;
1212 ScrollPerItemListView lv(&topLevel);
1213 QStringListModel model(&lv);
1214 model.setStringList(generateList(prefix: QLatin1String("Item "), size: rowCount));
1215 lv.setModel(&model);
1216 lv.resize(w: 250, h: 130);
1217
1218 lv.setItemDelegate(new TestDelegate(&lv, QSize(100, rowHeight)));
1219 topLevel.show();
1220
1221 for (int h = 30; h <= 210; ++h) {
1222 lv.resize(w: 250, h);
1223 int visibleRowCount = lv.viewport()->size().height() / rowHeight;
1224 int invisibleRowCount = rowCount - visibleRowCount;
1225 QTRY_COMPARE(lv.verticalScrollBar()->maximum(), invisibleRowCount);
1226 }
1227}
1228
1229void tst_QListView::scrollBarAsNeeded_data()
1230{
1231 QTest::addColumn<QSize>(name: "size");
1232 QTest::addColumn<int>(name: "itemCount");
1233 QTest::addColumn<QAbstractItemView::ScrollMode>(name: "verticalScrollMode");
1234 QTest::addColumn<QMargins>(name: "viewportMargins");
1235 QTest::addColumn<QSize>(name: "delegateSize");
1236 QTest::addColumn<QListView::Flow>(name: "flow");
1237 QTest::addColumn<bool>(name: "horizontalScrollBarVisible");
1238 QTest::addColumn<bool>(name: "verticalScrollBarVisible");
1239
1240 QTest::newRow(dataTag: "TopToBottom, count:0")
1241 << QSize(200, 100)
1242 << 0
1243 << QListView::ScrollPerItem
1244 << QMargins() << QSize()
1245 << QListView::TopToBottom
1246 << false
1247 << false;
1248
1249 QTest::newRow(dataTag: "TopToBottom, count:1")
1250 << QSize(200, 100)
1251 << 1
1252 << QListView::ScrollPerItem
1253 << QMargins() << QSize()
1254 << QListView::TopToBottom
1255 << false
1256 << false;
1257
1258 QTest::newRow(dataTag: "TopToBottom, count:20")
1259 << QSize(200, 100)
1260 << 20
1261 << QListView::ScrollPerItem
1262 << QMargins() << QSize()
1263 << QListView::TopToBottom
1264 << false
1265 << true;
1266
1267 QTest::newRow(dataTag: "TopToBottom, fixed size, count:4")
1268 << QSize(200, 200)
1269 << 4
1270 << QListView::ScrollPerPixel
1271 << QMargins() << QSize(40, 40)
1272 << QListView::TopToBottom
1273 << false
1274 << false;
1275
1276 // QTBUG-61383, vertical case: take viewport margins into account
1277 QTest::newRow(dataTag: "TopToBottom, fixed size, vertical margins, count:4")
1278 << QSize(200, 200)
1279 << 4
1280 << QListView::ScrollPerPixel
1281 << QMargins(0, 50, 0, 50) << QSize(40, 40)
1282 << QListView::TopToBottom
1283 << false
1284 << true;
1285
1286 // QTBUG-61383, horizontal case: take viewport margins into account
1287 QTest::newRow(dataTag: "TopToBottom, fixed size, horizontal margins, count:4")
1288 << QSize(200, 200)
1289 << 4
1290 << QListView::ScrollPerPixel
1291 << QMargins(50, 0, 50, 0) << QSize(120, 40)
1292 << QListView::TopToBottom
1293 << true
1294 << false;
1295
1296 QTest::newRow(dataTag: "LeftToRight, count:0")
1297 << QSize(200, 100)
1298 << 0
1299 << QListView::ScrollPerItem
1300 << QMargins() << QSize()
1301 << QListView::LeftToRight
1302 << false
1303 << false;
1304
1305 QTest::newRow(dataTag: "LeftToRight, count:1")
1306 << QSize(200, 100)
1307 << 1
1308 << QListView::ScrollPerItem
1309 << QMargins() << QSize()
1310 << QListView::LeftToRight
1311 << false
1312 << false;
1313
1314 QTest::newRow(dataTag: "LeftToRight, count:20")
1315 << QSize(200, 100)
1316 << 20
1317 << QListView::ScrollPerItem
1318 << QMargins() << QSize()
1319 << QListView::LeftToRight
1320 << true
1321 << false;
1322
1323
1324}
1325
1326void tst_QListView::scrollBarAsNeeded()
1327{
1328 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1329 QSKIP("Wayland: This fails. Figure out why.");
1330
1331 QFETCH(QSize, size);
1332 QFETCH(int, itemCount);
1333 QFETCH(QAbstractItemView::ScrollMode, verticalScrollMode);
1334 QFETCH(QMargins, viewportMargins);
1335 QFETCH(QSize, delegateSize);
1336 QFETCH(QListView::Flow, flow);
1337 QFETCH(bool, horizontalScrollBarVisible);
1338 QFETCH(bool, verticalScrollBarVisible);
1339
1340
1341 constexpr int rowCounts[3] = {0, 1, 20};
1342
1343 QWidget topLevel;
1344 topLevel.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QStringLiteral("::")
1345 + QLatin1String(QTest::currentDataTag()));
1346 PublicListView lv(&topLevel);
1347 lv.setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
1348 lv.setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
1349 lv.setVerticalScrollMode(verticalScrollMode);
1350 lv.setViewportMargins(viewportMargins);
1351 lv.setFlow(flow);
1352 if (!delegateSize.isEmpty())
1353 lv.setItemDelegate(new TestDelegate(&lv, delegateSize));
1354
1355 QStringListModel model(&lv);
1356 lv.setModel(&model);
1357 lv.resize(size);
1358 topLevel.show();
1359 QVERIFY(QTest::qWaitForWindowActive(&topLevel));
1360
1361 for (uint r = 0; r < sizeof(rowCounts) / sizeof(int); ++r) {
1362 model.setStringList(generateList(prefix: QLatin1String("Item "), size: rowCounts[r]));
1363 model.setStringList(generateList(prefix: QLatin1String("Item "), size: itemCount));
1364
1365 QTRY_COMPARE(lv.horizontalScrollBar()->isVisible(), horizontalScrollBarVisible);
1366 QTRY_COMPARE(lv.verticalScrollBar()->isVisible(), verticalScrollBarVisible);
1367 }
1368}
1369
1370void tst_QListView::moveItems()
1371{
1372 QStandardItemModel model;
1373 for (int r = 0; r < 4; ++r) {
1374 const QString prefix = QLatin1String("standard item (") + QString::number(r) + QLatin1Char(',');
1375 for (int c = 0; c < 4; ++c)
1376 model.setItem(row: r, column: c, item: new QStandardItem(prefix + QString::number(c) + QLatin1Char(')')));
1377 }
1378
1379 PublicListView view;
1380 view.setViewMode(QListView::IconMode);
1381 view.setResizeMode(QListView::Fixed);
1382 view.setWordWrap(true);
1383 view.setModel(&model);
1384 view.setItemDelegate(new TestDelegate(&view));
1385
1386 for (int r = 0; r < model.rowCount(); ++r) {
1387 for (int c = 0; c < model.columnCount(); ++c) {
1388 const QModelIndex& idx = model.index(row: r, column: c);
1389 view.setPositionForIndex(position: QPoint(r * 75, r * 75), index: idx);
1390 }
1391 }
1392
1393 QCOMPARE(view.contentsSize(), QSize(275, 275));
1394}
1395
1396void tst_QListView::wordWrap()
1397{
1398 QListView lv;
1399 lv.setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
1400 lv.setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
1401 QStringListModel model(&lv);
1402 QStringList list;
1403 list << "Short item 1";
1404 list << "Short item 2";
1405 list << "Short item 3";
1406 list << "Begin\nThis item take severals Lines\nEnd";
1407 list << "And this is a very long item very long item this is a very vary vary long item"
1408 "very long very very long long long this is a long item a very long item a very very long item";
1409 list << "And this is a second even a little more long very long item very long item this is a very vary vary long item"
1410 "very long very very long long long this is a long item a very long item a very very long item";
1411 list << "Short item";
1412 list << "rzeofig zerig fslfgj smdlfkgj qmsdlfj amrzriougf qsla zrg fgsdf gsdfg sdfgs dfg sdfgcvb sdfg qsdjfh qsdfjklh qs";
1413 list << "Short item";
1414 model.setStringList(list);
1415 lv.setModel(&model);
1416 lv.setWordWrap(true);
1417 lv.setFixedSize(w: 400, h: 150);
1418 lv.showNormal();
1419
1420 QTRY_COMPARE(lv.horizontalScrollBar()->isVisible(), false);
1421#ifdef Q_OS_WINRT
1422QSKIP("setFixedSize does not work on WinRT. Vertical scroll bar will not be visible.");
1423#endif
1424 QTRY_COMPARE(lv.verticalScrollBar()->isVisible(), true);
1425}
1426
1427#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
1428class SetCurrentIndexAfterAppendRowCrashDialog : public QDialog
1429{
1430 Q_OBJECT
1431public:
1432 SetCurrentIndexAfterAppendRowCrashDialog()
1433 {
1434 setWindowTitle(QTest::currentTestFunction());
1435 listView = new QListView(this);
1436 QVBoxLayout *layout = new QVBoxLayout(this);
1437 layout->addWidget(listView);
1438 listView->setViewMode(QListView::IconMode);
1439
1440 model = new QStandardItemModel(this);
1441 listView->setModel(model);
1442
1443 timer = new QTimer(this);
1444 connect(timer, &QTimer::timeout,
1445 this, &SetCurrentIndexAfterAppendRowCrashDialog::buttonClicked);
1446 timer->start(1000);
1447 }
1448
1449protected:
1450 void showEvent(QShowEvent *event) override
1451 {
1452 QDialog::showEvent(event);
1453 DWORD lParam = 0xFFFFFFFC/*OBJID_CLIENT*/;
1454 DWORD wParam = 0;
1455 if (const HWND hwnd = getHWNDForWidget(this))
1456 SendMessage(hwnd, WM_GETOBJECT, wParam, lParam);
1457 }
1458
1459private slots:
1460 void buttonClicked()
1461 {
1462 timer->stop();
1463 QStandardItem *item = new QStandardItem("test");
1464 model->appendRow(item);
1465 listView->setCurrentIndex(model->indexFromItem(item));
1466 close();
1467 }
1468private:
1469 QListView *listView;
1470 QStandardItemModel *model;
1471 QTimer *timer;
1472};
1473
1474void tst_QListView::setCurrentIndexAfterAppendRowCrash()
1475{
1476 SetCurrentIndexAfterAppendRowCrashDialog w;
1477 w.exec();
1478}
1479#endif // Q_OS_WIN && !Q_OS_WINRT
1480
1481void tst_QListView::emptyItemSize()
1482{
1483 QStandardItemModel model;
1484 for (int r = 0; r < 4; ++r) {
1485 const QString text = QLatin1String("standard item (") + QString::number(r) + QLatin1Char(')');
1486 model.setItem(arow: r, aitem: new QStandardItem(text));
1487 }
1488
1489 model.setItem(row: 4, column: 0, item: new QStandardItem());
1490
1491 PublicListView view;
1492 view.setModel(&model);
1493
1494 for (int i = 0; i < 5; ++i)
1495 QVERIFY(!view.visualRect(model.index(i, 0)).isEmpty());
1496}
1497
1498void tst_QListView::task203585_selectAll()
1499{
1500 //we make sure that "select all" doesn't select the hidden items
1501 QListView view;
1502 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
1503 view.setModel(new QStringListModel({"foo"}, &view));
1504 view.setRowHidden(row: 0, hide: true);
1505 view.selectAll();
1506 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
1507 view.setRowHidden(row: 0, hide: false);
1508 view.selectAll();
1509 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 1);
1510}
1511
1512void tst_QListView::task228566_infiniteRelayout()
1513{
1514 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1515 QSKIP("Wayland: This fails. Figure out why.");
1516
1517 QListView view;
1518
1519 QStringList list;
1520 for (int i = 0; i < 10; ++i)
1521 list << "small";
1522
1523 list << "BIGBIGBIGBIGBIGBIGBIGBIGBIGBIGBIGBIG"
1524 << "BIGBIGBIGBIGBIGBIGBIGBIGBIGBIGBIGBIG";
1525
1526 QStringListModel model(list);
1527 view.setModel(&model);
1528 view.setWrapping(true);
1529 view.setResizeMode(QListView::Adjust);
1530
1531 const int itemHeight = view.visualRect( index: model.index(row: 0, column: 0)).height();
1532
1533 view.setFixedHeight(itemHeight * 12);
1534 view.show();
1535 QVERIFY(QTest::qWaitForWindowActive(&view));
1536 QTest::qWait(ms: 100); //make sure the layout is done once
1537
1538 QSignalSpy spy(view.horizontalScrollBar(), &QScrollBar::rangeChanged);
1539
1540 //the layout should already have been done
1541 //so there should be no change made to the scrollbar
1542 QVERIFY(!spy.wait(200));
1543}
1544
1545void tst_QListView::task248430_crashWith0SizedItem()
1546{
1547 QListView view;
1548 view.setViewMode(QListView::IconMode);
1549 QStringListModel model({QLatin1String("item1"), QString()});
1550 view.setModel(&model);
1551 view.show();
1552 QVERIFY(QTest::qWaitForWindowExposed(&view));
1553 QTest::qWait(ms: 20);
1554}
1555
1556void tst_QListView::task250446_scrollChanged()
1557{
1558 QStandardItemModel model(200, 1);
1559 QListView view;
1560 view.setModel(&model);
1561 QModelIndex index = model.index(row: 0, column: 0);
1562 QVERIFY(index.isValid());
1563 view.setCurrentIndex(index);
1564 view.show();
1565 QVERIFY(QTest::qWaitForWindowExposed(&view));
1566 const int scrollValue = view.verticalScrollBar()->maximum();
1567 view.verticalScrollBar()->setValue(scrollValue);
1568 QTRY_COMPARE(view.verticalScrollBar()->value(), scrollValue);
1569 QTRY_COMPARE(view.currentIndex(), index);
1570
1571 view.showMinimized();
1572 QTRY_COMPARE(view.verticalScrollBar()->value(), scrollValue);
1573 QTRY_COMPARE(view.currentIndex(), index);
1574
1575 view.showNormal();
1576 QVERIFY(QTest::qWaitForWindowExposed(&view));
1577 QTRY_COMPARE(view.verticalScrollBar()->value(), scrollValue);
1578 QTRY_COMPARE(view.currentIndex(), index);
1579}
1580
1581void tst_QListView::task196118_visualRegionForSelection()
1582{
1583 PublicListView view;
1584 QStandardItemModel model;
1585 QStandardItem top1("top1");
1586 QStandardItem sub1("sub1");
1587 top1.appendRow(aitem: &sub1);
1588 model.appendColumn(items: {&top1});
1589 view.setModel(&model);
1590 view.setRootIndex(top1.index());
1591
1592 view.selectionModel()->select(index: top1.index(), command: QItemSelectionModel::Select);
1593
1594 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 1);
1595 QVERIFY(view.getVisualRegionForSelection().isEmpty());
1596}
1597
1598void tst_QListView::task254449_draggingItemToNegativeCoordinates()
1599{
1600 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1601 QSKIP("Wayland: This fails. Figure out why.");
1602
1603 //we'll check that the items are painted correctly
1604 PublicListView list;
1605 QStandardItemModel model(1, 1);
1606 QModelIndex index = model.index(row: 0, column: 0);
1607 model.setData(index, value: QLatin1String("foo"));
1608 list.setModel(&model);
1609 list.setViewMode(QListView::IconMode);
1610 list.show();
1611 list.activateWindow();
1612 QVERIFY(QTest::qWaitForWindowActive(&list));
1613
1614
1615 class MyItemDelegate : public QStyledItemDelegate
1616 {
1617 public:
1618 using QStyledItemDelegate::QStyledItemDelegate;
1619 void paint(QPainter *painter, const QStyleOptionViewItem &option,
1620 const QModelIndex &index) const override
1621 {
1622 numPaints++;
1623 QStyledItemDelegate::paint(painter, option, index);
1624 }
1625
1626 mutable int numPaints = 0;
1627 } delegate;
1628 list.setItemDelegate(&delegate);
1629 QTRY_VERIFY(delegate.numPaints > 0); //makes sure the layout is done
1630
1631 //we'll make sure the item is repainted
1632 delegate.numPaints = 0;
1633 const QPoint topLeft(-6, 0);
1634 list.setPositionForIndex(position: topLeft, index);
1635 QTRY_COMPARE(delegate.numPaints, 1);
1636 QCOMPARE(list.visualRect(index).topLeft(), topLeft);
1637}
1638
1639
1640void tst_QListView::keyboardSearch()
1641{
1642 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1643 QSKIP("Wayland: This fails. Figure out why.");
1644
1645 QStringListModel model({"AB", "AC", "BA", "BB", "BD", "KAFEINE",
1646 "KONQUEROR", "KOPETE", "KOOKA", "OKULAR"});
1647
1648 QListView view;
1649 view.setModel(&model);
1650 view.show();
1651 QApplication::setActiveWindow(&view);
1652 QVERIFY(QTest::qWaitForWindowActive(&view));
1653
1654 QTest::keyClick(widget: &view, key: Qt::Key_K);
1655 QTRY_COMPARE(view.currentIndex() , model.index(5, 0)); //KAFEINE
1656
1657 QTest::keyClick(widget: &view, key: Qt::Key_O);
1658 QTRY_COMPARE(view.currentIndex() , model.index(6, 0)); //KONQUEROR
1659
1660 QTest::keyClick(widget: &view, key: Qt::Key_N);
1661 QTRY_COMPARE(view.currentIndex() , model.index(6, 0)); //KONQUEROR
1662}
1663
1664void tst_QListView::shiftSelectionWithNonUniformItemSizes()
1665{
1666 // This checks that no items are selected unexpectedly by Shift-Arrow
1667 // when items with non-uniform sizes are laid out in a grid
1668 { // First test: QListView::LeftToRight flow
1669 QStringListModel model({"Long\nText", "Text", "Text","Text"});
1670
1671 QListView view;
1672 view.setFixedSize(w: 250, h: 250);
1673 view.setFlow(QListView::LeftToRight);
1674 view.setGridSize(QSize(100, 100));
1675 view.setSelectionMode(QListView::ExtendedSelection);
1676 view.setViewMode(QListView::IconMode);
1677 view.setModel(&model);
1678 view.show();
1679 QVERIFY(QTest::qWaitForWindowExposed(&view));
1680
1681 // Verfify that item sizes are non-uniform
1682 QVERIFY(view.sizeHintForIndex(model.index(0, 0)).height() > view.sizeHintForIndex(model.index(1, 0)).height());
1683
1684 QModelIndex index = model.index(row: 3, column: 0);
1685 view.setCurrentIndex(index);
1686 QCOMPARE(view.currentIndex(), index);
1687
1688 QTest::keyClick(widget: &view, key: Qt::Key_Up, modifier: Qt::ShiftModifier);
1689 QTRY_COMPARE(view.currentIndex(), model.index(1, 0));
1690
1691 QModelIndexList selected = view.selectionModel()->selectedIndexes();
1692 QCOMPARE(selected.count(), 3);
1693 QVERIFY(!selected.contains(model.index(0, 0)));
1694 }
1695 { // Second test: QListView::TopToBottom flow
1696 QStringListModel model({"ab", "a", "a", "a"});
1697
1698 QListView view;
1699 view.setFixedSize(w: 250, h: 250);
1700 view.setFlow(QListView::TopToBottom);
1701 view.setGridSize(QSize(100, 100));
1702 view.setSelectionMode(QListView::ExtendedSelection);
1703 view.setViewMode(QListView::IconMode);
1704 view.setModel(&model);
1705 view.show();
1706 QVERIFY(QTest::qWaitForWindowExposed(&view));
1707
1708 // Verfify that item sizes are non-uniform
1709 QVERIFY(view.sizeHintForIndex(model.index(0, 0)).width() > view.sizeHintForIndex(model.index(1, 0)).width());
1710
1711 QModelIndex index = model.index(row: 3, column: 0);
1712 view.setCurrentIndex(index);
1713 QCOMPARE(view.currentIndex(), index);
1714
1715 QTest::keyClick(widget: &view, key: Qt::Key_Left, modifier: Qt::ShiftModifier);
1716 QTRY_COMPARE(view.currentIndex(), model.index(1, 0));
1717
1718 QModelIndexList selected = view.selectionModel()->selectedIndexes();
1719 QCOMPARE(selected.count(), 3);
1720 QVERIFY(!selected.contains(model.index(0, 0)));
1721 }
1722}
1723
1724void tst_QListView::shiftSelectionWithItemAlignment()
1725{
1726 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1727 QSKIP("Wayland: This fails. Figure out why.");
1728
1729 QStringList items;
1730 for (int c = 0; c < 2; c++) {
1731 for (int i = 10; i > 0; i--)
1732 items << QString(i, QLatin1Char('*'));
1733
1734 for (int i = 1; i < 11; i++)
1735 items << QString(i, QLatin1Char('*'));
1736 }
1737
1738 QListView view;
1739 view.setFlow(QListView::TopToBottom);
1740 view.setWrapping(true);
1741 view.setItemAlignment(Qt::AlignLeft);
1742 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
1743
1744 QStringListModel model(items);
1745 view.setModel(&model);
1746
1747 QFont font = view.font();
1748 font.setPixelSize(10);
1749 view.setFont(font);
1750 view.resize(w: 300, h: view.sizeHintForRow(row: 0) * items.size() / 2 + view.horizontalScrollBar()->height());
1751
1752 view.show();
1753 QApplication::setActiveWindow(&view);
1754 QVERIFY(QTest::qWaitForWindowActive(&view));
1755 QCOMPARE(static_cast<QWidget *>(&view), QApplication::activeWindow());
1756
1757 QModelIndex index1 = view.model()->index(row: items.size() / 4, column: 0);
1758 QPoint p = view.visualRect(index: index1).center();
1759 QVERIFY(view.viewport()->rect().contains(p));
1760 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
1761 QCOMPARE(view.currentIndex(), index1);
1762 QCOMPARE(view.selectionModel()->selectedIndexes().size(), 1);
1763
1764 QModelIndex index2 = view.model()->index(row: items.size() / 4 * 3, column: 0);
1765 p = view.visualRect(index: index2).center();
1766 QVERIFY(view.viewport()->rect().contains(p));
1767 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: Qt::ShiftModifier, pos: p);
1768 QCOMPARE(view.currentIndex(), index2);
1769 QCOMPARE(view.selectionModel()->selectedIndexes().size(), index2.row() - index1.row() + 1);
1770}
1771
1772void tst_QListView::clickOnViewportClearsSelection()
1773{
1774 QStringListModel model({"Text1"});
1775 QListView view;
1776 view.setModel(&model);
1777 view.setSelectionMode(QListView::ExtendedSelection);
1778
1779 view.selectAll();
1780 QModelIndex index = model.index(row: 0);
1781 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 1);
1782 QVERIFY(view.selectionModel()->isSelected(index));
1783
1784 //we try to click outside of the index
1785 const QPoint point = view.visualRect(index).bottomRight() + QPoint(10,10);
1786
1787 QTest::mousePress(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: point);
1788 //at this point, the selection shouldn't have changed
1789 QCOMPARE(view.selectionModel()->selectedIndexes().count(), 1);
1790 QVERIFY(view.selectionModel()->isSelected(index));
1791
1792 QTest::mouseRelease(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: point);
1793 //now the selection should be cleared
1794 QVERIFY(!view.selectionModel()->hasSelection());
1795}
1796
1797void tst_QListView::task262152_setModelColumnNavigate()
1798{
1799 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1800 QSKIP("Wayland: This fails. Figure out why.");
1801
1802 QListView view;
1803 QStandardItemModel model(3,2);
1804 model.setItem(row: 0, column: 1, item: new QStandardItem("[0,1]"));
1805 model.setItem(row: 1, column: 1, item: new QStandardItem("[1,1]"));
1806 model.setItem(row: 2, column: 1, item: new QStandardItem("[2,1]"));
1807
1808 view.setModel(&model);
1809 view.setModelColumn(1);
1810
1811 view.show();
1812 QApplication::setActiveWindow(&view);
1813 QVERIFY(QTest::qWaitForWindowActive(&view));
1814 QCOMPARE(&view, QApplication::activeWindow());
1815 QTest::keyClick(widget: &view, key: Qt::Key_Down);
1816 QTRY_COMPARE(view.currentIndex(), model.index(1, 1));
1817 QTest::keyClick(widget: &view, key: Qt::Key_Down);
1818 QTRY_COMPARE(view.currentIndex(), model.index(2, 1));
1819}
1820
1821void tst_QListView::taskQTBUG_2233_scrollHiddenItems_data()
1822{
1823 QTest::addColumn<QListView::Flow>(name: "flow");
1824
1825 QTest::newRow(dataTag: "TopToBottom") << QListView::TopToBottom;
1826 QTest::newRow(dataTag: "LeftToRight") << QListView::LeftToRight;
1827}
1828
1829void tst_QListView::taskQTBUG_2233_scrollHiddenItems()
1830{
1831 QFETCH(QListView::Flow, flow);
1832 const int rowCount = 200;
1833
1834 QWidget topLevel;
1835 setFrameless(&topLevel);
1836 ScrollPerItemListView view(&topLevel);
1837 QStringListModel model(&view);
1838 model.setStringList(generateList(prefix: QString(), size: rowCount));
1839 view.setModel(&model);
1840 view.setUniformItemSizes(true);
1841 view.setViewMode(QListView::ListMode);
1842 for (int i = 0; i < rowCount / 2; ++i)
1843 view.setRowHidden(row: 2 * i, hide: true);
1844 view.setFlow(flow);
1845 view.resize(w: 130, h: 130);
1846
1847 for (int i = 0; i < 10; ++i) {
1848 (view.flow() == QListView::TopToBottom
1849 ? view.verticalScrollBar()
1850 : view.horizontalScrollBar())->setValue(i);
1851 QModelIndex index = view.indexAt(p: QPoint(0, 0));
1852 QVERIFY(index.isValid());
1853 QCOMPARE(index.row(), 2 * i + 1);
1854 }
1855
1856 //QTBUG-7929 should not crash
1857 topLevel.show();
1858 QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
1859 QScrollBar *bar = view.flow() == QListView::TopToBottom
1860 ? view.verticalScrollBar() : view.horizontalScrollBar();
1861
1862 int nbVisibleItem = rowCount / 2 - bar->maximum();
1863
1864 bar->setValue(bar->maximum());
1865 for (int i = rowCount; i > rowCount / 2; i--)
1866 view.setRowHidden(row: i, hide: true);
1867 QTRY_COMPARE(bar->maximum(), rowCount / 4 - nbVisibleItem);
1868 QCOMPARE(bar->value(), bar->maximum());
1869}
1870
1871void tst_QListView::taskQTBUG_633_changeModelData()
1872{
1873 QListView view;
1874 view.setFlow(QListView::LeftToRight);
1875 QStandardItemModel model(5,1);
1876 for (int i = 0; i < model.rowCount(); ++i)
1877 model.setData(index: model.index(row: i, column: 0), value: QString::number(i));
1878
1879 view.setModel(&model);
1880 view.show();
1881 QVERIFY(QTest::qWaitForWindowExposed(&view));
1882 model.setData( index: model.index(row: 1, column: 0), value: QLatin1String("long long text"));
1883 const auto longTextDoesNotIntersectNextItem = [&]() {
1884 QRect rectLongText = view.visualRect(index: model.index(row: 1,column: 0));
1885 QRect rect2 = view.visualRect(index: model.index(row: 2,column: 0));
1886 return !rectLongText.intersects(r: rect2);
1887 };
1888 QTRY_VERIFY(longTextDoesNotIntersectNextItem());
1889}
1890
1891void tst_QListView::taskQTBUG_435_deselectOnViewportClick()
1892{
1893 QListView view;
1894 QStringListModel model({"1", "2", "3", "4"});
1895 view.setModel(&model);
1896 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
1897 view.selectAll();
1898 QCOMPARE(view.selectionModel()->selectedIndexes().count(), model.rowCount());
1899
1900
1901 const QRect itemRect = view.visualRect(index: model.index(row: model.rowCount() - 1));
1902 QPoint p = view.visualRect(index: model.index(row: model.rowCount() - 1)).center() + QPoint(0, itemRect.height());
1903 //first the left button
1904 QTest::mouseClick(widget: view.viewport(), button: Qt::LeftButton, stateKey: {}, pos: p);
1905 QVERIFY(!view.selectionModel()->hasSelection());
1906
1907 view.selectAll();
1908 QCOMPARE(view.selectionModel()->selectedIndexes().count(), model.rowCount());
1909
1910 //and now the right button
1911 QTest::mouseClick(widget: view.viewport(), button: Qt::RightButton, stateKey: {}, pos: p);
1912 QVERIFY(!view.selectionModel()->hasSelection());
1913}
1914
1915void tst_QListView::taskQTBUG_2678_spacingAndWrappedText()
1916{
1917 static const QString lorem("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
1918 QStringListModel model({lorem, lorem, "foo", lorem, "bar", lorem, lorem});
1919 QListView w;
1920 w.setModel(&model);
1921 w.setViewMode(QListView::ListMode);
1922 w.setWordWrap(true);
1923 w.setSpacing(10);
1924 w.show();
1925 QVERIFY(QTest::qWaitForWindowExposed(&w));
1926 QCOMPARE(w.horizontalScrollBar()->minimum(), w.horizontalScrollBar()->maximum());
1927}
1928
1929void tst_QListView::taskQTBUG_5877_skippingItemInPageDownUp()
1930{
1931 QtTestModel model(100, 1);
1932
1933 static const int currentItemIndexes[] =
1934 {0, 6, 16, 25, 34, 42, 57, 68, 77, 83, 91, 94};
1935 PublicListView vu;
1936 vu.setModel(&model);
1937 vu.show();
1938
1939 QVERIFY(QTest::qWaitForWindowExposed(&vu));
1940
1941 int itemHeight = vu.visualRect(index: model.index(row: 0, column: 0)).height();
1942 int visibleRowCount = vu.viewport()->height() / itemHeight;
1943 int scrolledRowCount = visibleRowCount - 1;
1944
1945 for (int currentItemIndex : currentItemIndexes) {
1946 vu.selectionModel()->setCurrentIndex(index: model.index(row: currentItemIndex, column: 0),
1947 command: QItemSelectionModel::SelectCurrent);
1948
1949 QModelIndex idx = vu.moveCursor(cursorAction: PublicListView::MovePageDown, modifiers: Qt::NoModifier);
1950 int newCurrent = qMin(a: currentItemIndex + scrolledRowCount, b: 99);
1951 QCOMPARE(idx, model.index(newCurrent, 0));
1952
1953 idx = vu.moveCursor(cursorAction: PublicListView::MovePageUp, modifiers: Qt::NoModifier);
1954 newCurrent = qMax(a: currentItemIndex - scrolledRowCount, b: 0);
1955 QCOMPARE(idx, model.index(newCurrent, 0));
1956 }
1957}
1958
1959void tst_QListView::taskQTBUG_9455_wrongScrollbarRanges()
1960{
1961 QStringListModel model(generateList(prefix: "item ", size: 8));
1962 PublicListView w;
1963 setFrameless(&w);
1964 w.setModel(&model);
1965 w.setViewMode(QListView::IconMode);
1966 w.resize(w: 116, h: 132);
1967 w.setMovement(QListView::Static);
1968 w.setSpacing(200);
1969 w.showNormal();
1970 QVERIFY(QTest::qWaitForWindowExposed(&w));
1971 QCOMPARE(w.verticalScrollBar()->maximum(),
1972 w.contentsSize().height() - w.viewport()->geometry().height());
1973}
1974
1975void tst_QListView::styleOptionViewItem()
1976{
1977 class MyDelegate : public QStyledItemDelegate
1978 {
1979 public:
1980 void paint(QPainter *painter, const QStyleOptionViewItem &option,
1981 const QModelIndex &index) const override
1982 {
1983 QStyleOptionViewItem opt(option);
1984 initStyleOption(option: &opt, index);
1985
1986 QCOMPARE(opt.index, index);
1987
1988 QStyledItemDelegate::paint(painter, option, index);
1989 }
1990 };
1991
1992 QListView view;
1993 QStandardItemModel model;
1994 view.setModel(&model);
1995 MyDelegate delegate;
1996 view.setItemDelegate(&delegate);
1997 model.appendRow(items: {new QStandardItem("Beginning"),
1998 new QStandardItem("Middle"),
1999 new QStandardItem("Middle"),
2000 new QStandardItem("End")});
2001
2002 // Run test
2003 view.showMaximized();
2004 QVERIFY(QTest::qWaitForWindowExposed(&view));
2005}
2006
2007void tst_QListView::taskQTBUG_12308_artihmeticException()
2008{
2009 QListWidget lw;
2010 lw.setLayoutMode(QListView::Batched);
2011 lw.setViewMode(QListView::IconMode);
2012 for (int i = 0; i < lw.batchSize() + 1; i++) {
2013 QListWidgetItem *item = new QListWidgetItem(
2014 QLatin1String("Item ") + QString::number(i));
2015 lw.addItem(aitem: item);
2016 item->setHidden(true);
2017 }
2018 lw.show();
2019 QVERIFY(QTest::qWaitForWindowExposed(&lw));
2020 // No crash, it's all right.
2021}
2022
2023class Delegate12308 : public QStyledItemDelegate
2024{
2025 Q_OBJECT
2026public:
2027 using QStyledItemDelegate::QStyledItemDelegate;
2028 void paint(QPainter *painter, const QStyleOptionViewItem &option,
2029 const QModelIndex &index) const override
2030 {
2031 QVERIFY(option.rect.topLeft() != QPoint(-1, -1));
2032 QStyledItemDelegate::paint(painter, option, index);
2033 }
2034};
2035
2036void tst_QListView::taskQTBUG_12308_wrongFlowLayout()
2037{
2038 QListWidget lw;
2039 Delegate12308 delegate;
2040 lw.setLayoutMode(QListView::Batched);
2041 lw.setViewMode(QListView::IconMode);
2042 lw.setItemDelegate(&delegate);
2043 for (int i = 0; i < lw.batchSize() + 1; i++) {
2044 QListWidgetItem *item = new QListWidgetItem(
2045 QLatin1String("Item ") + QString::number(i));
2046 lw.addItem(aitem: item);
2047 if (!item->text().contains(c: QLatin1Char('1')))
2048 item->setHidden(true);
2049 }
2050 lw.show();
2051 QVERIFY(QTest::qWaitForWindowExposed(&lw));
2052}
2053
2054void tst_QListView::taskQTBUG_21115_scrollToAndHiddenItems_data()
2055{
2056 QTest::addColumn<QListView::Flow>(name: "flow");
2057 QTest::newRow(dataTag: "flow TopToBottom") << QListView::TopToBottom;
2058 QTest::newRow(dataTag: "flow LeftToRight") << QListView::LeftToRight;
2059}
2060
2061void tst_QListView::taskQTBUG_21115_scrollToAndHiddenItems()
2062{
2063 QFETCH(QListView::Flow, flow);
2064#ifdef Q_OS_WINRT
2065 QSKIP("Fails on WinRT - QTBUG-68297");
2066#endif
2067
2068 ScrollPerItemListView lv;
2069 lv.setUniformItemSizes(true);
2070 lv.setFlow(flow);
2071
2072 QStringListModel model;
2073 model.setStringList(generateList(prefix: QString(), size: 30));
2074 lv.setModel(&model);
2075 lv.showNormal();
2076 QVERIFY(QTest::qWaitForWindowExposed(&lv));
2077
2078 // Save first item rect for reference
2079 QRect firstItemRect = lv.visualRect(index: model.index(row: 0, column: 0));
2080
2081 // Select an item and scroll to selection
2082 QModelIndex index = model.index(row: 2, column: 0);
2083 lv.setCurrentIndex(index);
2084 lv.scrollTo(index, hint: QAbstractItemView::PositionAtTop);
2085 QTRY_COMPARE(lv.visualRect(index), firstItemRect);
2086
2087 // Hide some rows and scroll to selection
2088 for (int i = 0; i < 5; i++) {
2089 if (i == index.row())
2090 continue;
2091 lv.setRowHidden(row: i, hide: true);
2092 }
2093 lv.scrollTo(index, hint: QAbstractItemView::PositionAtTop);
2094 QTRY_COMPARE(lv.visualRect(index), firstItemRect);
2095}
2096
2097void tst_QListView::draggablePaintPairs_data()
2098{
2099 QTest::addColumn<int>(name: "row");
2100
2101 for (int row = 0; row < 30; ++row)
2102 QTest::newRow(dataTag: "row-" + QByteArray::number(row)) << row;
2103}
2104
2105void tst_QListView::draggablePaintPairs()
2106{
2107 QFETCH(int, row);
2108
2109 QListView view;
2110
2111 QStringListModel model;
2112 model.setStringList(generateList(prefix: QString(), size: 30));
2113 view.setModel(&model);
2114
2115 view.show();
2116 QVERIFY(QTest::qWaitForWindowExposed(&view));
2117
2118 QModelIndex expectedIndex = model.index(row, column: 0);
2119 QListViewPrivate *privateClass = static_cast<QListViewPrivate *>(QListViewPrivate::get(w: &view));
2120 QRect rect;
2121 const QModelIndexList indexList{ expectedIndex };
2122 view.scrollTo(index: expectedIndex);
2123 const QItemViewPaintPairs pairs = privateClass->draggablePaintPairs(indexes: indexList, r: &rect);
2124 QCOMPARE(indexList.size(), pairs.size());
2125 for (const QItemViewPaintPair &pair : pairs) {
2126 QCOMPARE(rect, pair.rect);
2127 QCOMPARE(expectedIndex, pair.index);
2128 }
2129}
2130
2131void tst_QListView::taskQTBUG_21804_hiddenItemsAndScrollingWithKeys_data()
2132{
2133 QTest::addColumn<QListView::Flow>(name: "flow");
2134 QTest::addColumn<int>(name: "spacing");
2135 QTest::newRow(dataTag: "flow TopToBottom no spacing") << QListView::TopToBottom << 0;
2136 QTest::newRow(dataTag: "flow TopToBottom with spacing") << QListView::TopToBottom << 5;
2137 QTest::newRow(dataTag: "flow LeftToRight no spacing") << QListView::LeftToRight << 0;
2138 QTest::newRow(dataTag: "flow LeftToRight with spacing") << QListView::LeftToRight << 5;
2139}
2140
2141void tst_QListView::taskQTBUG_21804_hiddenItemsAndScrollingWithKeys()
2142{
2143 QFETCH(QListView::Flow, flow);
2144 QFETCH(int, spacing);
2145
2146 // create some items to show
2147 QStringListModel model;
2148 model.setStringList(generateList(prefix: QString(), size: 60));
2149
2150 // create listview
2151 ScrollPerItemListView lv;
2152 lv.setFlow(flow);
2153 lv.setSpacing(spacing);
2154 lv.setModel(&model);
2155 lv.show();
2156 QVERIFY(QTest::qWaitForWindowExposed(&lv));
2157
2158 // hide every odd number row
2159 for (int i = 1; i < model.rowCount(); i+=2)
2160 lv.setRowHidden(row: i, hide: true);
2161
2162 // scroll forward and check that selected item is visible always
2163 int visibleItemCount = model.rowCount() / 2;
2164 for (int i = 0; i < visibleItemCount; i++) {
2165 if (flow == QListView::TopToBottom)
2166 QTest::keyClick(widget: &lv, key: Qt::Key_Down);
2167 else
2168 QTest::keyClick(widget: &lv, key: Qt::Key_Right);
2169 QTRY_VERIFY(lv.rect().contains(lv.visualRect(lv.currentIndex())));
2170 }
2171
2172 // scroll backward
2173 for (int i = 0; i < visibleItemCount; i++) {
2174 if (flow == QListView::TopToBottom)
2175 QTest::keyClick(widget: &lv, key: Qt::Key_Up);
2176 else
2177 QTest::keyClick(widget: &lv, key: Qt::Key_Left);
2178 QTRY_VERIFY(lv.rect().contains(lv.visualRect(lv.currentIndex())));
2179 }
2180
2181 // scroll forward only half way
2182 for (int i = 0; i < visibleItemCount / 2; i++) {
2183 if (flow == QListView::TopToBottom)
2184 QTest::keyClick(widget: &lv, key: Qt::Key_Down);
2185 else
2186 QTest::keyClick(widget: &lv, key: Qt::Key_Right);
2187 QTRY_VERIFY(lv.rect().contains(lv.visualRect(lv.currentIndex())));
2188 }
2189
2190 // scroll backward again
2191 for (int i = 0; i < visibleItemCount / 2; i++) {
2192 if (flow == QListView::TopToBottom)
2193 QTest::keyClick(widget: &lv, key: Qt::Key_Up);
2194 else
2195 QTest::keyClick(widget: &lv, key: Qt::Key_Left);
2196 QTRY_VERIFY(lv.rect().contains(lv.visualRect(lv.currentIndex())));
2197 }
2198}
2199
2200void tst_QListView::spacing_data()
2201{
2202 QTest::addColumn<QListView::Flow>(name: "flow");
2203 QTest::addColumn<int>(name: "spacing");
2204 QTest::newRow(dataTag: "flow=TopToBottom spacing=0") << QListView::TopToBottom << 0;
2205 QTest::newRow(dataTag: "flow=TopToBottom spacing=10") << QListView::TopToBottom << 10;
2206 QTest::newRow(dataTag: "flow=LeftToRight spacing=0") << QListView::LeftToRight << 0;
2207 QTest::newRow(dataTag: "flow=LeftToRight spacing=10") << QListView::LeftToRight << 10;
2208}
2209
2210void tst_QListView::spacing()
2211{
2212 QFETCH(QListView::Flow, flow);
2213 QFETCH(int, spacing);
2214
2215 // create some items to show
2216 QStringListModel model;
2217 model.setStringList(generateList(prefix: QString(), size: 60));
2218
2219 // create listview
2220 ScrollPerItemListView lv;
2221 lv.setFlow(flow);
2222 lv.setModel(&model);
2223 lv.setSpacing(spacing);
2224 lv.show();
2225 QVERIFY(QTest::qWaitForWindowExposed(&lv));
2226
2227 // check size and position of first two items
2228 QRect item1 = lv.visualRect(index: lv.model()->index(row: 0, column: 0));
2229 QRect item2 = lv.visualRect(index: lv.model()->index(row: 1, column: 0));
2230 QCOMPARE(item1.topLeft(), QPoint(flow == QListView::TopToBottom ? spacing : 0, spacing));
2231 if (flow == QListView::TopToBottom) {
2232 QCOMPARE(item1.width(), lv.viewport()->width() - 2 * spacing);
2233 QCOMPARE(item2.topLeft(), QPoint(spacing, spacing + item1.height() + 2 * spacing));
2234 }
2235 else { // QListView::LeftToRight
2236 QCOMPARE(item1.height(), lv.viewport()->height() - 2 * spacing);
2237 QCOMPARE(item2.topLeft(), QPoint(spacing + item1.width() + spacing, spacing));
2238 }
2239}
2240
2241void tst_QListView::testScrollToWithHidden()
2242{
2243 QListView lv;
2244
2245 QStringListModel model;
2246 model.setStringList(generateList(prefix: QString(), size: 30));
2247 lv.setModel(&model);
2248
2249 lv.setRowHidden(row: 1, hide: true);
2250 lv.setSpacing(5);
2251
2252 lv.showNormal();
2253 QVERIFY(QTest::qWaitForWindowExposed(&lv));
2254
2255 QCOMPARE(lv.verticalScrollBar()->value(), 0);
2256
2257 lv.scrollTo(index: model.index(row: 26, column: 0));
2258 int expectedScrollBarValue = lv.verticalScrollBar()->value();
2259#ifdef Q_OS_WINRT
2260 QSKIP("Might fail on WinRT - QTBUG-68297");
2261#endif
2262 QVERIFY(expectedScrollBarValue != 0);
2263
2264 lv.scrollTo(index: model.index(row: 25, column: 0));
2265 QCOMPARE(expectedScrollBarValue, lv.verticalScrollBar()->value());
2266}
2267
2268
2269
2270void tst_QListView::testViewOptions()
2271{
2272 PublicListView view;
2273 QStyleOptionViewItem options = view.viewOptions();
2274 QCOMPARE(options.decorationPosition, QStyleOptionViewItem::Left);
2275 view.setViewMode(QListView::IconMode);
2276 options = view.viewOptions();
2277 QCOMPARE(options.decorationPosition, QStyleOptionViewItem::Top);
2278}
2279
2280// make sure we have no transient scroll bars
2281class TempStyleSetter
2282{
2283public:
2284 TempStyleSetter()
2285 : m_oldStyle(QApplication::style())
2286 {
2287 m_oldStyle->setParent(nullptr);
2288 QListView tempView;
2289 if (QApplication::style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient,
2290 opt: nullptr, widget: tempView.horizontalScrollBar()))
2291 QApplication::setStyle(QStyleFactory::create("Fusion"));
2292 }
2293
2294 ~TempStyleSetter()
2295 {
2296 QApplication::setStyle(m_oldStyle);
2297 }
2298private:
2299 QStyle *m_oldStyle;
2300};
2301
2302void tst_QListView::taskQTBUG_39902_mutualScrollBars_data()
2303{
2304 QTest::addColumn<QAbstractItemView::ScrollMode>(name: "horizontalScrollMode");
2305 QTest::addColumn<QAbstractItemView::ScrollMode>(name: "verticalScrollMode");
2306 QTest::newRow(dataTag: "per item / per item") << QAbstractItemView::ScrollPerItem
2307 << QAbstractItemView::ScrollPerItem;
2308 QTest::newRow(dataTag: "per pixel / per item") << QAbstractItemView::ScrollPerPixel
2309 << QAbstractItemView::ScrollPerItem;
2310 QTest::newRow(dataTag: "per item / per pixel") << QAbstractItemView::ScrollPerItem
2311 << QAbstractItemView::ScrollPerPixel;
2312 QTest::newRow(dataTag: "per pixel / per pixel") << QAbstractItemView::ScrollPerPixel
2313 << QAbstractItemView::ScrollPerPixel;
2314}
2315
2316void tst_QListView::taskQTBUG_39902_mutualScrollBars()
2317{
2318 QFETCH(QAbstractItemView::ScrollMode, horizontalScrollMode);
2319 QFETCH(QAbstractItemView::ScrollMode, verticalScrollMode);
2320
2321 QWidget window;
2322 window.resize(w: 400, h: 300);
2323 QListView *view = new QListView(&window);
2324 // make sure we have no transient scroll bars
2325 TempStyleSetter styleSetter;
2326 QStandardItemModel model(200, 1);
2327 const QSize itemSize(100, 20);
2328 for (int i = 0; i < model.rowCount(); ++i)
2329 model.setData(index: model.index(row: i, column: 0), value: itemSize, role: Qt::SizeHintRole);
2330 view->setModel(&model);
2331
2332 view->setVerticalScrollMode(verticalScrollMode);
2333 view->setHorizontalScrollMode(horizontalScrollMode);
2334
2335 window.show();
2336 QVERIFY(QTest::qWaitForWindowExposed(&window));
2337 // make sure QListView is done with layouting the items (1/10 sec, like QListView)
2338 QTest::qWait(ms: 100);
2339
2340 model.setRowCount(2);
2341 for (int i = 0; i < model.rowCount(); ++i)
2342 model.setData(index: model.index(row: i, column: 0), value: itemSize, role: Qt::SizeHintRole);
2343 view->resize(w: itemSize.width() + view->frameWidth() * 2,
2344 h: model.rowCount() * itemSize.height() + view->frameWidth() * 2);
2345 // this will end up in a stack overflow, if QTBUG-39902 is not fixed
2346 QTest::qWait(ms: 100);
2347
2348 // these tests do not apply with transient scroll bars enabled
2349 QVERIFY (!view->style()->styleHint(QStyle::SH_ScrollBar_Transient,
2350 nullptr, view->horizontalScrollBar()));
2351
2352 // make it double as large, no scroll bars should be visible
2353 view->resize(w: (itemSize.width() + view->frameWidth() * 2) * 2,
2354 h: (model.rowCount() * itemSize.height() + view->frameWidth() * 2) * 2);
2355 QTRY_VERIFY(!view->horizontalScrollBar()->isVisible());
2356 QTRY_VERIFY(!view->verticalScrollBar()->isVisible());
2357
2358 // make it half the size, both scroll bars should be visible
2359 view->resize(w: (itemSize.width() + view->frameWidth() * 2) / 2,
2360 h: (model.rowCount() * itemSize.height() + view->frameWidth() * 2) / 2);
2361 QTRY_VERIFY(view->horizontalScrollBar()->isVisible());
2362 QTRY_VERIFY(view->verticalScrollBar()->isVisible());
2363
2364 // make it double as large, no scroll bars should be visible
2365 view->resize(w: (itemSize.width() + view->frameWidth() * 2) * 2,
2366 h: (model.rowCount() * itemSize.height() + view->frameWidth() * 2) * 2);
2367 QTRY_VERIFY(!view->horizontalScrollBar()->isVisible());
2368 QTRY_VERIFY(!view->verticalScrollBar()->isVisible());
2369
2370 // now, coming from the double size, resize it to the exactly matching size, still no scroll bars should be visible again
2371 view->resize(w: itemSize.width() + view->frameWidth() * 2,
2372 h: model.rowCount() * itemSize.height() + view->frameWidth() * 2);
2373 QTRY_VERIFY(!view->horizontalScrollBar()->isVisible());
2374 QTRY_VERIFY(!view->verticalScrollBar()->isVisible());
2375
2376 // now remove just one single pixel in height -> both scroll bars will show up since they depend on each other
2377 view->resize(w: itemSize.width() + view->frameWidth() * 2,
2378 h: model.rowCount() * itemSize.height() + view->frameWidth() * 2 - 1);
2379 QTRY_VERIFY(view->horizontalScrollBar()->isVisible());
2380 QTRY_VERIFY(view->verticalScrollBar()->isVisible());
2381
2382 // now remove just one single pixel in width -> both scroll bars will show up since they depend on each other
2383 view->resize(w: itemSize.width() + view->frameWidth() * 2 - 1,
2384 h: model.rowCount() * itemSize.height() + view->frameWidth() * 2);
2385 QTRY_VERIFY(view->horizontalScrollBar()->isVisible());
2386 QTRY_VERIFY(view->verticalScrollBar()->isVisible());
2387
2388 // finally, coming from a size being to small, resize back to the exactly matching size -> both scroll bars should disappear again
2389 view->resize(w: itemSize.width() + view->frameWidth() * 2,
2390 h: model.rowCount() * itemSize.height() + view->frameWidth() * 2);
2391 QTRY_VERIFY(!view->horizontalScrollBar()->isVisible());
2392 QTRY_VERIFY(!view->verticalScrollBar()->isVisible());
2393
2394 // now remove just one single pixel in height -> both scroll bars will show up since they depend on each other
2395 view->resize(w: itemSize.width() + view->frameWidth() * 2,
2396 h: model.rowCount() * itemSize.height() + view->frameWidth() * 2 - 1);
2397 QTRY_VERIFY(view->horizontalScrollBar()->isVisible());
2398 QTRY_VERIFY(view->verticalScrollBar()->isVisible());
2399}
2400
2401void tst_QListView::horizontalScrollingByVerticalWheelEvents()
2402{
2403#if QT_CONFIG(wheelevent)
2404 QListView lv;
2405 lv.setWrapping(true);
2406
2407 lv.setItemDelegate(new TestDelegate(&lv, QSize(100, 100)));
2408
2409 QtTestModel model(100, 1);
2410 lv.setModel(&model);
2411 lv.resize(w: 300, h: 300);
2412 lv.show();
2413 QVERIFY(QTest::qWaitForWindowExposed(&lv));
2414
2415 QPoint globalPos = lv.geometry().center();
2416 QPoint pos = lv.viewport()->geometry().center();
2417
2418 QWheelEvent wheelDownEvent(pos, globalPos, QPoint(0, 0), QPoint(0, -120), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
2419 QWheelEvent wheelUpEvent(pos, globalPos, QPoint(0, 0), QPoint(0, 120), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
2420 QWheelEvent wheelLeftDownEvent(pos, globalPos, QPoint(0, 0), QPoint(120, -120), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
2421
2422 int hValue = lv.horizontalScrollBar()->value();
2423 QCoreApplication::sendEvent(receiver: lv.viewport(), event: &wheelDownEvent);
2424 QVERIFY(lv.horizontalScrollBar()->value() > hValue);
2425
2426 QCoreApplication::sendEvent(receiver: lv.viewport(), event: &wheelUpEvent);
2427 QCOMPARE(lv.horizontalScrollBar()->value(), hValue);
2428
2429 QCoreApplication::sendEvent(receiver: lv.viewport(), event: &wheelLeftDownEvent);
2430 QCOMPARE(lv.horizontalScrollBar()->value(), hValue);
2431
2432 // ensure that vertical wheel events are not converted when vertical
2433 // scroll bar is not visible but vertical scrolling is possible
2434 lv.setWrapping(false);
2435 lv.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2436 QCoreApplication::processEvents();
2437
2438 int vValue = lv.verticalScrollBar()->value();
2439 QCoreApplication::sendEvent(receiver: lv.viewport(), event: &wheelDownEvent);
2440 QVERIFY(lv.verticalScrollBar()->value() > vValue);
2441#else
2442 QSKIP("Built with --no-feature-wheelevent");
2443#endif
2444}
2445
2446void tst_QListView::taskQTBUG_7232_AllowUserToControlSingleStep()
2447{
2448 // When we set the scrollMode to ScrollPerPixel it will adjust the scrollbars singleStep automatically
2449 // Setting a singlestep on a scrollbar should however imply that the user takes control.
2450 // Setting a singlestep to -1 return to an automatic control of the singleStep.
2451 QListView lv;
2452 lv.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
2453 lv.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
2454
2455 QStandardItemModel model(1000, 100);
2456 QString str = QString::fromLatin1(str: "This is a long string made to ensure that we get some horizontal scroll (and we want scroll)");
2457 model.setData(index: model.index(row: 0, column: 0), value: str);
2458 lv.setModel(&model);
2459 lv.setGeometry(ax: 150, ay: 150, aw: 150, ah: 150);
2460 lv.show();
2461 lv.setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
2462 lv.setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
2463 QVERIFY(QTest::qWaitForWindowExposed(&lv));
2464
2465 int vStep1 = lv.verticalScrollBar()->singleStep();
2466 int hStep1 = lv.horizontalScrollBar()->singleStep();
2467 QVERIFY(lv.verticalScrollBar()->singleStep() > 1);
2468 QVERIFY(lv.horizontalScrollBar()->singleStep() > 1);
2469
2470 lv.verticalScrollBar()->setSingleStep(1);
2471 lv.setGeometry(ax: 200, ay: 200, aw: 200, ah: 200);
2472 QCOMPARE(lv.verticalScrollBar()->singleStep(), 1);
2473
2474 lv.horizontalScrollBar()->setSingleStep(1);
2475 lv.setGeometry(ax: 150, ay: 150, aw: 150, ah: 150);
2476 QCOMPARE(lv.horizontalScrollBar()->singleStep(), 1);
2477
2478 lv.verticalScrollBar()->setSingleStep(-1);
2479 lv.horizontalScrollBar()->setSingleStep(-1);
2480 QCOMPARE(vStep1, lv.verticalScrollBar()->singleStep());
2481 QCOMPARE(hStep1, lv.horizontalScrollBar()->singleStep());
2482}
2483
2484void tst_QListView::taskQTBUG_51086_skippingIndexesInSelectedIndexes()
2485{
2486 QStandardItemModel data(10, 1);
2487 QItemSelectionModel selections(&data);
2488 PublicListView list;
2489 list.setModel(&data);
2490 list.setSelectionModel(&selections);
2491
2492 list.setRowHidden(row: 7, hide: true);
2493 list.setRowHidden(row: 8, hide: true);
2494
2495 for (int i = 0, count = data.rowCount(); i < count; ++i)
2496 selections.select(index: data.index(row: i, column: 0), command: QItemSelectionModel::Select);
2497
2498 const QModelIndexList indexes = list.selectedIndexes();
2499
2500 QVERIFY(!indexes.contains(data.index(7, 0)));
2501 QVERIFY(!indexes.contains(data.index(8, 0)));
2502}
2503
2504void tst_QListView::taskQTBUG_47694_indexOutOfBoundBatchLayout()
2505{
2506 QListView view;
2507 view.setLayoutMode(QListView::Batched);
2508 int batchSize = view.batchSize();
2509
2510 QStandardItemModel model(batchSize + 1, 1);
2511
2512 view.setModel(&model);
2513
2514 view.scrollTo(index: model.index(row: batchSize - 1, column: 0));
2515}
2516
2517void tst_QListView::itemAlignment()
2518{
2519 auto item1 = new QStandardItem("111");
2520 auto item2 = new QStandardItem("111111");
2521 QStandardItemModel model;
2522 model.appendRow(aitem: item1);
2523 model.appendRow(aitem: item2);
2524
2525 QListView w;
2526 w.setModel(&model);
2527 w.setWrapping(true);
2528 w.show();
2529 QVERIFY(QTest::qWaitForWindowExposed(&w));
2530
2531 QVERIFY(w.visualRect(item1->index()).width() > 0);
2532 QVERIFY(w.visualRect(item1->index()).width() == w.visualRect(item2->index()).width());
2533
2534 w.setItemAlignment(Qt::AlignLeft);
2535 QCoreApplication::processEvents();
2536
2537 QVERIFY(w.visualRect(item1->index()).width() < w.visualRect(item2->index()).width());
2538}
2539
2540void tst_QListView::internalDragDropMove_data()
2541{
2542 QTest::addColumn<QListView::ViewMode>(name: "viewMode");
2543 QTest::addColumn<QAbstractItemView::DragDropMode>(name: "dragDropMode");
2544 QTest::addColumn<Qt::DropActions>(name: "supportedDropActions");
2545 QTest::addColumn<Qt::DropAction>(name: "defaultDropAction");
2546 QTest::addColumn<Qt::ItemFlags>(name: "itemFlags");
2547 QTest::addColumn<bool>(name: "modelMoves");
2548 QTest::addColumn<QStringList>(name: "expectedData");
2549
2550 const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable
2551 | Qt::ItemIsEnabled
2552 | Qt::ItemIsEditable
2553 | Qt::ItemIsDragEnabled;
2554
2555 const QStringList unchanged = QStringList{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
2556 const QStringList reordered = QStringList{"0", "2", "3", "4", "5", "6", "7", "8", "9", "1"};
2557 const QStringList replaced = QStringList{"0", "2", "3", "4", "1", "6", "7", "8", "9"};
2558
2559 for (auto viewMode : { QListView::IconMode, QListView::ListMode }) {
2560 for (auto modelMoves : { true, false } ) {
2561 QByteArray rowName = viewMode == QListView::IconMode ? "icon" : "list" ;
2562 rowName += modelMoves ? ", model moves" : ", model doesn't move";
2563 QTest::newRow(dataTag: (rowName + ", copy&move").constData())
2564 << viewMode
2565 << QAbstractItemView::InternalMove
2566 << (Qt::CopyAction|Qt::MoveAction)
2567 << Qt::MoveAction
2568 << defaultFlags
2569 << modelMoves
2570 // listview in IconMode doesn't change the model
2571 << ((viewMode == QListView::IconMode && !modelMoves) ? unchanged : reordered);
2572
2573 QTest::newRow(dataTag: (rowName + ", only move").constData())
2574 << viewMode
2575 << QAbstractItemView::InternalMove
2576 << (Qt::IgnoreAction|Qt::MoveAction)
2577 << Qt::MoveAction
2578 << defaultFlags
2579 << modelMoves
2580 // listview in IconMode doesn't change the model
2581 << ((viewMode == QListView::IconMode && !modelMoves) ? unchanged : reordered);
2582
2583 QTest::newRow(dataTag: (rowName + ", replace item").constData())
2584 << viewMode
2585 << QAbstractItemView::InternalMove
2586 << (Qt::IgnoreAction|Qt::MoveAction)
2587 << Qt::MoveAction
2588 << (defaultFlags | Qt::ItemIsDropEnabled)
2589 << modelMoves
2590 << replaced;
2591 }
2592 }
2593}
2594
2595/*
2596 Test moving of items items via drag'n'drop.
2597
2598 This should reorder items when an item is dropped in between two items,
2599 or - if items can be dropped on - replace the content of the drop target.
2600
2601 Test QListView in both icon and list view modes.
2602
2603 See QTBUG-67440, QTBUG-83084, QTBUG-87057
2604*/
2605void tst_QListView::internalDragDropMove()
2606{
2607 const QString platform(QGuiApplication::platformName().toLower());
2608 if (platform != QLatin1String("xcb"))
2609 QSKIP("Need a window system with proper DnD support via injected mouse events");
2610
2611 QFETCH(QListView::ViewMode, viewMode);
2612 QFETCH(QAbstractItemView::DragDropMode, dragDropMode);
2613 QFETCH(Qt::DropActions, supportedDropActions);
2614 QFETCH(Qt::DropAction, defaultDropAction);
2615 QFETCH(Qt::ItemFlags, itemFlags);
2616 QFETCH(bool, modelMoves);
2617 QFETCH(QStringList, expectedData);
2618
2619 class ItemModel : public QStringListModel
2620 {
2621 public:
2622 ItemModel()
2623 {
2624 QStringList list;
2625 for (int i = 0; i < 10; ++i) {
2626 list << QString::number(i);
2627 }
2628 setStringList(list);
2629 }
2630
2631 Qt::DropActions supportedDropActions() const override { return m_supportedDropActions; }
2632 Qt::ItemFlags flags(const QModelIndex &index) const override
2633 {
2634 if (!index.isValid())
2635 return QStringListModel::flags(index);
2636 return m_itemFlags;
2637 }
2638 bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
2639 const QModelIndex &destinationParent, int destinationChild) override
2640 {
2641 if (!m_modelMoves) // many models don't implement moveRows
2642 return false;
2643 return QStringListModel::moveRows(sourceParent, sourceRow, count,
2644 destinationParent, destinationChild);
2645 }
2646 bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &values) override
2647 {
2648 return QStringListModel::setData(index, value: values.value(akey: Qt::DisplayRole), role: Qt::DisplayRole);
2649 }
2650 QVariant data(const QModelIndex &index, int role) const override
2651 {
2652 if (role == Qt::DecorationRole)
2653 return QColor(Qt::GlobalColor(index.row() + 1));
2654 return QStringListModel::data(index, role);
2655 }
2656 QMap<int, QVariant> itemData(const QModelIndex &index) const override
2657 {
2658 auto item = QStringListModel::itemData(index);
2659 item[Qt::DecorationRole] = data(index, role: Qt::DecorationRole);
2660 return item;
2661 }
2662
2663 Qt::DropActions m_supportedDropActions;
2664 Qt::ItemFlags m_itemFlags;
2665 bool m_modelMoves;
2666 };
2667
2668 ItemModel data;
2669 data.m_supportedDropActions = supportedDropActions;
2670 data.m_itemFlags = itemFlags;
2671 data.m_modelMoves = modelMoves;
2672
2673 QItemSelectionModel selections(&data);
2674 PublicListView list;
2675 list.setWindowTitle(QTest::currentTestFunction());
2676 list.setViewMode(viewMode);
2677 list.setDragDropMode(dragDropMode);
2678 list.setDefaultDropAction(defaultDropAction);
2679 list.setModel(&data);
2680 list.setSelectionModel(&selections);
2681 int itemHeight = list.sizeHintForIndex(index: data.index(row: 1, column: 0)).height();
2682 list.resize(w: 300, h: 15 * itemHeight);
2683 list.show();
2684 selections.select(index: data.index(row: 1, column: 0), command: QItemSelectionModel::Select);
2685 auto getSelectedTexts = [&]() -> QStringList {
2686 QStringList selectedTexts;
2687 for (auto index : selections.selectedIndexes())
2688 selectedTexts << data.itemData(index).value(akey: Qt::DisplayRole).toString();
2689 return selectedTexts;
2690 };
2691 // The test relies on the global position of mouse events; make sure
2692 // the window is properly mapped on X11.
2693 QVERIFY(QTest::qWaitForWindowActive(&list));
2694 // execute as soon as the eventloop is running again
2695 // which is the case inside list.startDrag()
2696 QTimer::singleShot(interval: 0, slot: [&]()
2697 {
2698 QPoint droppos;
2699 // take into account subtle differences between icon and list mode in QListView's drop placement
2700 if (itemFlags & Qt::ItemIsDropEnabled)
2701 droppos = list.rectForIndex(index: data.index(row: 5, column: 0)).center();
2702 else if (viewMode == QListView::IconMode)
2703 droppos = list.rectForIndex(index: data.index(row: 9, column: 0)).bottomRight() + QPoint(30, 30);
2704 else
2705 droppos = list.rectForIndex(index: data.index(row: 9, column: 0)).bottomRight();
2706
2707 QMouseEvent mouseMove(QEvent::MouseMove, droppos, list.mapToGlobal(droppos), Qt::NoButton, {}, {});
2708 QCoreApplication::sendEvent(receiver: &list, event: &mouseMove);
2709 QMouseEvent mouseRelease(QEvent::MouseButtonRelease, droppos, list.mapToGlobal(droppos), Qt::LeftButton, {}, {});
2710 QCoreApplication::sendEvent(receiver: &list, event: &mouseRelease);
2711 });
2712
2713 const QStringList expectedSelected = getSelectedTexts();
2714
2715 list.startDrag(supportedActions: Qt::MoveAction);
2716
2717 QTRY_COMPARE(data.stringList(), expectedData);
2718
2719 // if the model doesn't implement moveRows, or if items are replaced, then selection is lost
2720 if (modelMoves && !(itemFlags & Qt::ItemIsDropEnabled)) {
2721 const QStringList actualSelected = getSelectedTexts();
2722 QTRY_COMPARE(actualSelected, expectedSelected);
2723 }
2724}
2725
2726
2727void tst_QListView::scrollOnRemove_data()
2728{
2729 QTest::addColumn<QListView::ViewMode>(name: "viewMode");
2730 QTest::addColumn<QAbstractItemView::SelectionMode>(name: "selectionMode");
2731
2732 const QMetaObject &mo = QListView::staticMetaObject;
2733 const auto viewModeEnum = mo.enumerator(index: mo.indexOfEnumerator(name: "ViewMode"));
2734 const auto selectionModeEnum = mo.enumerator(index: mo.indexOfEnumerator(name: "SelectionMode"));
2735 for (auto viewMode : { QListView::ListMode, QListView::IconMode }) {
2736 const char *viewModeName = viewModeEnum.valueToKey(value: viewMode);
2737 for (int index = 0; index < selectionModeEnum.keyCount(); ++index) {
2738 const auto selectionMode = QAbstractItemView::SelectionMode(selectionModeEnum.value(index));
2739 const char *selectionModeName = selectionModeEnum.valueToKey(value: selectionMode);
2740 QTest::addRow(format: "%s, %s", viewModeName, selectionModeName) << viewMode << selectionMode;
2741 }
2742 }
2743}
2744
2745void tst_QListView::scrollOnRemove()
2746{
2747 QFETCH(QListView::ViewMode, viewMode);
2748 QFETCH(QAbstractItemView::SelectionMode, selectionMode);
2749
2750 QPixmap pixmap;
2751 if (viewMode == QListView::IconMode) {
2752 pixmap = QPixmap(25, 25);
2753 pixmap.fill(fillColor: Qt::red);
2754 }
2755
2756 QStandardItemModel model;
2757 for (int i = 0; i < 50; ++i) {
2758 QStandardItem *item = new QStandardItem(QString::number(i));
2759 item->setIcon(pixmap);
2760 model.appendRow(aitem: item);
2761 }
2762
2763 QWidget widget;
2764 QListView view(&widget);
2765 view.setFixedSize(w: 100, h: 100);
2766 view.setAutoScroll(true);
2767 if (viewMode == QListView::IconMode)
2768 view.setWrapping(true);
2769 view.setModel(&model);
2770 view.setSelectionMode(selectionMode);
2771 view.setViewMode(viewMode);
2772
2773 widget.show();
2774 QVERIFY(QTest::qWaitForWindowExposed(&widget));
2775
2776 QCOMPARE(view.verticalScrollBar()->value(), 0);
2777 const QModelIndex item25 = model.index(row: 25, column: 0);
2778 view.scrollTo(index: item25);
2779 QTRY_VERIFY(view.verticalScrollBar()->value() > 0); // layout and scrolling are delayed
2780 const int item25Position = view.verticalScrollBar()->value();
2781 // selecting a fully visible item shouldn't scroll
2782 view.selectionModel()->setCurrentIndex(index: item25, command: QItemSelectionModel::SelectCurrent);
2783 QTRY_COMPARE(view.verticalScrollBar()->value(), item25Position);
2784
2785 // removing the selected item might scroll if another item is selected
2786 model.removeRow(arow: 25);
2787
2788 // if nothing is selected now, then the view should not have scrolled
2789 if (!view.selectionModel()->selectedIndexes().count())
2790 QTRY_COMPARE(view.verticalScrollBar()->value(), item25Position);
2791}
2792
2793
2794QTEST_MAIN(tst_QListView)
2795#include "tst_qlistview.moc"
2796

source code of qtbase/tests/auto/widgets/itemviews/qlistview/tst_qlistview.cpp